Initial Commit
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* 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>
|
||||
),
|
||||
};
|
||||
Reference in New Issue
Block a user