popover.stories.jsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. import React, { useState } from 'react';
  2. import Popover from '../index';
  3. import { strings } from '@douyinfe/semi-foundation/tooltip/constants';
  4. import { Button, Input, Table, IconButton, Modal, Tag, Space, Select } from '@douyinfe/semi-ui';
  5. import SelectInPopover from './SelectInPopover';
  6. import BtnClose from './BtnClose';
  7. import PopRight from './PopRight';
  8. import NestedPopover from './NestedPopover';
  9. import ArrowPointAtCenter from './ArrowPointAtCenter';
  10. import { IconDelete } from '@douyinfe/semi-icons';
  11. const Option = Select.Option;
  12. export default {
  13. title: 'Popover',
  14. parameters: {
  15. chromatic: { disableSnapshot: true },
  16. },
  17. }
  18. let style = {
  19. display: 'inline-block',
  20. padding: '20px',
  21. };
  22. export const _Popover = () => (
  23. <div>
  24. <div style={style}>
  25. <Popover content="ies vigo" title="bytedance" position="bottom" visible={true} showArrow>
  26. bottom hover
  27. </Popover>
  28. </div>
  29. <div style={style}>
  30. <Popover content="ies vigo" title="bytedance" trigger="click" position="bottom">
  31. bottom click
  32. </Popover>
  33. </div>
  34. <div style={style}>
  35. <Popover content="ies vigo" title="bytedance" trigger="click" position="right">
  36. <Button>Pos:right, trigger: Click</Button>
  37. </Popover>
  38. </div>
  39. <div style={style}>
  40. <Popover
  41. content={<Button type="warning">btn</Button>}
  42. title="bytedance"
  43. trigger="click"
  44. position="right"
  45. >
  46. content is Node
  47. </Popover>
  48. </div>
  49. <div style={style}>
  50. <Popover content={<Input />} title="bytedance" trigger="click" position="right">
  51. content is Node
  52. </Popover>
  53. </div>
  54. </div>
  55. );
  56. _Popover.story = {
  57. name: 'popover',
  58. };
  59. export const Positions = () => (
  60. <div
  61. style={{
  62. width: 480,
  63. height: 360,
  64. boxSizing: 'content-box',
  65. padding: '150px 50px',
  66. display: 'flex',
  67. flexWrap: 'wrap',
  68. justifyContent: 'space-evenly',
  69. }}
  70. >
  71. {strings.POSITION_SET.map(pos => (
  72. <Popover
  73. key={pos}
  74. content={
  75. <div
  76. style={{
  77. padding: 20,
  78. }}
  79. >
  80. <p>Hi Bytedancer!</p>
  81. </div>
  82. }
  83. trigger="click"
  84. position={pos}
  85. >
  86. <Button key={pos}>{pos}</Button>
  87. </Popover>
  88. ))}
  89. </div>
  90. );
  91. Positions.story = {
  92. name: 'positions',
  93. };
  94. export const PopConfirm = () => (
  95. <div>
  96. <div style={style}>
  97. <Popover isConfirmMode content="hi byteddance ies">
  98. <a>IconDelete</a>
  99. </Popover>
  100. </div>
  101. </div>
  102. );
  103. PopConfirm.story = {
  104. name: 'popConfirm',
  105. };
  106. class Demo extends React.Component {
  107. constructor(props) {
  108. super(props);
  109. this.state = {
  110. visible: false,
  111. };
  112. this.changeVisible = this.changeVisible.bind(this);
  113. this.renderContent = this.renderContent.bind(this);
  114. }
  115. changeVisible(visible = true) {
  116. this.setState({
  117. visible,
  118. });
  119. }
  120. renderContent() {
  121. return (
  122. <>
  123. <p>hi byteddance ies</p>
  124. <Button onClick={() => this.changeVisible(false)}>cancel</Button>
  125. <Button onClick={() => this.changeVisible(false)}>confirm</Button>
  126. </>
  127. );
  128. }
  129. render() {
  130. const content = this.renderContent();
  131. const { visible } = this.state;
  132. return (
  133. <Popover trigger="custom" content={content} visible={visible} position="bottomLeft">
  134. <Button onClick={() => this.changeVisible(true)}>show Popover</Button>
  135. </Popover>
  136. );
  137. }
  138. }
  139. export const PopoverCustomVisible = () => <Demo />;
  140. PopoverCustomVisible.story = {
  141. name: 'popover custom visible',
  142. };
  143. CompositeComponent.story = { name: '复合组件' };
  144. export function CompositeComponent() {
  145. class TableApp extends React.Component {
  146. constructor(props) {
  147. super(props);
  148. this.raw = [
  149. {
  150. key: '1',
  151. name: 'John Brown',
  152. age: 32,
  153. address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
  154. },
  155. {
  156. key: '2',
  157. name: 'Jim Green',
  158. age: 42,
  159. address: 'London No. 1 Lake Park',
  160. },
  161. {
  162. key: '3',
  163. name: 'Joe Black',
  164. age: 32,
  165. address: 'Sidney No. 1 Lake Park',
  166. },
  167. {
  168. key: '4',
  169. name: 'Disabled User',
  170. age: 99,
  171. address: 'Sidney No. 1 Lake Park',
  172. },
  173. ];
  174. this.state = {
  175. dataSource: [...this.raw],
  176. modalVisible: false,
  177. columns: [
  178. {
  179. title: 'Name',
  180. dataIndex: 'name',
  181. render: text => <a href="javascript:;">{text}</a>,
  182. },
  183. {
  184. title: 'Age',
  185. dataIndex: 'age',
  186. },
  187. {
  188. title: 'Address',
  189. dataIndex: 'address',
  190. },
  191. {
  192. title: 'Operation',
  193. render: (text, record) => (
  194. <div>
  195. <Button icon={<IconDelete />} onClick={() => this.removeRecord(record.key)} />
  196. <Button onClick={() => this.toggleModal(true)}>编辑</Button>
  197. </div>
  198. ),
  199. },
  200. ],
  201. };
  202. }
  203. removeRecord(key) {
  204. let dataSource = [...this.state.dataSource];
  205. if (key != null) {
  206. let idx = dataSource.findIndex(data => data.key === key); // console.log(key, dataSource, idx);
  207. if (idx > -1) {
  208. dataSource.splice(idx, 1);
  209. this.setState({
  210. dataSource,
  211. });
  212. }
  213. }
  214. }
  215. resetData() {
  216. let dataSource = [...this.raw];
  217. this.setState({
  218. dataSource,
  219. });
  220. }
  221. toggleModal = modalVisible => {
  222. this.setState({
  223. modalVisible,
  224. });
  225. };
  226. renderModalContent = () => {
  227. const { modalVisible } = this.state;
  228. return (
  229. <Modal
  230. visible={modalVisible}
  231. onCancel={() => this.toggleModal(false)}
  232. onOk={() => this.toggleModal(false)}
  233. >
  234. <p>This is a modal with customized styles.</p>
  235. <p>More content...</p>
  236. <Popover
  237. content={
  238. <div>
  239. <Button>按钮1</Button>
  240. <Button>按钮2</Button>
  241. </div>
  242. }
  243. >
  244. <Button>hover</Button>
  245. </Popover>
  246. </Modal>
  247. );
  248. };
  249. render() {
  250. let { columns, dataSource } = this.state;
  251. return (
  252. <>
  253. <Button onClick={() => this.resetData()}>Reset</Button>
  254. <Table columns={columns} dataSource={dataSource} pagination={false} />
  255. {this.renderModalContent()}
  256. </>
  257. );
  258. }
  259. }
  260. return <TableApp />;
  261. };
  262. const ScrollDemo = function ScrollDemo(props = {}) {
  263. const tops = [
  264. ['topLeft', 'TL'],
  265. ['top', 'Top'],
  266. ['topRight', 'TR'],
  267. ];
  268. const lefts = [
  269. ['leftTop', 'LT'],
  270. ['left', 'Left'],
  271. ['leftBottom', 'LB'],
  272. ];
  273. const rights = [
  274. ['rightTop', 'RT'],
  275. ['right', 'Right'],
  276. ['rightBottom', 'RB'],
  277. ];
  278. const bottoms = [
  279. ['bottomLeft', 'BL'],
  280. ['bottom', 'Bottom'],
  281. ['bottomRight', 'BR'],
  282. ];
  283. const { tagstyle, ...restProps } = props;
  284. return (
  285. <div
  286. style={{
  287. paddingLeft: 40,
  288. }}
  289. >
  290. <div
  291. style={{
  292. marginLeft: 40,
  293. whiteSpace: 'nowrap',
  294. }}
  295. >
  296. {tops.map((pos, index) => (
  297. <Popover
  298. content={
  299. <article>
  300. <p>hi bytedance</p>
  301. <p>hi bytedance</p>
  302. </article>
  303. }
  304. position={Array.isArray(pos) ? pos[0] : pos}
  305. key={index}
  306. trigger={'click'}
  307. {...restProps}
  308. >
  309. <Tag style={tagstyle}>{Array.isArray(pos) ? pos[1] : pos}</Tag>
  310. </Popover>
  311. ))}
  312. </div>
  313. <div
  314. style={{
  315. width: 40,
  316. float: 'left',
  317. }}
  318. >
  319. {lefts.map((pos, index) => (
  320. <Popover
  321. content={
  322. <article>
  323. <p>hi bytedance</p>
  324. <p>hi bytedance</p>
  325. </article>
  326. }
  327. position={Array.isArray(pos) ? pos[0] : pos}
  328. key={index}
  329. trigger={'click'}
  330. {...restProps}
  331. >
  332. <Tag style={tagstyle}>{Array.isArray(pos) ? pos[1] : pos}</Tag>
  333. </Popover>
  334. ))}
  335. </div>
  336. <div
  337. style={{
  338. width: 40,
  339. marginLeft: 180,
  340. }}
  341. >
  342. {rights.map((pos, index) => (
  343. <Popover
  344. content={
  345. <article>
  346. <p>hi bytedance</p>
  347. <p>hi bytedance</p>
  348. </article>
  349. }
  350. position={Array.isArray(pos) ? pos[0] : pos}
  351. key={index}
  352. trigger={'click'}
  353. {...restProps}
  354. >
  355. <Tag style={tagstyle}>{Array.isArray(pos) ? pos[1] : pos}</Tag>
  356. </Popover>
  357. ))}
  358. </div>
  359. <div
  360. style={{
  361. marginLeft: 40,
  362. clear: 'both',
  363. whiteSpace: 'nowrap',
  364. }}
  365. >
  366. {bottoms.map((pos, index) => (
  367. <Popover
  368. content={
  369. <article>
  370. <p>hi bytedance</p>
  371. <p>hi bytedance</p>
  372. </article>
  373. }
  374. position={Array.isArray(pos) ? pos[0] : pos}
  375. key={index}
  376. trigger={'click'}
  377. {...restProps}
  378. >
  379. <Tag style={tagstyle}>{Array.isArray(pos) ? pos[1] : pos}</Tag>
  380. </Popover>
  381. ))}
  382. </div>
  383. </div>
  384. );
  385. };
  386. export const ScrollPopover = () => {
  387. return (
  388. <>
  389. <div id="wrapper">
  390. <div
  391. style={{
  392. height: '200vh',
  393. width: '200vw',
  394. padding: 50,
  395. }}
  396. >
  397. 滚动到下面
  398. </div>
  399. </div>
  400. <div
  401. style={{
  402. padding: 1200,
  403. }}
  404. >
  405. <ScrollDemo
  406. content={
  407. <article
  408. style={{
  409. padding: 12,
  410. }}
  411. >
  412. <p>hi bytedance</p>
  413. <p>hi bytedance</p>
  414. </article>
  415. }
  416. />
  417. </div>
  418. </>
  419. );
  420. };
  421. ScrollPopover.story = {
  422. name: 'scroll popover',
  423. };
  424. export const WithArrow = () => (
  425. <div>
  426. <div
  427. style={{
  428. padding: 120,
  429. }}
  430. >
  431. <ScrollDemo showArrow />
  432. </div>
  433. <div
  434. style={{
  435. padding: 120,
  436. }}
  437. >
  438. <ScrollDemo
  439. showArrow
  440. tagstyle={{
  441. minHeight: 80,
  442. minWidth: 120,
  443. }}
  444. />
  445. </div>
  446. <div
  447. style={{
  448. padding: 120,
  449. }}
  450. >
  451. <ScrollDemo
  452. showArrow
  453. tagstyle={{
  454. minHeight: 80,
  455. minWidth: 120,
  456. }}
  457. style={{
  458. backgroundColor: 'green',
  459. }}
  460. />
  461. </div>
  462. </div>
  463. );
  464. WithArrow.story = {
  465. name: 'with arrow',
  466. };
  467. export const NoContent = () => (
  468. <div>
  469. <div
  470. style={{
  471. padding: 50,
  472. }}
  473. >
  474. <ScrollDemo content={<div></div>} />
  475. </div>
  476. <div
  477. style={{
  478. padding: 50,
  479. }}
  480. >
  481. <ScrollDemo showArrow content={''} />
  482. </div>
  483. <div
  484. style={{
  485. padding: 50,
  486. }}
  487. >
  488. <ScrollDemo
  489. showArrow
  490. content={''}
  491. tagstyle={{
  492. height: 80,
  493. minWidth: 100,
  494. }}
  495. style={{
  496. padding: 20,
  497. }}
  498. />
  499. </div>
  500. </div>
  501. );
  502. NoContent.story = {
  503. name: 'no content',
  504. };
  505. export const _SelectInPopover = () => (
  506. <div
  507. style={{
  508. padding: 50,
  509. }}
  510. >
  511. <SelectInPopover />
  512. </div>
  513. );
  514. _SelectInPopover.story = {
  515. name: 'select in popover',
  516. };
  517. export const CloseBtnInPopover = () => <BtnClose />;
  518. CloseBtnInPopover.story = {
  519. name: 'close btn in popover',
  520. };
  521. export const PopoverFloatRight = () => <PopRight />;
  522. PopoverFloatRight.story = {
  523. name: 'popover float right',
  524. };
  525. export const NestedPopoverDemo = () => <NestedPopover />;
  526. NestedPopoverDemo.story = {
  527. name: 'nested popover'
  528. }
  529. export const ArrowPointAtCenterDemo = () => <ArrowPointAtCenter />;
  530. ArrowPointAtCenterDemo.story = {
  531. name: 'arrow point at center'
  532. }
  533. export const A11yKeyboard = () => {
  534. const [visible, setVisible] = React.useState(false);
  535. const popStyle = { height: 200, width: 200 };
  536. const renderContent = ({ initialFocusRef }) => {
  537. return (
  538. <div style={popStyle} data-cy="pop">
  539. <button data-cy="pop-focusable-first">first focusable</button>
  540. <a href="https://semi.design">link</a>
  541. {/* <input ref={initialFocusRef} placeholder="init focus" /> */}
  542. <input placeholder="" defaultValue="semi" />
  543. <a href="https://semi.design">link2</a>
  544. <button data-cy="pop-focusable-last">last focusable</button>
  545. </div>
  546. );
  547. };
  548. const noFocusableContent = (
  549. <div style={popStyle}>没有可聚焦元素</div>
  550. );
  551. const initFocusContent = ({ initialFocusRef }) => {
  552. return (
  553. <div style={popStyle} data-cy="pop">
  554. <button data-cy="pop-focusable-first">first focusable</button>
  555. <input placeholder="" defaultValue="semi" ref={initialFocusRef} data-cy="initial-focus-input" />
  556. <button data-cy="pop-focusable-last">last focusable</button>
  557. </div>
  558. );
  559. };
  560. return (
  561. <div style={{ paddingLeft: 100, paddingTop: 100 }}>
  562. <Space spacing={100}>
  563. <Popover content={renderContent} trigger="click" motion={false}>
  564. <Button data-cy="click">click</Button>
  565. </Popover>
  566. <Popover content={renderContent} trigger="hover">
  567. <span data-cy="hover">hover</span>
  568. </Popover>
  569. <Popover content={renderContent} trigger="focus">
  570. <Input data-cy="focus" defaultValue="focus" style={{ width: 150 }} />
  571. </Popover>
  572. <Popover
  573. content={renderContent}
  574. trigger="custom"
  575. visible={visible}
  576. onEscKeyDown={() => {
  577. console.log('esc key down');
  578. setVisible(false);
  579. }}
  580. >
  581. <Button onClick={() => setVisible(!visible)} data-cy="custom">
  582. custom trigger + click me toggle show
  583. </Button>
  584. </Popover>
  585. <Popover content={noFocusableContent} trigger="click" data-cy="click-pop-contains-no-focusable">
  586. <Button>pop内没有可聚焦元素</Button>
  587. </Popover>
  588. <Popover content={initFocusContent} trigger="click" motion={false}>
  589. <Button data-cy="initial-focus">custom initialFocus</Button>
  590. </Popover>
  591. <Popover content={renderContent} trigger="click" motion={false} closeOnEsc={false}>
  592. <Button data-cy="closeOnEsc-false">closeOnEsc=false</Button>
  593. </Popover>
  594. <Popover content={renderContent} trigger="click" motion={false} returnFocusOnClose={false}>
  595. <Button data-cy="returnFocusOnClose-false">returnFocusOnClose=false</Button>
  596. </Popover>
  597. </Space>
  598. </div>
  599. );
  600. };
  601. A11yKeyboard.storyName = "a11y keyboard and focus";
  602. /**
  603. * fix 嵌套 popover 的弹出层会导致外部 popover 关闭问题
  604. *
  605. * @see https://github.com/DouyinFE/semi-design/issues/818
  606. * @see https://github.com/facebook/react/issues/4335#issuecomment-421705171
  607. */
  608. export const FixNestedPopover = () => {
  609. return (
  610. <div data-cy="fix-nested-popover" style={{ paddingLeft: 100 }}>
  611. <Popover
  612. content={(
  613. <div data-cy="select-in-popover" style={{ padding: 20 }}>
  614. <Select
  615. defaultValue="abc"
  616. style={{ width: 120 }}
  617. >
  618. <Option value="abc">抖音</Option>
  619. <Option value="hotsoon">火山</Option>
  620. <Option value="pipixia" disabled>
  621. 皮皮虾
  622. </Option>
  623. <Option value="xigua">西瓜视频</Option>
  624. </Select>
  625. </div>
  626. )}
  627. trigger="click"
  628. showArrow
  629. >
  630. <Tag>点击此处</Tag>
  631. </Popover>
  632. </div>
  633. );
  634. }