mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
carousel 初版
This commit is contained in:
parent
c27a13e0af
commit
42ebbd2d50
@ -6,6 +6,31 @@ export default {
|
||||
"type": "audio",
|
||||
"autoPlay": false,
|
||||
"src": "http://www.ytmp3.cn/down/32791.mp3",
|
||||
},
|
||||
{
|
||||
"type": 'form',
|
||||
"title": '',
|
||||
"actions": [],
|
||||
"className": 'b v-middle inline w-lg h-xs',
|
||||
"controls": [
|
||||
{
|
||||
"type": "card",
|
||||
"className": 'v-middle w inline no-border',
|
||||
"header": {
|
||||
"title": "歌曲名称",
|
||||
"subTitle": "专辑名称",
|
||||
"description": "description",
|
||||
"avatarClassName": "pull-left thumb-md avatar m-r no-border",
|
||||
"avatar": "http://hiphotos.baidu.com/fex/%70%69%63/item/c9fcc3cec3fdfc03ccabb38edd3f8794a4c22630.jpg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "audio",
|
||||
"className": 'v-middle no-border',
|
||||
"src": "http://www.ytmp3.cn/down/32791.mp3",
|
||||
"controls": ['play']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -10,6 +10,11 @@ export default {
|
||||
label: "ID",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "carousel",
|
||||
label: "轮播图",
|
||||
type: "carousel"
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
label: "文本",
|
||||
|
@ -2,7 +2,76 @@ export default {
|
||||
type: 'page',
|
||||
title: '标题',
|
||||
remark: '提示 Tip',
|
||||
body: "内容部分. 可以使用 \\${var} 获取变量。如: `\\$date`: ${date}",
|
||||
data: {
|
||||
id: 1,
|
||||
image: "https://www.baidu.com/img/bd_logo1.png",
|
||||
carousel: [
|
||||
{
|
||||
html: '<p style="height: 300px;">This is data </p>'
|
||||
},
|
||||
/*{
|
||||
label: 'This is data and is label'
|
||||
},*/
|
||||
/*{
|
||||
image: 'https://www.baidu.com/img/bd_logo1.png'
|
||||
},*/
|
||||
{
|
||||
// 狗
|
||||
image: 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
|
||||
},
|
||||
{
|
||||
image: 'https://gss0.bdstatic.com/5bVWsj_p_tVS5dKfpU_Y_D3/res/r/image/2018-09-29/208e4ce8af2846d584cbe55b245a4134.jpeg'
|
||||
}/*,
|
||||
{
|
||||
image: 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
|
||||
// image: 'https://gss0.bdstatic.com/5bVWsj_p_tVS5dKfpU_Y_D3/res/r/image/2017-09-27/297f5edb1e984613083a2d3cc0c5bb36.png'
|
||||
}*/
|
||||
]
|
||||
},
|
||||
body: [{
|
||||
type: 'form',
|
||||
title: '表单',
|
||||
controls: [
|
||||
{
|
||||
type: 'carousel',
|
||||
controlTheme: 'light',
|
||||
name: 'carousel',
|
||||
label: 'carousel',
|
||||
animation: 'slideLeft'
|
||||
}
|
||||
]
|
||||
},
|
||||
/*{
|
||||
"type": "card",
|
||||
className: 'v-middle w',
|
||||
"header": {
|
||||
"title": "Title",
|
||||
"subTitle": "Sub Title",
|
||||
"description": "description",
|
||||
"avatarClassName": "pull-left thumb-md avatar b-3x m-r",
|
||||
"avatar": "http://hiphotos.baidu.com/fex/%70%69%63/item/c9fcc3cec3fdfc03ccabb38edd3f8794a4c22630.jpg"
|
||||
}
|
||||
}*/
|
||||
/*{
|
||||
type: 'carousel',
|
||||
width: '500',
|
||||
height: '200',
|
||||
controlTheme: 'dark',
|
||||
controls: ['arrows', 'dots'],
|
||||
animation: 'slideRight',
|
||||
options: [
|
||||
{
|
||||
html: '<p style="height: 300px;">This is data </p>'
|
||||
},
|
||||
{
|
||||
label: 'This is data and is label'
|
||||
},
|
||||
{
|
||||
image: 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
|
||||
}
|
||||
]
|
||||
}*/
|
||||
],
|
||||
aside: '边栏部分',
|
||||
toolbar: '工具栏',
|
||||
initApi: '/api/mock2/page/initData'
|
||||
|
@ -23,7 +23,11 @@ module.exports = function(req, res) {
|
||||
list: repeat(() => ({
|
||||
title: '{{name.title}}',
|
||||
description: '{{lorem.words}}'
|
||||
}), Math.round(Math.random() * 10)),
|
||||
}), Math.round(Math.random() * 3)),
|
||||
carousel: repeat(() => ({
|
||||
image: 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg',
|
||||
// html: '<p>{{lorem.words}}</p>'
|
||||
}), Math.round(Math.random() * 5)),
|
||||
date: Math.round(Date.now() / 1000),
|
||||
// image: '{{image.imageUrl}}',
|
||||
image: 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg',
|
||||
|
141
scss/components/_carousel.scss
Normal file
141
scss/components/_carousel.scss
Normal file
@ -0,0 +1,141 @@
|
||||
@mixin arrow-control {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
font: 16px/30px sans-serif;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
@mixin svg-theme {
|
||||
&.light {
|
||||
svg {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
|
||||
&.dark {
|
||||
svg {
|
||||
fill: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Carousel {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
&-container {
|
||||
min-width: 100px;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.#{$ns}Carousel-item {
|
||||
top:0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
background: lightblue;
|
||||
transition: ease-out all 0.5s;
|
||||
|
||||
&.fade {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.fade.in {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.slideLeft {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
&.slideLeft.in {
|
||||
transform: translateX(0);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.slideLeft.out {
|
||||
transform: translateX(-100%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&.slideRight {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
&.slideRight.in {
|
||||
transform: translateX(0);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.slideRight.out {
|
||||
transform: translateX(100%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
> span > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-control-dots {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
bottom: 0px;
|
||||
text-align: center;
|
||||
|
||||
.#{$ns}Carousel-dot {
|
||||
display: inline-block;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
margin: 7px 5px;
|
||||
transition-duration: 300ms;
|
||||
|
||||
&.light {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
&.is-default {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-control-arrows {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
|
||||
.#{$ns}Carousel-leftArrow {
|
||||
@include arrow-control;
|
||||
@include svg-theme;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.#{$ns}Carousel-rightArrow {
|
||||
@include arrow-control;
|
||||
@include svg-theme;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
@ -432,6 +432,7 @@ $TagControl-sugTip-color: $primary;
|
||||
@import"../components/pagination";
|
||||
@import"../components/wrapper";
|
||||
@import"../components/status";
|
||||
@import"../components/carousel";
|
||||
|
||||
@import "../components/form/fieldset";
|
||||
@import "../components/form/group";
|
||||
|
@ -59,6 +59,7 @@ $Form-input-borderColor: #cfdadd;
|
||||
@import"../components/pagination";
|
||||
@import"../components/wrapper";
|
||||
@import"../components/status";
|
||||
@import"../components/carousel";
|
||||
|
||||
@import "../components/form/fieldset";
|
||||
@import "../components/form/group";
|
||||
|
@ -75,3 +75,28 @@ export const pauseIcon = (
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export const dotIcon = (
|
||||
<svg className="icon" viewBox="0 0 1024 1024" version="1.1">
|
||||
<path
|
||||
d="M512.006827 3.413333C792.855893 3.413333 1020.586667 231.10656 1020.586667 511.993173 1020.586667 792.8832 792.855893 1020.586667 512.006827 1020.586667S3.413333 792.85248 3.413333 511.993173 231.120213 3.413333 512.006827 3.413333z"
|
||||
p-id="4070"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export const leftArrowIcon = (
|
||||
<svg className="icon" viewBox="0 0 1024 1024" version="1.1">
|
||||
<path
|
||||
d="M324.211517 511.805631 787.889594 73.082583c16.19422-16.630365 16.19422-43.974704 0-60.605068-16.19422-16.630365-42.495607-16.630365-58.613976 0L235.750113 479.360302c-8.647031 8.969398-12.344775 20.934917-11.719003 32.445329-0.644735 11.90863 3.071972 23.874149 11.719003 32.824585l493.506542 466.882788c16.118369 16.649327 42.438718 16.649327 58.613976 0 16.19422-17.085471 16.19422-43.974704 0-60.605068L324.211517 511.805631"
|
||||
p-id="2160"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export const rightArrowIcon = (
|
||||
<svg className="icon" viewBox="0 0 1024 1024" version="1.1">
|
||||
<path
|
||||
d="M311.559054 1013.77369L767.908116 512.684524 311.559054 12.234501a31.318073 31.318073 0 1 0-46.657538 41.544383L679.706197 512.684524 267.458094 969.672731a31.318073 31.318073 0 0 0 46.018393 42.183526z"
|
||||
p-id="1981"
|
||||
/>
|
||||
</svg>
|
||||
);
|
@ -149,6 +149,7 @@ import './renderers/Wrapper';
|
||||
import './renderers/IFrame';
|
||||
import './renderers/QRCode';
|
||||
import './renderers/Icon';
|
||||
import './renderers/Carousel';
|
||||
import Scoped, {ScopedContext} from './Scoped';
|
||||
|
||||
import {
|
||||
|
@ -379,7 +379,7 @@ export class Audio extends React.Component<AudioProps, AudioState> {
|
||||
const {muted} = this.state;
|
||||
|
||||
return (
|
||||
<div className={cx(inline ? 'Audio--inline' : '')}>
|
||||
<div className={cx('Audio', className, inline ? 'Audio--inline' : '')}>
|
||||
<audio
|
||||
className={cx('Audio-original')}
|
||||
ref={this.audioRef}
|
||||
@ -390,7 +390,7 @@ export class Audio extends React.Component<AudioProps, AudioState> {
|
||||
loop={loop}>
|
||||
<source src={src} />
|
||||
</audio>
|
||||
<div className={cx('Audio', className)}>
|
||||
<div className={cx('Audio-controls')}>
|
||||
{controls && controls.map((control:string, index:number) => {
|
||||
control = 'render' + upperFirst(control);
|
||||
const method:'renderRates'|'renderPlay'|'renderTime'|'renderProcess'|'renderVolume'|'render' = control as any;
|
||||
|
292
src/renderers/Carousel.tsx
Normal file
292
src/renderers/Carousel.tsx
Normal file
@ -0,0 +1,292 @@
|
||||
import * as React from 'react';
|
||||
import Transition, {ENTERED, ENTERING, EXITED, EXITING} from 'react-transition-group/Transition';
|
||||
import {Renderer, RendererProps} from '../factory';
|
||||
import {autobind} from '../utils/helper';
|
||||
import {dotIcon, leftArrowIcon, rightArrowIcon} from '../components/icons';
|
||||
|
||||
const animationStyles: {
|
||||
[propName: string]: string;
|
||||
} = {
|
||||
[ENTERING]: 'in',
|
||||
[ENTERED]: 'in',
|
||||
[EXITING]: 'out',
|
||||
// [EXITED]: 'out'
|
||||
};
|
||||
export interface CarouselProps extends RendererProps {
|
||||
className?: string;
|
||||
auto?: boolean;
|
||||
value?: any;
|
||||
placeholder?: any;
|
||||
width?: number;
|
||||
height?: number;
|
||||
controls: string[];
|
||||
interval: number;
|
||||
duration: number;
|
||||
controlTheme: 'light' | 'dark';
|
||||
animation: 'fade' | 'slideLeft' | 'slideRight';
|
||||
}
|
||||
|
||||
export interface CarouselState {
|
||||
current: number;
|
||||
options: any[];
|
||||
showArrows: boolean;
|
||||
nextAnimation?: 'slideLeft' | 'slideRight' | '';
|
||||
}
|
||||
|
||||
export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
||||
wrapperRef: HTMLDivElement;
|
||||
intervalTimeout: any;
|
||||
durationTimeout: any;
|
||||
|
||||
static defaultProps: Pick<
|
||||
CarouselProps,
|
||||
'auto' | 'interval' | 'duration' | 'controlTheme' | 'animation' | 'controls' | 'placeholder'
|
||||
> = {
|
||||
auto: true,
|
||||
interval: 2000,
|
||||
duration: 500,
|
||||
controlTheme: 'light',
|
||||
animation: 'fade',
|
||||
controls: ['dots', 'arrows'],
|
||||
placeholder: ''
|
||||
};
|
||||
|
||||
state:CarouselState = {
|
||||
current: 0,
|
||||
options: this.props.value ? this.props.value : this.props.options ? this.props.options : [],
|
||||
showArrows: false,
|
||||
nextAnimation: ''
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.prepareAutoSlide();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearAutoTimeout()
|
||||
}
|
||||
|
||||
@autobind
|
||||
prepareAutoSlide () {
|
||||
if (this.state.options.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearAutoTimeout();
|
||||
if (this.props.auto) {
|
||||
this.intervalTimeout = setTimeout(this.autoSlide, this.props.interval);
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
autoSlide (rel?:string) {
|
||||
this.clearAutoTimeout();
|
||||
const {animation} = this.props;
|
||||
let {nextAnimation} = this.state;
|
||||
|
||||
switch (rel) {
|
||||
case 'prev':
|
||||
animation.includes('slide') ? nextAnimation = 'slideLeft' : nextAnimation = '';
|
||||
this.transitFramesTowards('right', nextAnimation);
|
||||
break;
|
||||
case 'next':
|
||||
animation.includes('slide') ? nextAnimation = 'slideRight' : nextAnimation = '';
|
||||
this.transitFramesTowards('left', nextAnimation);
|
||||
break;
|
||||
default:
|
||||
nextAnimation = '';
|
||||
this.transitFramesTowards('left', nextAnimation);
|
||||
break;
|
||||
}
|
||||
|
||||
this.durationTimeout = setTimeout(this.prepareAutoSlide, this.props.duration);
|
||||
}
|
||||
|
||||
@autobind
|
||||
transitFramesTowards (direction:string, nextAnimation: 'slideLeft' | 'slideRight' | '') {
|
||||
let {current} = this.state;
|
||||
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
current = this.getFrameId('next');
|
||||
break;
|
||||
case 'right':
|
||||
current = this.getFrameId('prev');
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
current,
|
||||
nextAnimation
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
getFrameId (pos?:string) {
|
||||
const {options, current} = this.state;
|
||||
const total = options.length;
|
||||
switch (pos) {
|
||||
case 'prev':
|
||||
return (current - 1 + total) % total;
|
||||
case 'next':
|
||||
return (current + 1) % total;
|
||||
default:
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
next () {
|
||||
this.autoSlide('next');
|
||||
}
|
||||
|
||||
@autobind
|
||||
prev () {
|
||||
this.autoSlide('prev');
|
||||
}
|
||||
|
||||
@autobind
|
||||
clearAutoTimeout () {
|
||||
clearTimeout(this.intervalTimeout);
|
||||
clearTimeout(this.durationTimeout);
|
||||
}
|
||||
|
||||
setWrapperRef = (wrapper:HTMLDivElement) => {
|
||||
this.wrapperRef = wrapper;
|
||||
}
|
||||
|
||||
renderDots() {
|
||||
const {classnames: cx, controlTheme} = this.props;
|
||||
const {current, options} = this.state;
|
||||
return (
|
||||
<div
|
||||
className={cx('Carousel-control-dots')}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseMove={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
{Array.from({length: options.length}).map((_, i) =>
|
||||
<span key={i} className={cx('Carousel-dot', controlTheme, current === i ? 'is-active' : 'is-default')}></span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderButtons() {
|
||||
const {classnames: cx, controlTheme} = this.props;
|
||||
return (
|
||||
<div
|
||||
className={cx('Carousel-control-arrows')}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseMove={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
<div className={cx('Carousel-leftArrow', controlTheme)} onClick={this.prev}>{leftArrowIcon}</div>
|
||||
<div className={cx('Carousel-rightArrow', controlTheme)} onClick={this.next}>{rightArrowIcon}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@autobind
|
||||
defaultSchema() {
|
||||
return {
|
||||
type: 'tpl',
|
||||
tpl: "<% if (data.image) { %> <img src=\"<%= data.image %>\" /> <% } else if (data.label) { %> <div> <%= data.label %> </div> <% } else if (data.html) { %> <%= data.html %> <% } %>"
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleMouseEnter() {
|
||||
this.setState({
|
||||
showArrows: true
|
||||
});
|
||||
this.clearAutoTimeout();
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleMouseLeave() {
|
||||
this.setState({
|
||||
showArrows: false
|
||||
});
|
||||
this.prepareAutoSlide();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
render,
|
||||
className,
|
||||
classnames: cx,
|
||||
itemSchema,
|
||||
animation,
|
||||
width,
|
||||
height,
|
||||
controls,
|
||||
placeholder
|
||||
} = this.props;
|
||||
const {
|
||||
options,
|
||||
showArrows,
|
||||
current,
|
||||
nextAnimation
|
||||
} = this.state;
|
||||
|
||||
let body:JSX.Element | null = null;
|
||||
const [dots, arrows] = [controls.indexOf('dots') > -1, controls.indexOf('arrows') > -1];
|
||||
const animationName = nextAnimation || animation;
|
||||
const style = {
|
||||
width: width + 'px',
|
||||
height: height + 'px'
|
||||
};
|
||||
|
||||
if (options && options.length) {
|
||||
body = (
|
||||
<div
|
||||
ref={this.setWrapperRef}
|
||||
className={cx('Carousel-container')}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseMove={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
style={style}
|
||||
>
|
||||
{options.map((option:any, key:number) => (
|
||||
<Transition
|
||||
mountOnEnter={false}
|
||||
unmountOnExit={false}
|
||||
in={key === current}
|
||||
timeout={500}
|
||||
key={key}
|
||||
>
|
||||
{(status:string) => {
|
||||
if (status === ENTERING) {
|
||||
this.wrapperRef.childNodes.forEach((item:HTMLElement) => item.offsetHeight);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx('Carousel-item', animationName, animationStyles[status])}>
|
||||
{render(`${current}/body`, itemSchema ? itemSchema : this.defaultSchema(), {
|
||||
data: option
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Transition>
|
||||
))}
|
||||
{dots ? this.renderDots() : null}
|
||||
{arrows && showArrows ? this.renderButtons() : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx('Carousel', className)}>
|
||||
{body ? body : placeholder}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)carousel/,
|
||||
name: 'carousel',
|
||||
})
|
||||
export class CarouselRenderer extends Carousel {}
|
Loading…
Reference in New Issue
Block a user