TextModal.stories.tsx 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { useState } from 'react';
  2. import type { Meta, StoryObj } from '@storybook/react-vite';
  3. import { expect, waitFor, within } from 'storybook/test';
  4. import { Button } from 'antd';
  5. import TextModal from './TextModal';
  6. const meta = {
  7. title: 'Feedback/TextModal',
  8. component: TextModal,
  9. tags: ['autodocs'],
  10. parameters: {
  11. docs: {
  12. description: {
  13. component:
  14. 'Read-only modal for viewing generated text or JSON — a client config, subscription, or exported settings — with copy and optional download actions, plus optional tabs for multiple documents.',
  15. },
  16. },
  17. },
  18. argTypes: {
  19. open: { description: 'Whether the modal is visible.' },
  20. title: { description: 'Modal title text.' },
  21. content: { description: 'Text shown when no `tabs` are provided.' },
  22. fileName: { description: 'When set, adds a download button that saves the active content under this name.' },
  23. json: { description: 'Render the content in a read-only JSON editor with syntax highlighting.' },
  24. tabs: { description: 'Optional list of `{ key, label, content }` documents shown as tabs.' },
  25. onClose: { description: 'Called when the modal is dismissed.' },
  26. },
  27. } satisfies Meta<typeof TextModal>;
  28. export default meta;
  29. type Story = StoryObj<typeof meta>;
  30. const jsonSample = JSON.stringify({ outbounds: [{ protocol: 'vless', tag: 'proxy' }] }, null, 2);
  31. function Demo({ json, fileName }: { json?: boolean; fileName?: string }) {
  32. const [open, setOpen] = useState(false);
  33. return (
  34. <>
  35. <Button onClick={() => setOpen(true)}>Show config</Button>
  36. <TextModal
  37. open={open}
  38. title="Client configuration"
  39. content={json ? jsonSample : 'vless://[email protected]:443#node'}
  40. fileName={fileName}
  41. json={json}
  42. onClose={() => setOpen(false)}
  43. />
  44. </>
  45. );
  46. }
  47. const placeholderArgs = {
  48. open: false,
  49. title: '',
  50. content: '',
  51. onClose: () => undefined,
  52. };
  53. export const PlainText: Story = {
  54. args: placeholderArgs,
  55. render: () => <Demo fileName="client.txt" />,
  56. play: async ({ canvas, canvasElement, userEvent }) => {
  57. const body = within(canvasElement.ownerDocument.body);
  58. await userEvent.click(canvas.getByRole('button', { name: 'Show config' }));
  59. const content = await body.findByDisplayValue(/vless:\/\/uuid@example\.com/);
  60. await waitFor(() => expect(content).toBeVisible());
  61. await expect(body.getByRole('button', { name: /client\.txt/ })).toBeEnabled();
  62. },
  63. };
  64. export const Json: Story = {
  65. args: placeholderArgs,
  66. render: () => <Demo json fileName="config.json" />,
  67. };