@fabrika/ui 0.8.2 → 0.9.0-cloudflare.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabrika/ui",
3
- "version": "0.8.2",
3
+ "version": "0.9.0-cloudflare.2",
4
4
  "type": "module",
5
5
  "description": "React component kit for the paved road: accessible admin components styled through CSS custom properties.",
6
6
  "license": "MIT",
@@ -14,17 +14,18 @@
14
14
  },
15
15
  "files": [
16
16
  "src",
17
- "!src/**/__tests__/**"
17
+ "!src/**/__tests__/**",
18
+ "types"
18
19
  ],
19
20
  "exports": {
20
21
  ".": {
21
22
  "bun": "./src/index.ts",
22
- "types": "./src/index.ts",
23
+ "types": "./types/src/index.d.ts",
23
24
  "default": "./src/index.ts"
24
25
  },
25
26
  "./challenge": {
26
27
  "bun": "./src/Challenge.tsx",
27
- "types": "./src/Challenge.tsx",
28
+ "types": "./types/src/Challenge.d.ts",
28
29
  "default": "./src/Challenge.tsx"
29
30
  },
30
31
  "./styles.css": "./src/styles.css",
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface NavItem {
3
+ readonly label: string;
4
+ readonly href: string;
5
+ readonly active?: boolean;
6
+ readonly count?: number;
7
+ }
8
+ export interface AppShellProps {
9
+ readonly brand: ReactNode;
10
+ readonly nav: readonly NavItem[];
11
+ /** The signed-in caller, shown at the end of the top bar. */
12
+ readonly user?: ReactNode;
13
+ readonly actions?: ReactNode;
14
+ /** The top bar: shown when there is a `user` or `actions`, unless `false`. */
15
+ readonly topBar?: boolean;
16
+ /** The current navigation item: a soft accent (the default), or `filled` with the accent and an inverted count. */
17
+ readonly navStyle?: 'soft' | 'filled';
18
+ readonly children: ReactNode;
19
+ readonly navLabel?: string;
20
+ readonly className?: string;
21
+ }
22
+ export declare function AppShell({ brand, nav, user, actions, topBar, navStyle, children, navLabel, className }: AppShellProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import type { ComponentProps } from 'react';
2
+ export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
3
+ export type ButtonSize = 'sm' | 'md';
4
+ export interface ButtonProps extends Readonly<ComponentProps<'button'>> {
5
+ readonly variant?: ButtonVariant;
6
+ readonly size?: ButtonSize;
7
+ /** Disables the button and shows a spinner beside its label until the work it started settles. */
8
+ readonly loading?: boolean;
9
+ }
10
+ export declare function Button({ variant, size, loading, type, disabled, className, children, ...rest }: ButtonProps): import("react").JSX.Element;
11
+ export interface ButtonLinkProps extends Readonly<ComponentProps<'a'>> {
12
+ readonly href: string;
13
+ readonly variant?: ButtonVariant;
14
+ readonly size?: ButtonSize;
15
+ }
16
+ export declare function ButtonLink({ variant, size, className, children, ...rest }: ButtonLinkProps): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface CardProps {
3
+ readonly title?: ReactNode;
4
+ readonly actions?: ReactNode;
5
+ readonly children: ReactNode;
6
+ readonly footer?: ReactNode;
7
+ readonly className?: string;
8
+ }
9
+ export declare function Card({ title, actions, children, footer, className }: CardProps): import("react").JSX.Element;
10
+ export interface SectionProps {
11
+ readonly title: ReactNode;
12
+ readonly description?: ReactNode;
13
+ readonly actions?: ReactNode;
14
+ readonly children: ReactNode;
15
+ readonly className?: string;
16
+ }
17
+ export declare function Section({ title, description, actions, children, className }: SectionProps): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import 'altcha';
2
+ /**
3
+ * The texts the widget renders as text, each with the widget's English default. Its footer is not among them: the
4
+ * widget renders that one as HTML, and the kit hides it.
5
+ */
6
+ export interface ChallengeStrings {
7
+ readonly label: string;
8
+ readonly verifying: string;
9
+ readonly verified: string;
10
+ readonly verificationRequired: string;
11
+ readonly expired: string;
12
+ readonly error: string;
13
+ readonly reload: string;
14
+ readonly waitAlert: string;
15
+ /** @deprecated The label of the widget's logo link, which the kit hides; it reaches nothing, so it is not sent. */
16
+ readonly ariaLinkLabel?: string;
17
+ }
18
+ export interface ChallengeProps {
19
+ /** Where the widget fetches its challenge: `challengeUrl(api.<procedure>)` of `@fabrika/app/client`. */
20
+ readonly url: string;
21
+ /** The solved payload, or `null` once there is none again: the challenge expired or failed. */
22
+ readonly onToken?: (token: string | null) => void;
23
+ /**
24
+ * The hidden input the widget adds to its form, which a native form posts: `challengeToken`, the field a procedure
25
+ * with a challenge reads.
26
+ */
27
+ readonly name?: string;
28
+ /** When the widget solves by itself; `off` (the default) waits for the visitor to tick it. */
29
+ readonly auto?: 'off' | 'onfocus' | 'onload' | 'onsubmit';
30
+ readonly strings?: Partial<ChallengeStrings>;
31
+ readonly className?: string;
32
+ }
33
+ /**
34
+ * The proof-of-work widget for one procedure's challenge. A challenge passes once: after a refused submission, give the
35
+ * component a new `key` so the visitor solves a fresh one.
36
+ */
37
+ export declare function Challenge({ url, onToken, name, auto, strings, className }: ChallengeProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface ColumnsProps {
3
+ readonly children: ReactNode;
4
+ readonly className?: string;
5
+ }
6
+ /** A main column that grows and a `SidePanel` beside it; they stack on narrow screens. */
7
+ export declare function Columns({ children, className }: ColumnsProps): import("react").JSX.Element;
8
+ export interface SidePanelProps {
9
+ readonly title?: ReactNode;
10
+ readonly children: ReactNode;
11
+ readonly className?: string;
12
+ }
13
+ export declare function SidePanel({ title, children, className }: SidePanelProps): import("react").JSX.Element;
@@ -0,0 +1,53 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface Column<Row> {
3
+ readonly key: string;
4
+ readonly header: ReactNode;
5
+ readonly cell: (row: Row) => ReactNode;
6
+ readonly align?: 'start' | 'end';
7
+ /** Takes the width the other columns leave; the remaining columns shrink to their content. */
8
+ readonly grow?: boolean;
9
+ /** A width of the column scale (`--fui-column-width-<size>`), on its head and its cells. */
10
+ readonly width?: ColumnWidth;
11
+ /** Its cells wrap their text instead of keeping it on one line. */
12
+ readonly wrap?: boolean;
13
+ }
14
+ export type ColumnWidth = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
15
+ export interface TableSelection {
16
+ readonly selected: ReadonlySet<string>;
17
+ readonly onChange: (next: ReadonlySet<string>) => void;
18
+ }
19
+ export interface LoadMore {
20
+ readonly hasMore: boolean;
21
+ readonly loading: boolean;
22
+ readonly onLoadMore: () => void;
23
+ readonly label?: string;
24
+ }
25
+ export type TableStatus = 'loading' | 'error' | 'ready';
26
+ export interface DataTableProps<Row> {
27
+ readonly caption?: ReactNode;
28
+ readonly columns: readonly Column<Row>[];
29
+ readonly rows: readonly Row[];
30
+ readonly rowKey: (row: Row) => string;
31
+ readonly status?: TableStatus;
32
+ readonly error?: ReactNode;
33
+ readonly onRetry?: () => void;
34
+ readonly empty?: ReactNode;
35
+ readonly selection?: TableSelection;
36
+ readonly selectionActions?: (selectedKeys: readonly string[]) => ReactNode;
37
+ readonly loadMore?: LoadMore;
38
+ readonly selectRowLabel?: (row: Row) => string;
39
+ readonly selectAllLabel?: string;
40
+ readonly selectedLabel?: (count: number) => string;
41
+ readonly selectionToolbarLabel?: string;
42
+ readonly clearSelectionLabel?: string;
43
+ readonly loadingLabel?: string;
44
+ readonly retryLabel?: string;
45
+ readonly className?: string;
46
+ }
47
+ export declare function DataTable<Row>({ caption, columns, rows, rowKey, status, error, onRetry, empty, selection, selectionActions, loadMore, selectRowLabel, selectAllLabel, selectedLabel, selectionToolbarLabel, clearSelectionLabel, loadingLabel, retryLabel, className, }: DataTableProps<Row>): import("react").JSX.Element;
48
+ export interface Selection extends TableSelection {
49
+ readonly toggle: (key: string) => void;
50
+ readonly clear: () => void;
51
+ }
52
+ /** Selection state shaped to pass straight to `DataTable`'s `selection`. */
53
+ export declare function useSelection(initial?: Iterable<string>): Selection;
@@ -0,0 +1,27 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface DialogProps {
3
+ readonly open: boolean;
4
+ readonly title: ReactNode;
5
+ readonly description?: ReactNode;
6
+ readonly onClose: () => void;
7
+ readonly children?: ReactNode;
8
+ readonly actions?: ReactNode;
9
+ readonly closeLabel?: string;
10
+ readonly className?: string;
11
+ }
12
+ /** A modal dialog rendered in place over a fixed overlay; it traps focus and gives it back on close. */
13
+ export declare function Dialog({ open, title, description, onClose, children, actions, closeLabel, className }: DialogProps): import("react").JSX.Element | null;
14
+ export interface ConfirmDialogProps {
15
+ readonly open: boolean;
16
+ readonly title: ReactNode;
17
+ readonly message: ReactNode;
18
+ readonly confirmLabel: string;
19
+ readonly cancelLabel?: string;
20
+ readonly tone?: 'primary' | 'danger';
21
+ /** The confirmed work is running: the confirm button spins and the dialog cannot be dismissed. */
22
+ readonly pending?: boolean;
23
+ readonly onConfirm: () => void;
24
+ readonly onCancel: () => void;
25
+ readonly closeLabel?: string;
26
+ }
27
+ export declare function ConfirmDialog({ open, title, message, confirmLabel, cancelLabel, tone, pending, onConfirm, onCancel, closeLabel, }: ConfirmDialogProps): import("react").JSX.Element;
@@ -0,0 +1,30 @@
1
+ import { type ReactNode } from 'react';
2
+ /** What a `Field` hands the one control inside it. */
3
+ export interface FieldControl {
4
+ readonly id: string;
5
+ readonly name: string | undefined;
6
+ readonly describedBy: string | undefined;
7
+ readonly invalid: boolean;
8
+ readonly required: boolean;
9
+ }
10
+ export declare function useFieldControl(): FieldControl | undefined;
11
+ export interface FieldProps {
12
+ readonly label: ReactNode;
13
+ readonly name?: string;
14
+ readonly hint?: ReactNode;
15
+ readonly error?: string;
16
+ readonly required?: boolean;
17
+ readonly children: ReactNode;
18
+ readonly className?: string;
19
+ }
20
+ export declare function Field({ label, name, hint, error, required, children, className }: FieldProps): import("react").JSX.Element;
21
+ interface ControlAttributes {
22
+ readonly id?: string;
23
+ readonly name?: string;
24
+ readonly required?: boolean;
25
+ readonly 'aria-describedby'?: string;
26
+ readonly 'aria-invalid'?: boolean | 'true' | 'false' | 'grammar' | 'spelling';
27
+ }
28
+ /** The attributes a control takes from its `Field`; what the author passed explicitly wins. */
29
+ export declare function useFieldAttributes(own: ControlAttributes): ControlAttributes;
30
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface FieldListItem {
3
+ readonly label: ReactNode;
4
+ readonly value: ReactNode;
5
+ }
6
+ export interface FieldListProps {
7
+ readonly items: readonly FieldListItem[];
8
+ readonly columns?: 1 | 2 | 3;
9
+ readonly className?: string;
10
+ }
11
+ export declare function FieldList({ items, columns, className }: FieldListProps): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface FilterBarProps {
3
+ readonly children: ReactNode;
4
+ readonly onReset?: () => void;
5
+ readonly resetLabel?: string;
6
+ readonly label?: string;
7
+ readonly className?: string;
8
+ }
9
+ export declare function FilterBar({ children, onReset, resetLabel, label, className }: FilterBarProps): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * The error a form shows. Structural on purpose: the framework's page hooks answer an error union whose members all
4
+ * carry a `message`, and the validation member carries `fields` by input path — it fits without this kit importing it.
5
+ */
6
+ export interface FormErrorLike {
7
+ readonly message: string;
8
+ readonly fields?: Readonly<Record<string, string>>;
9
+ }
10
+ /** The message the enclosing `Form` holds for the field `name`, if any. */
11
+ export declare function useFormFieldError(name: string | undefined): string | undefined;
12
+ export interface FormProps {
13
+ readonly onSubmit: (data: FormData, form: HTMLFormElement) => void;
14
+ readonly error?: FormErrorLike | null;
15
+ readonly children: ReactNode;
16
+ readonly className?: string;
17
+ readonly fieldsSummary?: string;
18
+ readonly 'aria-label'?: string;
19
+ }
20
+ export declare function Form({ onSubmit, error, children, className, fieldsSummary, ...rest }: FormProps): import("react").JSX.Element;
21
+ export interface FormActionsProps {
22
+ readonly children: ReactNode;
23
+ readonly className?: string;
24
+ }
25
+ export declare function FormActions({ children, className }: FormActionsProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface PageHeaderProps {
3
+ readonly title: ReactNode;
4
+ readonly lead?: ReactNode;
5
+ readonly back?: {
6
+ readonly href: string;
7
+ readonly label: string;
8
+ };
9
+ readonly actions?: ReactNode;
10
+ readonly meta?: ReactNode;
11
+ readonly className?: string;
12
+ }
13
+ export declare function PageHeader({ title, lead, back, actions, meta, className }: PageHeaderProps): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Tone } from './tone';
3
+ export interface StatusPillProps {
4
+ readonly tone?: Tone;
5
+ /** The tone's dot before the text; `false` leaves it out. */
6
+ readonly dot?: boolean;
7
+ readonly children: ReactNode;
8
+ readonly className?: string;
9
+ }
10
+ export declare function StatusPill({ tone, dot, children, className }: StatusPillProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ export interface TabItem<K extends string> {
2
+ readonly key: K;
3
+ readonly label: string;
4
+ readonly count?: number;
5
+ }
6
+ export interface TabsProps<K extends string> {
7
+ readonly label: string;
8
+ readonly items: readonly TabItem<K>[];
9
+ readonly active: K;
10
+ readonly onChange: (key: K) => void;
11
+ /** `underline` (the default) marks the active tab by a line under it; `boxed` puts each tab in a box, its count after a dot. */
12
+ readonly variant?: 'underline' | 'boxed';
13
+ readonly className?: string;
14
+ }
15
+ /** A WAI-ARIA tab list with automatic activation: moving focus with the keyboard also selects. */
16
+ export declare function Tabs<K extends string>({ label, items, active, onChange, variant, className }: TabsProps<K>): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Tone } from './tone';
3
+ export interface TimelineItem {
4
+ readonly id: string;
5
+ readonly at: ReactNode;
6
+ readonly title: ReactNode;
7
+ readonly body?: ReactNode;
8
+ readonly tone?: Tone;
9
+ }
10
+ export interface TimelineProps {
11
+ readonly items: readonly TimelineItem[];
12
+ readonly empty?: ReactNode;
13
+ readonly className?: string;
14
+ }
15
+ export declare function Timeline({ items, empty, className }: TimelineProps): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from 'react';
2
+ export type ToastTone = 'neutral' | 'success' | 'danger';
3
+ export interface ToastOptions {
4
+ readonly tone?: ToastTone;
5
+ readonly message: string;
6
+ /** How long the toast stays; `0` keeps it until it is dismissed. */
7
+ readonly timeoutMs?: number;
8
+ }
9
+ export interface ToastApi {
10
+ readonly show: (options: ToastOptions) => void;
11
+ }
12
+ export interface ToastProviderProps {
13
+ readonly children: ReactNode;
14
+ readonly dismissLabel?: string;
15
+ readonly regionLabel?: string;
16
+ }
17
+ export declare function ToastProvider({ children, dismissLabel, regionLabel }: ToastProviderProps): import("react").JSX.Element;
18
+ export declare function useToast(): ToastApi;
@@ -0,0 +1,2 @@
1
+ /** Joins the truthy class names: `classNames('fui-button', primary && 'fui-button--primary', className)`. */
2
+ export declare function classNames(...names: readonly (string | false | null | undefined)[]): string;
@@ -0,0 +1,20 @@
1
+ export { AppShell, type AppShellProps, type NavItem } from './AppShell';
2
+ export { Button, ButtonLink, type ButtonLinkProps, type ButtonProps, type ButtonSize, type ButtonVariant } from './Button';
3
+ export { Card, type CardProps, Section, type SectionProps } from './Card';
4
+ export { Columns, type ColumnsProps, SidePanel, type SidePanelProps } from './Columns';
5
+ export { type Column, type ColumnWidth, DataTable, type DataTableProps, type LoadMore, type Selection, type TableSelection, type TableStatus, useSelection, } from './DataTable';
6
+ export { ConfirmDialog, type ConfirmDialogProps, Dialog, type DialogProps } from './Dialog';
7
+ export { Field, type FieldControl, type FieldProps, useFieldControl } from './Field';
8
+ export { FieldList, type FieldListItem, type FieldListProps } from './FieldList';
9
+ export { FilterBar, type FilterBarProps } from './FilterBar';
10
+ export { Form, FormActions, type FormActionsProps, type FormErrorLike, type FormProps } from './Form';
11
+ export { Checkbox, type CheckboxProps, DateInput, type DateInputProps, DateRange, type DateRangeProps, type DateRangeValue, Input, type InputProps, RadioGroup, type RadioGroupProps, Select, type SelectOption, type SelectProps, Textarea, type TextareaProps, } from './inputs';
12
+ export { type Gap, Inline, type InlineProps, Stack, type StackProps } from './layout';
13
+ export { PageHeader, type PageHeaderProps } from './PageHeader';
14
+ export { type CreatedRouter, createRouter, type LinkProps, type NavigateOptions, type ParamsArgs, type ParamsOf, type RouteMatch, type RouterOptions, type RouterProps, type RouteTable, } from './router';
15
+ export { CountBadge, type CountBadgeProps, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, LoadingState, type LoadingStateProps, Spinner, type SpinnerProps, } from './states';
16
+ export { StatusPill, type StatusPillProps } from './StatusPill';
17
+ export { type TabItem, Tabs, type TabsProps } from './Tabs';
18
+ export { Timeline, type TimelineItem, type TimelineProps } from './Timeline';
19
+ export { type ToastApi, type ToastOptions, ToastProvider, type ToastProviderProps, type ToastTone, useToast } from './Toast';
20
+ export type { Tone } from './tone';
@@ -0,0 +1,59 @@
1
+ import { type ComponentProps, type ReactNode } from 'react';
2
+ export interface InputProps extends Readonly<ComponentProps<'input'>> {
3
+ }
4
+ export declare function Input({ className, ...rest }: InputProps): import("react").JSX.Element;
5
+ export interface DateInputProps extends Readonly<Omit<ComponentProps<'input'>, 'type'>> {
6
+ }
7
+ export declare function DateInput({ className, ...rest }: DateInputProps): import("react").JSX.Element;
8
+ export interface TextareaProps extends Readonly<ComponentProps<'textarea'>> {
9
+ }
10
+ export declare function Textarea({ className, ...rest }: TextareaProps): import("react").JSX.Element;
11
+ export interface SelectOption {
12
+ readonly value: string;
13
+ readonly label: string;
14
+ }
15
+ export interface SelectProps extends Readonly<Omit<ComponentProps<'select'>, 'children'>> {
16
+ readonly options: readonly SelectOption[];
17
+ /** An empty-valued first option, such as "All statuses" in a filter or "Choose…" in a form. */
18
+ readonly placeholder?: string;
19
+ }
20
+ export declare function Select({ options, placeholder, className, ...rest }: SelectProps): import("react").JSX.Element;
21
+ export interface DateRangeValue {
22
+ readonly from: string;
23
+ readonly to: string;
24
+ }
25
+ export interface DateRangeProps extends DateRangeValue {
26
+ readonly onChange: (next: DateRangeValue) => void;
27
+ readonly fromLabel?: string;
28
+ readonly toLabel?: string;
29
+ readonly disabled?: boolean;
30
+ readonly className?: string;
31
+ }
32
+ export declare function DateRange({ from, to, onChange, fromLabel, toLabel, disabled, className }: DateRangeProps): import("react").JSX.Element;
33
+ export interface CheckboxProps {
34
+ readonly label: ReactNode;
35
+ readonly checked: boolean;
36
+ readonly onChange: (checked: boolean) => void;
37
+ readonly indeterminate?: boolean;
38
+ readonly disabled?: boolean;
39
+ readonly name?: string;
40
+ readonly value?: string;
41
+ /** Keeps the label for assistive technology only, as in a table's selection column. */
42
+ readonly hideLabel?: boolean;
43
+ readonly className?: string;
44
+ }
45
+ export declare function Checkbox({ label, checked, onChange, indeterminate, disabled, name, value, hideLabel, className }: CheckboxProps): import("react").JSX.Element;
46
+ export interface RadioGroupProps<V extends string> {
47
+ readonly label: ReactNode;
48
+ readonly name: string;
49
+ readonly options: readonly {
50
+ readonly value: V;
51
+ readonly label: ReactNode;
52
+ }[];
53
+ readonly value: V | undefined;
54
+ readonly onChange: (value: V) => void;
55
+ readonly disabled?: boolean;
56
+ readonly inline?: boolean;
57
+ readonly className?: string;
58
+ }
59
+ export declare function RadioGroup<V extends string>({ label, name, options, value, onChange, disabled, inline, className }: RadioGroupProps<V>): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ export type Gap = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
3
+ export interface StackProps {
4
+ readonly gap?: Gap;
5
+ readonly children: ReactNode;
6
+ readonly className?: string;
7
+ }
8
+ export declare function Stack({ gap, children, className }: StackProps): import("react").JSX.Element;
9
+ export interface InlineProps {
10
+ readonly gap?: Gap;
11
+ readonly align?: 'start' | 'center' | 'end' | 'baseline' | 'stretch';
12
+ readonly justify?: 'start' | 'center' | 'end' | 'between';
13
+ readonly wrap?: boolean;
14
+ readonly children: ReactNode;
15
+ readonly className?: string;
16
+ }
17
+ export declare function Inline({ gap, align, justify, wrap, children, className }: InlineProps): import("react").JSX.Element;
@@ -0,0 +1,47 @@
1
+ import { type ComponentProps, type ReactNode } from 'react';
2
+ type ParamNames<P extends string> = P extends `${string}:${infer Name}/${infer Rest}` ? Name | ParamNames<`/${Rest}`> : P extends `${string}:${infer Name}` ? Name : never;
3
+ /** The params of a path pattern: `ParamsOf<'/deals/:id'>` is `{ readonly id: string }`. */
4
+ export type ParamsOf<P extends string> = {
5
+ readonly [Name in ParamNames<P>]: string;
6
+ };
7
+ /** A route name → path pattern table, such as `{ deals: '/deals', deal: '/deals/:id' }`. */
8
+ export type RouteTable = Readonly<Record<string, string>>;
9
+ type RouteName<R extends RouteTable> = keyof R & string;
10
+ /** A pattern without params takes no params argument; one with params requires them. */
11
+ export type ParamsArgs<P extends string> = [ParamNames<P>] extends [never] ? [] : [params: ParamsOf<P>];
12
+ export type RouteMatch<R extends RouteTable> = {
13
+ [Name in RouteName<R>]: {
14
+ readonly name: Name;
15
+ readonly params: ParamsOf<R[Name]>;
16
+ };
17
+ }[RouteName<R>];
18
+ export interface RouterOptions {
19
+ /** The path the application is mounted under; it is stripped when matching and added by `href`. */
20
+ readonly base?: string;
21
+ }
22
+ export interface NavigateOptions {
23
+ readonly replace?: boolean;
24
+ }
25
+ export type LinkProps<R extends RouteTable, Name extends RouteName<R>> = Readonly<Omit<ComponentProps<'a'>, 'href'>> & {
26
+ readonly to: Name;
27
+ } & ([ParamNames<R[Name]>] extends [never] ? {
28
+ readonly params?: undefined;
29
+ } : {
30
+ readonly params: ParamsOf<R[Name]>;
31
+ });
32
+ export interface RouterProps {
33
+ readonly children: ReactNode;
34
+ }
35
+ export interface CreatedRouter<R extends RouteTable> {
36
+ readonly Router: (props: RouterProps) => ReactNode;
37
+ readonly Link: <Name extends RouteName<R>>(props: LinkProps<R, Name>) => ReactNode;
38
+ readonly useRoute: () => RouteMatch<R> | null;
39
+ readonly href: <Name extends RouteName<R>>(name: Name, ...params: ParamsArgs<R[Name]>) => string;
40
+ readonly navigate: <Name extends RouteName<R>>(name: Name, ...args: [...ParamsArgs<R[Name]>, options?: NavigateOptions]) => void;
41
+ }
42
+ /**
43
+ * A typed History API router over a route table. Every function it answers is typed from `routes`, and `Router` turns
44
+ * every plain same-origin `<a href>` under `base` into an in-app navigation.
45
+ */
46
+ export declare function createRouter<const R extends RouteTable>(routes: R, options?: RouterOptions): CreatedRouter<R>;
47
+ export {};
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface SpinnerProps {
3
+ readonly label?: string;
4
+ readonly className?: string;
5
+ }
6
+ export declare function Spinner({ label, className }: SpinnerProps): import("react").JSX.Element;
7
+ export interface CountBadgeProps {
8
+ readonly count: number;
9
+ readonly className?: string;
10
+ }
11
+ export declare function CountBadge({ count, className }: CountBadgeProps): import("react").JSX.Element;
12
+ export interface EmptyStateProps {
13
+ readonly title: ReactNode;
14
+ readonly text?: ReactNode;
15
+ readonly action?: ReactNode;
16
+ readonly className?: string;
17
+ }
18
+ export declare function EmptyState({ title, text, action, className }: EmptyStateProps): import("react").JSX.Element;
19
+ export interface ErrorStateProps {
20
+ readonly title?: ReactNode;
21
+ readonly text: ReactNode;
22
+ readonly onRetry?: () => void;
23
+ readonly retryLabel?: string;
24
+ readonly className?: string;
25
+ }
26
+ export declare function ErrorState({ title, text, onRetry, retryLabel, className }: ErrorStateProps): import("react").JSX.Element;
27
+ export interface LoadingStateProps {
28
+ readonly text?: ReactNode;
29
+ readonly className?: string;
30
+ }
31
+ export declare function LoadingState({ text, className }: LoadingStateProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export type Tone = 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'accent';