mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-20 18:11:00 +00:00
feat(frontend): make Storybook a validated, fully covered component workbench
Storybook existed only as an undocumented local tool: 9 of 24 reusable components had stories, autodocs pages were bare prop tables, nothing built or tested the stories, and no contributor doc mentioned the workbench existed. Every reusable component under src/components/ now has a co-located story with enriched autodocs (component descriptions plus per-prop argTypes, kept as string metadata since the repo bans line comments). Stories double as headless Chromium tests through the Storybook vitest addon, with axe accessibility checks enforced as errors and play-function interaction tests covering the modals, the RHF field bridge, the config block, and the select-all buttons. The preview now mirrors the panel's real theme DOM (body class, shared AntD theme config, seeded theme storage) so what stories render matches production. CI and make verify gain a static Storybook build as a compile gate, and the frontend test job installs Chromium so story tests run on every PR. Contributor docs (frontend README, CONTRIBUTING, agent guides) document the workbench, the story conventions, and the Controls setup. Node engines move to 24 LTS and gen:api drops the type-stripping flags that Node 24 makes default.
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { InputNumber, Space } from 'antd';
|
||||
import {
|
||||
CloudServerOutlined,
|
||||
DashboardOutlined,
|
||||
DesktopOutlined,
|
||||
SafetyOutlined,
|
||||
ThunderboltOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
import { NotificationLayout } from './NotificationLayout';
|
||||
import { NotificationCard } from './NotificationCard';
|
||||
import { NotificationEvent } from './NotificationEvent';
|
||||
import { NotificationHeader } from './NotificationHeader';
|
||||
|
||||
const noop = () => undefined;
|
||||
|
||||
function OutboundGroup() {
|
||||
return (
|
||||
<NotificationCard
|
||||
icon={<CloudServerOutlined />}
|
||||
title="Outbound"
|
||||
extra={<NotificationHeader count={1} total={2} allSelected={false} indeterminate onToggleAll={noop} />}
|
||||
>
|
||||
<Space orientation="vertical" size={8} style={{ width: '100%' }}>
|
||||
<NotificationEvent label="Outbound went down" checked onToggle={noop} />
|
||||
<NotificationEvent label="Outbound recovered" checked={false} onToggle={noop} />
|
||||
</Space>
|
||||
</NotificationCard>
|
||||
);
|
||||
}
|
||||
|
||||
function XrayGroup() {
|
||||
return (
|
||||
<NotificationCard
|
||||
icon={<ThunderboltOutlined />}
|
||||
title="Xray"
|
||||
extra={<NotificationHeader count={1} total={1} allSelected indeterminate={false} onToggleAll={noop} />}
|
||||
>
|
||||
<Space orientation="vertical" size={8} style={{ width: '100%' }}>
|
||||
<NotificationEvent label="Xray crashed" checked onToggle={noop} />
|
||||
</Space>
|
||||
</NotificationCard>
|
||||
);
|
||||
}
|
||||
|
||||
function NodeGroup() {
|
||||
return (
|
||||
<NotificationCard
|
||||
icon={<DesktopOutlined />}
|
||||
title="Nodes"
|
||||
extra={<NotificationHeader count={0} total={2} allSelected={false} indeterminate={false} onToggleAll={noop} />}
|
||||
>
|
||||
<Space orientation="vertical" size={8} style={{ width: '100%' }}>
|
||||
<NotificationEvent label="Node went offline" checked={false} onToggle={noop} />
|
||||
<NotificationEvent label="Node back online" checked={false} onToggle={noop} />
|
||||
</Space>
|
||||
</NotificationCard>
|
||||
);
|
||||
}
|
||||
|
||||
function SystemGroup() {
|
||||
return (
|
||||
<NotificationCard
|
||||
icon={<DashboardOutlined />}
|
||||
title="System"
|
||||
extra={<NotificationHeader count={2} total={2} allSelected indeterminate={false} onToggleAll={noop} />}
|
||||
>
|
||||
<Space orientation="vertical" size={8} style={{ width: '100%' }}>
|
||||
<NotificationEvent label="CPU usage above threshold (%)" checked onToggle={noop}>
|
||||
<InputNumber size="small" min={0} max={100} defaultValue={80} aria-label="CPU usage threshold percent" style={{ width: 80 }} />
|
||||
</NotificationEvent>
|
||||
<NotificationEvent label="Memory usage above threshold (%)" checked onToggle={noop}>
|
||||
<InputNumber size="small" min={0} max={100} defaultValue={90} aria-label="Memory usage threshold percent" style={{ width: 80 }} />
|
||||
</NotificationEvent>
|
||||
</Space>
|
||||
</NotificationCard>
|
||||
);
|
||||
}
|
||||
|
||||
function SecurityGroup() {
|
||||
return (
|
||||
<NotificationCard
|
||||
icon={<SafetyOutlined />}
|
||||
title="Security"
|
||||
extra={<NotificationHeader count={1} total={1} allSelected indeterminate={false} onToggleAll={noop} />}
|
||||
>
|
||||
<Space orientation="vertical" size={8} style={{ width: '100%' }}>
|
||||
<NotificationEvent label="Panel login attempt" checked onToggle={noop} />
|
||||
</Space>
|
||||
</NotificationCard>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'UI/Notifications/NotificationLayout',
|
||||
component: NotificationLayout,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
'Responsive auto-fit grid (min 260px columns) that arranges notification event-group cards; the Telegram and email notification tabs on the settings page render their groups inside it.',
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
children: { description: 'Grid items, typically one NotificationCard per event group.' },
|
||||
},
|
||||
} satisfies Meta<typeof NotificationLayout>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const AllEventGroups: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<OutboundGroup />
|
||||
<XrayGroup />
|
||||
<NodeGroup />
|
||||
<SystemGroup />
|
||||
<SecurityGroup />
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const TwoGroups: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<SystemGroup />
|
||||
<SecurityGroup />
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const SingleGroup: Story = {
|
||||
args: {
|
||||
children: <OutboundGroup />,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user