JsonEditor.stories.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { useState } from 'react';
  2. import type { Meta, StoryObj } from '@storybook/react-vite';
  3. import { Typography } from 'antd';
  4. import { ThemeProvider } from '@/hooks/useTheme';
  5. import JsonEditor from './JsonEditor';
  6. const warpOutbound = JSON.stringify(
  7. {
  8. tag: 'warp-out',
  9. protocol: 'wireguard',
  10. settings: {
  11. secretKey: 'yFXfmXX3Zn5tnpNJ7HAcbLvqcMVioqPDGV1GXn2FeV0=',
  12. address: ['172.16.0.2/32', '2606:4700:110:8f81::2/128'],
  13. peers: [
  14. {
  15. publicKey: 'bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=',
  16. allowedIPs: ['0.0.0.0/0', '::/0'],
  17. endpoint: 'engage.cloudflareclient.com:2408',
  18. },
  19. ],
  20. mtu: 1280,
  21. },
  22. },
  23. null,
  24. 2,
  25. );
  26. const realityStreamSettings = JSON.stringify(
  27. {
  28. network: 'tcp',
  29. security: 'reality',
  30. realitySettings: {
  31. show: false,
  32. dest: 'yahoo.com:443',
  33. xver: 0,
  34. serverNames: ['yahoo.com', 'www.yahoo.com'],
  35. privateKey: 'wLc4dpQvRt8mK1nS9jH2fXaU7yEoB3iZ6vNqTgCkW5A',
  36. shortIds: ['6ba85179e30d4fc2'],
  37. },
  38. },
  39. null,
  40. 2,
  41. );
  42. const brokenInboundSettings = [
  43. '{',
  44. ' "clients": [',
  45. ' {',
  46. ' "id": "9f4c3a2b-7d61-4e8a-b5c0-1f2e3d4a5b6c",',
  47. ' "email": "user1@node-de",',
  48. ' "flow": "xtls-rprx-vision",',
  49. ' }',
  50. ' ],',
  51. ' "decryption": "none"',
  52. ].join('\n');
  53. function ControlledDemo() {
  54. const [value, setValue] = useState(warpOutbound);
  55. let parseError = '';
  56. try {
  57. JSON.parse(value);
  58. } catch (err) {
  59. parseError = err instanceof Error ? err.message : String(err);
  60. }
  61. return (
  62. <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
  63. <JsonEditor value={value} onChange={setValue} minHeight="220px" maxHeight="360px" />
  64. <Typography.Text type={parseError ? 'danger' : 'success'}>
  65. {parseError ? `Parse error: ${parseError}` : `Valid JSON (${value.length} chars)`}
  66. </Typography.Text>
  67. </div>
  68. );
  69. }
  70. const meta = {
  71. title: 'Form/JsonEditor',
  72. component: JsonEditor,
  73. tags: ['autodocs'],
  74. parameters: {
  75. layout: 'padded',
  76. docs: {
  77. description: {
  78. component:
  79. 'CodeMirror-based JSON editor with syntax highlighting, live parse linting, and theme-aware styling. The panel uses it for raw xray config snippets — inbound settings, stream settings, and outbound JSON in the modals and settings pages.',
  80. },
  81. },
  82. },
  83. decorators: [
  84. (Story) => (
  85. <ThemeProvider>
  86. <Story />
  87. </ThemeProvider>
  88. ),
  89. ],
  90. argTypes: {
  91. value: { description: 'JSON document text; the editor resyncs when this prop changes.' },
  92. onChange: { description: 'Called with the full document text on every edit.' },
  93. minHeight: { description: 'CSS min-height of the scrollable editor area.' },
  94. maxHeight: { description: 'CSS max-height before the editor scrolls internally.' },
  95. readOnly: { description: 'Disables editing while keeping selection and scrolling.' },
  96. },
  97. } satisfies Meta<typeof JsonEditor>;
  98. export default meta;
  99. type Story = StoryObj<typeof meta>;
  100. export const Default: Story = {
  101. args: { value: warpOutbound },
  102. };
  103. export const ReadOnly: Story = {
  104. args: { value: realityStreamSettings, readOnly: true, minHeight: '200px' },
  105. };
  106. export const LintErrors: Story = {
  107. args: { value: brokenInboundSettings, minHeight: '220px' },
  108. };
  109. export const Controlled: Story = {
  110. args: { value: '' },
  111. parameters: {
  112. a11y: {
  113. config: {
  114. rules: [{ id: 'scrollable-region-focusable', enabled: false }],
  115. },
  116. },
  117. },
  118. render: () => <ControlledDemo />,
  119. };