Files
3x-ui/frontend/src/components/ui/notifications/NotificationHeader.stories.tsx
T
MHSanaei 7078abc14a 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.
2026-07-14 03:37:21 +02:00

105 lines
2.9 KiB
TypeScript

import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Checkbox } from 'antd';
import { NotificationHeader } from './NotificationHeader';
const meta = {
title: 'UI/Notifications/NotificationHeader',
component: NotificationHeader,
tags: ['autodocs'],
parameters: {
layout: 'padded',
docs: {
description: {
component:
'Selection summary for a notification group header — a `count/total` tag plus a tri-state master checkbox that selects or clears every event in the group. Rendered in the `extra` slot of the Telegram/email notification cards on the settings page.',
},
},
},
argTypes: {
count: { description: 'Number of events currently selected in the group.' },
total: { description: 'Total number of events the group offers.' },
allSelected: { description: 'Checks the master checkbox when every event is selected.' },
indeterminate: { description: 'Shows the dash state when only some events are selected.' },
onToggleAll: { description: 'Called when the master checkbox is clicked to select or clear all events.' },
},
} satisfies Meta<typeof NotificationHeader>;
export default meta;
type Story = StoryObj<typeof meta>;
export const NoneSelected: Story = {
args: {
count: 0,
total: 6,
allSelected: false,
indeterminate: false,
onToggleAll: () => undefined,
},
};
export const PartialSelection: Story = {
args: {
count: 3,
total: 6,
allSelected: false,
indeterminate: true,
onToggleAll: () => undefined,
},
};
export const AllSelected: Story = {
args: {
count: 6,
total: 6,
allSelected: true,
indeterminate: false,
onToggleAll: () => undefined,
},
};
const events = ['Panel login', 'Xray crashed', 'CPU high', 'Client depleted'];
function GroupDemo() {
const [selected, setSelected] = useState<string[]>(['Panel login', 'CPU high']);
const count = selected.length;
const total = events.length;
function toggleAll() {
setSelected(count === total ? [] : [...events]);
}
function toggle(name: string) {
setSelected((prev) => (prev.includes(name) ? prev.filter((e) => e !== name) : [...prev, name]));
}
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, maxWidth: 260 }}>
<NotificationHeader
count={count}
total={total}
allSelected={count === total}
indeterminate={count > 0 && count < total}
onToggleAll={toggleAll}
/>
{events.map((name) => (
<Checkbox key={name} checked={selected.includes(name)} onChange={() => toggle(name)}>
{name}
</Checkbox>
))}
</div>
);
}
const placeholderArgs = {
count: 0,
total: 0,
allSelected: false,
indeterminate: false,
onToggleAll: () => undefined,
};
export const Interactive: Story = {
args: placeholderArgs,
render: () => <GroupDemo />,
};