283 lines
7.9 KiB
TypeScript
283 lines
7.9 KiB
TypeScript
/**
|
|
* Storybook stories for `<Label>`.
|
|
*
|
|
* `@storybook/react` is NOT a dep of `@suaveplan/react-ui`; story-meta
|
|
* shapes are inlined.
|
|
*/
|
|
|
|
import {
|
|
createI18n as createI18nNarrow,
|
|
type I18nInstance,
|
|
} from "@suaveplan/i18n/plugin";
|
|
import { TranslationProvider } from "@suaveplan/react-i18n";
|
|
|
|
// Widen to `I18nInstance<string>` so the literal-locale narrowing from
|
|
// the storybook fixture does not collide with `<TranslationProvider>`'s
|
|
// expected `I18nInstance<string>` parameterisation.
|
|
function createI18n(
|
|
options: Parameters<typeof createI18nNarrow>[0]
|
|
): I18nInstance {
|
|
return createI18nNarrow(options) as unknown as I18nInstance;
|
|
}
|
|
|
|
import type { ReactElement, ReactNode } from "react";
|
|
import type { SupportedLocale } from "../../locales/locales.types.js";
|
|
import type { ThemePlugin } from "../../theme/theme.types.js";
|
|
import { ThemeProvider } from "../../theme/theme-provider/theme-provider.js";
|
|
import { darkTheme } from "../../theme/themes/dark.js";
|
|
import { highContrastTheme } from "../../theme/themes/high-contrast.js";
|
|
import { lightTheme } from "../../theme/themes/light.js";
|
|
import { Input } from "../input/input.js";
|
|
import { Label } from "./label.js";
|
|
import type { LabelProps } from "./label.types.js";
|
|
|
|
interface Meta<T> {
|
|
readonly component: T;
|
|
readonly title: string;
|
|
readonly tags?: readonly string[];
|
|
readonly parameters?: Record<string, unknown>;
|
|
readonly argTypes?: Record<string, unknown>;
|
|
}
|
|
|
|
interface StoryObj<TArgs> {
|
|
readonly args?: Partial<TArgs>;
|
|
readonly render?: (args: Partial<TArgs>) => ReactNode;
|
|
readonly name?: string;
|
|
readonly parameters?: Record<string, unknown>;
|
|
}
|
|
|
|
const meta: Meta<typeof Label> = {
|
|
component: Label,
|
|
title: "react-ui/Foundation/Label",
|
|
tags: ["autodocs"],
|
|
argTypes: {
|
|
disabled: { control: "boolean" },
|
|
},
|
|
};
|
|
export default meta;
|
|
|
|
type Story = StoryObj<LabelProps>;
|
|
|
|
interface ProviderShellProps {
|
|
readonly locale: SupportedLocale;
|
|
readonly themes: ReadonlyArray<ThemePlugin>;
|
|
readonly defaultTheme: string;
|
|
readonly children: ReactNode;
|
|
}
|
|
|
|
function ProviderShellSync(props: ProviderShellProps): ReactElement {
|
|
const i18n = createI18n({
|
|
defaultLocale: props.locale,
|
|
fallbackLocale: "en-GB",
|
|
});
|
|
return (
|
|
<TranslationProvider i18n={i18n}>
|
|
<ThemeProvider
|
|
themes={props.themes}
|
|
defaultTheme={props.defaultTheme}
|
|
>
|
|
{props.children}
|
|
</ThemeProvider>
|
|
</TranslationProvider>
|
|
);
|
|
}
|
|
|
|
export const Default: Story = {
|
|
render: (args) => (
|
|
<ProviderShellSync
|
|
locale="en-GB"
|
|
themes={[lightTheme]}
|
|
defaultTheme="light"
|
|
>
|
|
<Label {...args} htmlFor="story-default">
|
|
Email
|
|
</Label>
|
|
</ProviderShellSync>
|
|
),
|
|
};
|
|
|
|
export const Playground: Story = {
|
|
render: (args) => (
|
|
<ProviderShellSync
|
|
locale="en-GB"
|
|
themes={[lightTheme]}
|
|
defaultTheme="light"
|
|
>
|
|
<Label {...args} htmlFor="story-playground">
|
|
{(args.children as ReactNode) ?? "Playground"}
|
|
</Label>
|
|
</ProviderShellSync>
|
|
),
|
|
args: {
|
|
disabled: false,
|
|
},
|
|
};
|
|
|
|
export const Variants: Story = {
|
|
render: () => (
|
|
<ProviderShellSync
|
|
locale="en-GB"
|
|
themes={[lightTheme]}
|
|
defaultTheme="light"
|
|
>
|
|
<div className="flex flex-col gap-2">
|
|
<Label htmlFor="v1">Default</Label>
|
|
<Label htmlFor="v2" disabled>
|
|
Disabled
|
|
</Label>
|
|
</div>
|
|
</ProviderShellSync>
|
|
),
|
|
};
|
|
|
|
export const States: Story = {
|
|
render: () => (
|
|
<ProviderShellSync
|
|
locale="en-GB"
|
|
themes={[lightTheme]}
|
|
defaultTheme="light"
|
|
>
|
|
<div className="flex flex-col gap-2">
|
|
<Label htmlFor="s1">Active</Label>
|
|
<Label htmlFor="s2" disabled>
|
|
Disabled
|
|
</Label>
|
|
</div>
|
|
</ProviderShellSync>
|
|
),
|
|
};
|
|
|
|
const LOCALES: ReadonlyArray<SupportedLocale> = [
|
|
"en-GB",
|
|
"zh-CN",
|
|
"ar-AE",
|
|
"cy-GB",
|
|
"ru-RU",
|
|
];
|
|
|
|
const LOCALE_TEXT: Record<SupportedLocale, string> = {
|
|
"en-GB": "Email",
|
|
"zh-CN": "电子邮件",
|
|
"ar-AE": "البريد الإلكتروني",
|
|
"cy-GB": "E-bost",
|
|
"ru-RU": "Электронная почта",
|
|
};
|
|
|
|
export const WithI18n: Story = {
|
|
render: () => (
|
|
<div className="flex flex-col gap-2">
|
|
{LOCALES.map((locale) => (
|
|
<div
|
|
key={locale}
|
|
dir={locale === "ar-AE" ? "rtl" : "ltr"}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<span style={{ width: 80 }}>{locale}</span>
|
|
<ProviderShellSync
|
|
locale={locale}
|
|
themes={[lightTheme]}
|
|
defaultTheme="light"
|
|
>
|
|
<Label htmlFor={`i18n-${locale}`}>
|
|
{LOCALE_TEXT[locale]}
|
|
</Label>
|
|
</ProviderShellSync>
|
|
</div>
|
|
))}
|
|
</div>
|
|
),
|
|
};
|
|
|
|
export const WithRTL: Story = {
|
|
render: () => (
|
|
<div dir="rtl">
|
|
<ProviderShellSync
|
|
locale="ar-AE"
|
|
themes={[lightTheme]}
|
|
defaultTheme="light"
|
|
>
|
|
<Label htmlFor="rtl-x">{LOCALE_TEXT["ar-AE"]}</Label>
|
|
</ProviderShellSync>
|
|
</div>
|
|
),
|
|
};
|
|
|
|
const THEMES: ReadonlyArray<{
|
|
readonly label: string;
|
|
readonly theme: ThemePlugin;
|
|
}> = [
|
|
{ label: "light", theme: lightTheme },
|
|
{ label: "dark", theme: darkTheme },
|
|
{ label: "highContrast", theme: highContrastTheme },
|
|
];
|
|
|
|
export const WithTheme: Story = {
|
|
render: () => (
|
|
<div className="flex flex-col gap-2">
|
|
{THEMES.map(({ label, theme }) => (
|
|
<div key={label} className="flex items-center gap-2 p-3 border">
|
|
<span style={{ width: 120 }}>{label}</span>
|
|
<ProviderShellSync
|
|
locale="en-GB"
|
|
themes={[theme]}
|
|
defaultTheme={theme.name}
|
|
>
|
|
<Label htmlFor={`t-${label}`}>Email</Label>
|
|
</ProviderShellSync>
|
|
</div>
|
|
))}
|
|
</div>
|
|
),
|
|
};
|
|
|
|
export const A11y: Story = {
|
|
parameters: {
|
|
a11y: {
|
|
element: "#storybook-root",
|
|
config: {
|
|
rules: [
|
|
{ id: "label", enabled: true },
|
|
{ id: "color-contrast", enabled: true },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
render: () => (
|
|
<ProviderShellSync
|
|
locale="en-GB"
|
|
themes={[lightTheme]}
|
|
defaultTheme="light"
|
|
>
|
|
<div className="flex flex-col gap-1">
|
|
<Label htmlFor="a11y-input">Email</Label>
|
|
<Input id="a11y-input" type="email" />
|
|
</div>
|
|
</ProviderShellSync>
|
|
),
|
|
};
|
|
|
|
export const Composition: Story = {
|
|
name: "Composition (with Input)",
|
|
render: () => (
|
|
<ProviderShellSync
|
|
locale="en-GB"
|
|
themes={[lightTheme]}
|
|
defaultTheme="light"
|
|
>
|
|
<div className="flex flex-col gap-1">
|
|
<Label htmlFor="comp-input">
|
|
Email{" "}
|
|
<span aria-hidden="true" className="text-destructive">
|
|
*
|
|
</span>
|
|
</Label>
|
|
<Input
|
|
id="comp-input"
|
|
type="email"
|
|
placeholder={"react-ui.empty.noResults" as never}
|
|
/>
|
|
</div>
|
|
</ProviderShellSync>
|
|
),
|
|
};
|