Transition.react.stories.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. import React, { useState, Component, isValidElement, PureComponent, createRef } from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { storiesOf } from '@storybook/react';
  4. import { withKnobs, text, boolean } from '@storybook/addon-knobs';
  5. import withPropsCombinations from 'react-storybook-addon-props-combinations';
  6. import { Transition } from '../index';
  7. import QueueTransition from './queue-transition';
  8. import QueueStyledTransition from './queue-transition/styled';
  9. import { Modal, Button, IconButton, Collapse, Nav, Switch, RadioGroup, Radio } from '@douyinfe/semi-ui';
  10. const stories = storiesOf('semi-animation-react/Transition', module);
  11. stories.addDecorator(withKnobs);
  12. const itemStyle = {
  13. backgroundColor: 'rgb(241, 101, 101)',
  14. color: 'white',
  15. borderRadius: 4,
  16. padding: 10,
  17. textAlign: 'center',
  18. };
  19. const bigSquareStyle = {
  20. backgroundColor: 'rgb(241, 101, 101)',
  21. width: 100,
  22. height: 100,
  23. margin: 20,
  24. marginLeft: 50,
  25. borderRadius: 4,
  26. fontSize: 16,
  27. color: 'white',
  28. display: 'flex',
  29. alignItems: 'center',
  30. justifyContent: 'center',
  31. };
  32. stories.add('transition-bounceInOut', () => {
  33. class App extends PureComponent {
  34. constructor(props = {}) {
  35. super(props);
  36. this.state = {
  37. visible: true,
  38. };
  39. this.ref = createRef();
  40. }
  41. setVisible = visible => this.setState({ visible });
  42. render() {
  43. const { visible } = this.state;
  44. return (
  45. <div>
  46. <div>
  47. <Transition
  48. // config={{ ...presets.wobbly }}
  49. immediate
  50. config={{ tension: 745, friction: 35 }}
  51. from={{ scale: 0.7, opacity: { val: 0, duration: 200 } }}
  52. enter={{ scale: 1, opacity: { val: 1, duration: 200 } }}
  53. leave={{ scale: { val: 0.7, duration: 300 }, opacity: { val: 0, duration: 200 } }}
  54. >
  55. {visible
  56. ? ({ scale, opacity }) => {
  57. const node = this.ref.current;
  58. console.log('ref rect: ', node && node.getBoundingClientRect());
  59. return (
  60. <div
  61. ref={this.ref}
  62. style={{
  63. ...bigSquareStyle,
  64. opacity,
  65. transform: `scale(${scale}, ${scale})`,
  66. }}
  67. >
  68. bounceInOut
  69. </div>
  70. );
  71. }
  72. : null}
  73. </Transition>
  74. </div>
  75. <div>
  76. <button
  77. onClick={() => {
  78. this.setVisible(true);
  79. // setState('enter');
  80. }}
  81. >
  82. 入场
  83. </button>
  84. <button onClick={() => this.setVisible(false)}>出场</button>
  85. </div>
  86. </div>
  87. );
  88. }
  89. }
  90. class ModalDemo extends React.Component {
  91. constructor() {
  92. super();
  93. this.state = { visible: false, visibleWithAnimation: false };
  94. this.showDialog = this.showDialog.bind(this);
  95. this.handleOk = this.handleOk.bind(this);
  96. this.handleCancel = this.handleCancel.bind(this);
  97. }
  98. showDialog() {
  99. this.setState({
  100. visible: true,
  101. visibleWithAnimation: true,
  102. });
  103. }
  104. handleOk(e) {
  105. this.setState({
  106. // visible: false,
  107. visibleWithAnimation: false,
  108. });
  109. }
  110. handleCancel(e) {
  111. this.setState({
  112. // visible: false,
  113. visibleWithAnimation: false,
  114. });
  115. }
  116. render() {
  117. const { visibleWithAnimation, visible } = this.state;
  118. return (
  119. <>
  120. <Button onClick={this.showDialog}>Open Modal</Button>
  121. <Transition
  122. config={{
  123. // ...presets.wobbly
  124. duration: 200,
  125. easing: visibleWithAnimation ? 'easeInCubic' : 'easeOutCubic',
  126. }}
  127. from={{
  128. scale: 0,
  129. // opacity: { val: 0, easing: 'linear' },
  130. }}
  131. enter={{
  132. scale: 1,
  133. // opacity: { val: 1, easing: 'linear' },
  134. }}
  135. leave={{
  136. scale: 0,
  137. // opacity: { val: 0, easing: 'linear' },
  138. }}
  139. didLeave={() => this.setState({ visible: false })}
  140. >
  141. {visibleWithAnimation
  142. ? ({ opacity, scale }) => (
  143. <Modal
  144. title="自定义样式"
  145. visible={visible}
  146. onOk={this.handleOk}
  147. onCancel={this.handleCancel}
  148. style={{ top: '30vh', transform: `scale(${scale},${scale})` }}
  149. maskStyle={{ backgroundColor: 'pink', opacity: '.3' }}
  150. bodyStyle={{ backgroundColor: 'lightgrey' }}
  151. >
  152. <p>This is a modal with customized styles.</p>
  153. <p>More content...</p>
  154. </Modal>
  155. )
  156. : null}
  157. </Transition>
  158. </>
  159. );
  160. }
  161. }
  162. return (
  163. <>
  164. <ModalDemo />
  165. <App />
  166. </>
  167. );
  168. });
  169. stories.add('transition-bounceInOut-受控', () => {
  170. class App extends PureComponent {
  171. constructor(props = {}) {
  172. super(props);
  173. this.state = {
  174. state: 'enter',
  175. visible: true,
  176. };
  177. this.ref = createRef();
  178. }
  179. setVisible = visible => this.setState({ state: visible ? 'enter' : 'leave' });
  180. didEnter = () => {
  181. this.setState({ visible: true });
  182. };
  183. didLeave = () => {
  184. this.setState({ visible: false });
  185. };
  186. render() {
  187. const { visible, state } = this.state;
  188. return (
  189. <div>
  190. <div>
  191. <Transition
  192. // config={{ ...presets.wobbly }}
  193. state={state}
  194. config={{ tension: 745, friction: 35 }}
  195. from={{ scale: 0.7, opacity: { val: 0, duration: 200 } }}
  196. enter={{ scale: 1, opacity: { val: 1, duration: 200 } }}
  197. leave={{ scale: { val: 0.7, duration: 300 }, opacity: { val: 0, duration: 200 } }}
  198. didEnter={this.didEnter}
  199. didLeave={this.didLeave}
  200. >
  201. {state === 'enter' || visible
  202. ? ({ scale, opacity }) => {
  203. const node = this.ref.current;
  204. // console.log('ref rect: ', node && node.getBoundingClientRect());
  205. return (
  206. <div
  207. ref={this.ref}
  208. style={{
  209. ...bigSquareStyle,
  210. opacity,
  211. transform: `scale(${scale}, ${scale})`,
  212. }}
  213. >
  214. bounceInOut
  215. </div>
  216. );
  217. }
  218. : null}
  219. </Transition>
  220. </div>
  221. <div>
  222. <button onClick={() => this.setVisible(true)}>入场</button>
  223. <button onClick={() => this.setVisible(false)}>出场</button>
  224. </div>
  225. </div>
  226. );
  227. }
  228. }
  229. return (
  230. <>
  231. <App />
  232. </>
  233. );
  234. });
  235. stories.add('transition-slideInOutDown', () => {
  236. const SlideInOutDown = function SlideInOutDown(props = {}) {
  237. return (
  238. <Transition from={{ maxHeight: 0 }} enter={{ maxHeight: 100 }} leave={{ maxHeight: 0 }}>
  239. {props.children}
  240. </Transition>
  241. );
  242. };
  243. class App extends PureComponent {
  244. state = {
  245. itemKey: '',
  246. };
  247. setItemKey = itemKey => this.setState({ itemKey });
  248. render() {
  249. const { itemKey } = this.state;
  250. return (
  251. <Collapse accordion onChange={this.setItemKey}>
  252. {['1', '2', '3'].map(key => (
  253. <Collapse.Panel header={`This is panel header ${key}`} itemKey={key} key={key}>
  254. {/* <SlideInOutDown> */}
  255. <Transition from={{ maxHeight: 0 }} enter={{ maxHeight: 100 }} leave={{ maxHeight: 0 }}>
  256. {itemKey === key
  257. ? ({ maxHeight }) => (
  258. <p
  259. style={{
  260. maxHeight: `${maxHeight}%`,
  261. transform: `translateY(${maxHeight - 100}%)`,
  262. }}
  263. >
  264. Hi, bytedance dance dance. This is the docsite of Semi UI.{' '}
  265. </p>
  266. )
  267. : null}
  268. {/* </SlideInOutDown> */}
  269. </Transition>
  270. </Collapse.Panel>
  271. ))}
  272. </Collapse>
  273. );
  274. }
  275. }
  276. return <App />;
  277. });
  278. stories.add('transition-slideInOutLeft', () => {
  279. function SlideInOutLeft(props = {}) {
  280. return (
  281. <Transition {...props} from={{ translateX: -100 }} enter={{ translateX: 0 }} leave={{ translateX: -100 }}>
  282. {props.children}
  283. </Transition>
  284. );
  285. }
  286. class NavApp extends React.Component {
  287. constructor() {
  288. super();
  289. this.state = {
  290. isCollapsed: true,
  291. defaultOpenKeys: ['2', '2-3'],
  292. mode: 'vertical',
  293. navHeight: 480,
  294. selectedKeys: [],
  295. openKeys: ['2'],
  296. };
  297. this.onSelect = (data = {}) => {
  298. console.log('trigger onSelect: ', data);
  299. let selectedKeys = Array.from(data.selectedKeys);
  300. this.setState({ selectedKeys });
  301. };
  302. this.onOpenChange = (data = {}) => {
  303. console.log('trigger onOpenChange: ', data);
  304. let openKeys = Array.from(data.openKeys);
  305. this.setState({ openKeys });
  306. };
  307. }
  308. updateCollapsed(isCollapsed) {
  309. this.setState({ isCollapsed });
  310. }
  311. toggleMode() {
  312. let { mode, navHeight } = this.state;
  313. if (mode === 'vertical') {
  314. mode = 'horizontal';
  315. navHeight = 60;
  316. } else {
  317. mode = 'vertical';
  318. navHeight = 480;
  319. }
  320. this.setState({ mode, navHeight });
  321. }
  322. renderNavWithTransform = ({ transform, translateX }) => {
  323. let { isCollapsed, defaultOpenKeys, mode, navHeight, selectedKeys, openKeys } = this.state;
  324. let testIcon = '//lf1-cdn-tos.bytescm.com/obj/mosaic-legacy/da9d0015af0f09667998';
  325. let vigoIcon = '//lf1-cdn-tos.bytescm.com/obj/mosaic-legacy/504100070cbe0498d66f';
  326. let icon3 = '//lf1-cdn-tos.bytescm.com/eesz/resource/bear/public/doc-favicon-v4.902ddd709d8aaa55df8d.ico';
  327. return (
  328. <Nav
  329. // isCollapsed={isCollapsed}
  330. defaultOpenKeys={defaultOpenKeys}
  331. style={{ height: navHeight, transform: `translateX(${translateX}%)` }}
  332. mode={mode}
  333. selectedKeys={selectedKeys}
  334. openKeys={openKeys}
  335. onSelect={this.onSelect}
  336. onOpenChange={this.onOpenChange}
  337. >
  338. <Nav.Header logo="doc-toast" text="互娱运营" />
  339. <Nav.Item
  340. itemKey={'1'}
  341. text={<strong>火山运营</strong>}
  342. icon={<img width="20" height="20" src={vigoIcon} />}
  343. />
  344. <Nav.Sub
  345. itemKey={'2'}
  346. text={<strong>抖音运营</strong>}
  347. icon={<img width="20" height="20" src={testIcon} />}
  348. >
  349. {['2-1', '2-2'].map(k => (
  350. <Nav.Item key={k} itemKey={String(k)} text={'Option ' + k} />
  351. ))}
  352. <Nav.Sub text={'Group 2-3'} icon={<img width="20" height="20" src={icon3} />} itemKey="2-3">
  353. <Nav.Item itemKey={'2-3-1'} text={'Option 2-3-1'} />
  354. <Nav.Item itemKey={'2-3-2'} text={'Option 2-3-2'} />
  355. </Nav.Sub>
  356. </Nav.Sub>
  357. </Nav>
  358. );
  359. };
  360. render() {
  361. let { isCollapsed, defaultOpenKeys, mode, navHeight, selectedKeys, openKeys } = this.state;
  362. let testIcon = '//lf1-cdn-tos.bytescm.com/obj/mosaic-legacy/da9d0015af0f09667998';
  363. let vigoIcon = '//lf1-cdn-tos.bytescm.com/obj/mosaic-legacy/504100070cbe0498d66f';
  364. let icon3 = '//lf1-cdn-tos.bytescm.com/eesz/resource/bear/public/doc-favicon-v4.902ddd709d8aaa55df8d.ico';
  365. return (
  366. <div style={{ position: 'relative' }}>
  367. <SlideInOutLeft>
  368. {isCollapsed
  369. ? ({ transform, translateX }) => (
  370. <Nav
  371. // isCollapsed={isCollapsed}
  372. defaultOpenKeys={defaultOpenKeys}
  373. style={{ height: navHeight, transform: `translateX(${translateX}%)` }}
  374. mode={mode}
  375. selectedKeys={selectedKeys}
  376. openKeys={openKeys}
  377. onSelect={this.onSelect}
  378. onOpenChange={this.onOpenChange}
  379. >
  380. <Nav.Header logo="doc-toast" text="互娱运营" />
  381. <Nav.Item
  382. itemKey={'1'}
  383. text={<strong>火山运营</strong>}
  384. icon={<img width="20" height="20" src={vigoIcon} />}
  385. />
  386. <Nav.Sub
  387. itemKey={'2'}
  388. text={<strong>抖音运营</strong>}
  389. icon={<img width="20" height="20" src={testIcon} />}
  390. >
  391. {['2-1', '2-2'].map(k => (
  392. <Nav.Item key={k} itemKey={String(k)} text={'Option ' + k} />
  393. ))}
  394. <Nav.Sub
  395. text={'Group 2-3'}
  396. icon={<img width="20" height="20" src={icon3} />}
  397. itemKey="2-3"
  398. >
  399. <Nav.Item itemKey={'2-3-1'} text={'Option 2-3-1'} />
  400. <Nav.Item itemKey={'2-3-2'} text={'Option 2-3-2'} />
  401. </Nav.Sub>
  402. </Nav.Sub>
  403. </Nav>
  404. )
  405. : null}
  406. </SlideInOutLeft>
  407. <IconButton
  408. style={{ position: 'absolute', left: 10, top: 10 }}
  409. title="展开/收起切换"
  410. iconType="list"
  411. onClick={() => this.updateCollapsed(!isCollapsed)}
  412. />
  413. </div>
  414. );
  415. }
  416. }
  417. return <NavApp />;
  418. });
  419. stories.add('queue transition', () => {
  420. const Demo = () => {
  421. const animStyle = { ...itemStyle, width: 100, marginBottom: 10 };
  422. const [state, setState] = useState('enter');
  423. const [position, setPosition] = useState('left');
  424. const onSwitchPosition = e => {
  425. setPosition(e.target.value);
  426. };
  427. const positionList = ['left', 'top', 'right', 'bottom'];
  428. return (
  429. <div style={{ padding: 100 }}>
  430. <QueueTransition state={state} position={position}>
  431. <div style={{ ...animStyle }}>1</div>
  432. <div style={{ ...animStyle }}>2</div>
  433. <div style={{ ...animStyle }}>3</div>
  434. <div style={{ ...animStyle }}>4</div>
  435. </QueueTransition>
  436. <div>
  437. <div>
  438. <p>方向</p>
  439. <RadioGroup value={position} onChange={onSwitchPosition}>
  440. {positionList.map(pos => (
  441. <Radio value={pos} key={pos}>
  442. {pos}
  443. </Radio>
  444. ))}
  445. </RadioGroup>
  446. </div>
  447. <div>
  448. <Button onClick={() => setState('enter')}>入场</Button>
  449. <Button onClick={() => setState('leave')}>离场</Button>
  450. </div>
  451. </div>
  452. </div>
  453. );
  454. };
  455. return <Demo />;
  456. });
  457. stories.add('queue styled transition', () => {
  458. const Demo = () => {
  459. const animStyle = {
  460. backgroundColor: 'rgb(241, 101, 101)',
  461. color: 'white',
  462. borderRadius: 4,
  463. padding: 10,
  464. textAlign: 'center',
  465. width: 100,
  466. marginBottom: 10,
  467. };
  468. const [state, setState] = useState('enter');
  469. const [position, setPosition] = useState('Left');
  470. const switchState = state => {
  471. setState(undefined);
  472. setTimeout(() => setState(state));
  473. };
  474. const onSwitchPosition = e => {
  475. setPosition(e.target.value);
  476. };
  477. const positionList = ['Left', 'Up', 'Right', 'Down'];
  478. return (
  479. <div style={{ padding: 100, display: 'flex', flexDirection: 'row' }}>
  480. <div>
  481. <QueueStyledTransition state={state} position={position}>
  482. <div style={{ ...animStyle }}>1</div>
  483. <div style={{ ...animStyle }}>2</div>
  484. <div style={{ ...animStyle }}>3</div>
  485. <div style={{ ...animStyle }}>4</div>
  486. </QueueStyledTransition>
  487. </div>
  488. <div style={{ marginLeft: 20 }}>
  489. <div style={{ display: 'flex' }}>
  490. <label>方向:</label>
  491. <RadioGroup value={position} onChange={onSwitchPosition}>
  492. {positionList.map(pos => (
  493. <Radio value={pos} key={pos}>
  494. {pos}
  495. </Radio>
  496. ))}
  497. </RadioGroup>
  498. </div>
  499. <div>
  500. <label>时机:</label>
  501. <Button onClick={() => switchState('enter')}>入场</Button>
  502. <Button onClick={() => switchState('leave')}>离场</Button>
  503. </div>
  504. </div>
  505. </div>
  506. );
  507. };
  508. return <Demo />;
  509. });