SelectAllClearButtons.stories.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { useState } from 'react';
  2. import type { Meta, StoryObj } from '@storybook/react-vite';
  3. import { expect } from 'storybook/test';
  4. import { Select } from 'antd';
  5. import SelectAllClearButtons from './SelectAllClearButtons';
  6. const inboundOptions: Array<{ value: number; label: string }> = [
  7. { value: 1, label: 'VLESS Reality — 443' },
  8. { value: 2, label: 'VMess WS — 8443' },
  9. { value: 3, label: 'Trojan TCP — 2053' },
  10. { value: 4, label: 'Shadowsocks — 8388' },
  11. ];
  12. const clientEmailOptions: Array<{ value: string; label: string }> = [
  13. { value: '[email protected]', label: '[email protected]' },
  14. { value: 'reza.mobile', label: 'reza.mobile' },
  15. { value: 'office-tv', label: 'office-tv' },
  16. { value: 'guest-42', label: 'guest-42' },
  17. ];
  18. const meta = {
  19. title: 'Form/SelectAllClearButtons',
  20. component: SelectAllClearButtons,
  21. tags: ['autodocs'],
  22. parameters: {
  23. layout: 'padded',
  24. docs: {
  25. description: {
  26. component:
  27. 'Small "Select all" / "Clear all" button pair rendered above a multi-select. The panel places it over the attached-inbounds picker in the client form and the bulk attach/detach modals.',
  28. },
  29. },
  30. },
  31. argTypes: {
  32. options: {
  33. description:
  34. 'Option list whose values define the "all" set; matches the AntD Select option shape.',
  35. },
  36. value: { description: 'Currently selected values (controlled).' },
  37. onChange: {
  38. description:
  39. 'Called with the union of the current selection and every option value, or with an empty array on clear.',
  40. },
  41. selectAllLabel: {
  42. description:
  43. 'Override for the "Select all" button text; defaults to the translated inbound copy.',
  44. },
  45. clearLabel: {
  46. description:
  47. 'Override for the "Clear all" button text; defaults to the translated inbound copy.',
  48. },
  49. },
  50. } satisfies Meta<typeof SelectAllClearButtons>;
  51. export default meta;
  52. type Story = StoryObj<typeof meta>;
  53. function InboundPickerDemo() {
  54. const [selected, setSelected] = useState<number[]>([1]);
  55. return (
  56. <div style={{ maxWidth: 360 }}>
  57. <SelectAllClearButtons options={inboundOptions} value={selected} onChange={setSelected} />
  58. <Select
  59. mode="multiple"
  60. style={{ width: '100%' }}
  61. value={selected}
  62. onChange={setSelected}
  63. options={inboundOptions}
  64. placeholder="Select inbounds"
  65. aria-label="Select inbounds"
  66. maxTagCount="responsive"
  67. />
  68. </div>
  69. );
  70. }
  71. function ClientEmailsDemo() {
  72. const [selected, setSelected] = useState<string[]>(['[email protected]']);
  73. return (
  74. <div style={{ maxWidth: 360 }}>
  75. <SelectAllClearButtons
  76. options={clientEmailOptions}
  77. value={selected}
  78. onChange={setSelected}
  79. selectAllLabel="Select all clients"
  80. clearLabel="Deselect clients"
  81. />
  82. <Select
  83. mode="multiple"
  84. style={{ width: '100%' }}
  85. value={selected}
  86. onChange={setSelected}
  87. options={clientEmailOptions}
  88. placeholder="Select clients"
  89. aria-label="Select clients"
  90. maxTagCount="responsive"
  91. />
  92. </div>
  93. );
  94. }
  95. const placeholderArgs = {
  96. options: [],
  97. value: [],
  98. onChange: () => undefined,
  99. };
  100. export const PartiallySelected: Story = {
  101. args: {
  102. options: [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }],
  103. value: [1, 3],
  104. onChange: () => undefined,
  105. },
  106. };
  107. export const AllSelected: Story = {
  108. args: {
  109. options: [{ value: 1 }, { value: 2 }, { value: 3 }],
  110. value: [1, 2, 3],
  111. onChange: () => undefined,
  112. },
  113. };
  114. export const WithInboundSelect: Story = {
  115. args: placeholderArgs,
  116. render: () => <InboundPickerDemo />,
  117. };
  118. export const CustomLabels: Story = {
  119. args: placeholderArgs,
  120. render: () => <ClientEmailsDemo />,
  121. play: async ({ canvas, userEvent }) => {
  122. const selectAll = canvas.getByRole('button', { name: 'Select all clients' });
  123. const clearAll = canvas.getByRole('button', { name: 'Deselect clients' });
  124. await expect(selectAll).toBeEnabled();
  125. await userEvent.click(selectAll);
  126. await expect(selectAll).toBeDisabled();
  127. await userEvent.click(clearAll);
  128. await expect(clearAll).toBeDisabled();
  129. await expect(selectAll).toBeEnabled();
  130. },
  131. };