@flood-md/react 0.2.1 → 0.2.3

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type ButtonHTMLAttributes, type InputHTMLAttributes, type TextareaHTMLAttributes, type AnchorHTMLAttributes, type ReactNode, type HTMLAttributes } from "react";
1
+ import { type ButtonHTMLAttributes, type InputHTMLAttributes, type TextareaHTMLAttributes, type SelectHTMLAttributes, type AnchorHTMLAttributes, type ReactNode, type HTMLAttributes, type LabelHTMLAttributes, type FieldsetHTMLAttributes, type RefCallback, type RefObject } from "react";
2
2
  export type ControlSize = "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
3
3
  export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
4
4
  variant?: "solid" | "subtle" | "outline" | "ghost";
@@ -6,50 +6,272 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
6
6
  loading?: boolean;
7
7
  }
8
8
  export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
9
+ export type FieldStatus = "error" | "warning" | "success" | "info";
10
+ export interface TabItem {
11
+ value: string;
12
+ label: string;
13
+ content: ReactNode;
14
+ icon?: ReactNode;
15
+ disabled?: boolean;
16
+ }
17
+ export interface TabsProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "defaultValue" | "onChange"> {
18
+ label: string;
19
+ items: readonly TabItem[];
20
+ value?: string;
21
+ defaultValue?: string;
22
+ onValueChange?: (value: string) => void;
23
+ activationMode?: "automatic" | "manual";
24
+ orientation?: "horizontal" | "vertical";
25
+ controlSize?: "sm" | "md" | "lg" | "xl";
26
+ disabled?: boolean;
27
+ iconOnly?: boolean;
28
+ }
29
+ export declare const Tabs: import("react").ForwardRefExoticComponent<TabsProps & import("react").RefAttributes<HTMLDivElement>>;
30
+ export interface BreadcrumbItem {
31
+ label: string;
32
+ href?: string;
33
+ }
34
+ export interface BreadcrumbProps extends Omit<HTMLAttributes<HTMLElement>, "children"> {
35
+ items: readonly BreadcrumbItem[];
36
+ label?: string;
37
+ separator?: string;
38
+ }
39
+ export declare const Breadcrumb: import("react").ForwardRefExoticComponent<BreadcrumbProps & import("react").RefAttributes<HTMLElement>>;
40
+ export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, "children" | "onChange"> {
41
+ page: number;
42
+ pageCount: number;
43
+ onPageChange?: (page: number) => void;
44
+ getHref?: (page: number) => string;
45
+ siblingCount?: 0 | 1 | 2;
46
+ controlSize?: "sm" | "md" | "lg" | "xl";
47
+ disabled?: boolean;
48
+ label?: string;
49
+ previousLabel?: string;
50
+ nextLabel?: string;
51
+ pageLabel?: (page: number) => string;
52
+ }
53
+ export declare const Pagination: import("react").ForwardRefExoticComponent<PaginationProps & import("react").RefAttributes<HTMLElement>>;
54
+ export interface OverlayTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
55
+ ref: RefCallback<HTMLButtonElement>;
56
+ }
57
+ export interface TooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "content" | "title" | "role"> {
58
+ content: string;
59
+ trigger: (props: OverlayTriggerProps) => ReactNode;
60
+ side?: "top" | "bottom" | "left" | "right";
61
+ align?: "start" | "center" | "end";
62
+ delay?: number;
63
+ disabled?: boolean;
64
+ }
65
+ export declare const Tooltip: import("react").ForwardRefExoticComponent<TooltipProps & import("react").RefAttributes<HTMLDivElement>>;
66
+ export interface PopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "title" | "role"> {
67
+ title: string;
68
+ trigger: (props: OverlayTriggerProps) => ReactNode;
69
+ children?: ReactNode;
70
+ open?: boolean;
71
+ defaultOpen?: boolean;
72
+ onOpenChange?: (open: boolean) => void;
73
+ disabled?: boolean;
74
+ side?: "top" | "bottom" | "left" | "right";
75
+ align?: "start" | "center" | "end";
76
+ closeLabel?: string;
77
+ initialFocusRef?: RefObject<HTMLElement | null>;
78
+ }
79
+ export declare const Popover: import("react").ForwardRefExoticComponent<PopoverProps & import("react").RefAttributes<HTMLDivElement>>;
80
+ export interface DialogProps extends Omit<HTMLAttributes<HTMLDialogElement>, "children" | "title" | "role"> {
81
+ title: string;
82
+ description?: string;
83
+ trigger: (props: OverlayTriggerProps) => ReactNode;
84
+ children?: ReactNode;
85
+ open?: boolean;
86
+ defaultOpen?: boolean;
87
+ onOpenChange?: (open: boolean) => void;
88
+ disabled?: boolean;
89
+ closeLabel?: string;
90
+ closeOnOutsideClick?: boolean;
91
+ initialFocusRef?: RefObject<HTMLElement | null>;
92
+ }
93
+ export declare const Dialog: import("react").ForwardRefExoticComponent<DialogProps & import("react").RefAttributes<HTMLDialogElement>>;
94
+ export interface CollapsibleProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onChange"> {
95
+ title: string;
96
+ open?: boolean;
97
+ defaultOpen?: boolean;
98
+ onOpenChange?: (open: boolean) => void;
99
+ disabled?: boolean;
100
+ controlSize?: "sm" | "md" | "lg" | "xl";
101
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
102
+ }
103
+ export declare const Collapsible: import("react").ForwardRefExoticComponent<CollapsibleProps & import("react").RefAttributes<HTMLDivElement>>;
104
+ export interface AccordionItem {
105
+ value: string;
106
+ title: string;
107
+ content: ReactNode;
108
+ disabled?: boolean;
109
+ }
110
+ export interface AccordionProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "defaultValue" | "onChange"> {
111
+ label: string;
112
+ items: readonly AccordionItem[];
113
+ expansionMode?: "single" | "multiple";
114
+ value?: readonly string[];
115
+ defaultValue?: readonly string[];
116
+ onValueChange?: (value: string[]) => void;
117
+ disabled?: boolean;
118
+ controlSize?: "sm" | "md" | "lg" | "xl";
119
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
120
+ }
121
+ export declare const Accordion: import("react").ForwardRefExoticComponent<AccordionProps & import("react").RefAttributes<HTMLDivElement>>;
122
+ export interface ToggleProps extends Omit<ButtonProps, "aria-pressed" | "type" | "role" | "defaultValue"> {
123
+ pressed?: boolean;
124
+ defaultPressed?: boolean;
125
+ onPressedChange?: (pressed: boolean) => void;
126
+ iconOnly?: boolean;
127
+ }
128
+ /** A native toggle button; its accessible name stays stable when pressed. */
129
+ export declare const Toggle: import("react").ForwardRefExoticComponent<ToggleProps & import("react").RefAttributes<HTMLButtonElement>>;
130
+ export interface ToggleOption {
131
+ value: string;
132
+ label: string;
133
+ icon?: ReactNode;
134
+ disabled?: boolean;
135
+ }
136
+ export interface ToggleGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "role" | "children" | "defaultValue" | "onChange"> {
137
+ label: string;
138
+ options: readonly ToggleOption[];
139
+ selectionMode?: "single" | "multiple";
140
+ value?: readonly string[];
141
+ defaultValue?: readonly string[];
142
+ onValueChange?: (value: string[]) => void;
143
+ orientation?: "horizontal" | "vertical";
144
+ size?: ControlSize;
145
+ variant?: "solid" | "subtle" | "outline" | "ghost";
146
+ disabled?: boolean;
147
+ iconOnly?: boolean;
148
+ }
149
+ /** Optional selections exposed as pressed buttons, not radios, tabs or a toolbar. */
150
+ export declare const ToggleGroup: import("react").ForwardRefExoticComponent<ToggleGroupProps & import("react").RefAttributes<HTMLDivElement>>;
151
+ export interface ButtonGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "role"> {
152
+ label: string;
153
+ orientation?: "horizontal" | "vertical";
154
+ }
155
+ /** Connected actions keep their ordinary Tab order; this is not a toolbar. */
156
+ export declare const ButtonGroup: import("react").ForwardRefExoticComponent<ButtonGroupProps & import("react").RefAttributes<HTMLDivElement>>;
9
157
  export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
158
+ status?: FieldStatus;
10
159
  invalid?: boolean;
11
160
  controlSize?: "sm" | "md" | "lg" | "xl";
12
161
  }
13
162
  export declare const Input: import("react").ForwardRefExoticComponent<InputProps & import("react").RefAttributes<HTMLInputElement>>;
163
+ export interface InputGroupProps extends Omit<InputProps, "children" | "type"> {
164
+ type?: "text" | "email" | "password" | "search" | "tel" | "url" | "number";
165
+ start?: ReactNode;
166
+ end?: ReactNode;
167
+ }
168
+ /** One real Input with host-owned logical start/end adornments. Ref and native attributes target the input. */
169
+ export declare const InputGroup: import("react").ForwardRefExoticComponent<InputGroupProps & import("react").RefAttributes<HTMLInputElement>>;
14
170
  export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
171
+ status?: FieldStatus;
15
172
  invalid?: boolean;
16
173
  controlSize?: "sm" | "md" | "lg" | "xl";
17
174
  }
18
175
  export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaProps & import("react").RefAttributes<HTMLTextAreaElement>>;
176
+ export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
177
+ status?: FieldStatus;
178
+ invalid?: boolean;
179
+ controlSize?: "sm" | "md" | "lg" | "xl";
180
+ }
181
+ /** Native select and option semantics with an independent visual control size. */
182
+ export declare const Select: import("react").ForwardRefExoticComponent<SelectProps & import("react").RefAttributes<HTMLSelectElement>>;
19
183
  export interface ChoiceProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "size"> {
20
184
  label: ReactNode;
21
185
  }
22
186
  export declare const Checkbox: import("react").ForwardRefExoticComponent<ChoiceProps & import("react").RefAttributes<HTMLInputElement>>;
23
187
  export declare const Radio: import("react").ForwardRefExoticComponent<ChoiceProps & import("react").RefAttributes<HTMLInputElement>>;
24
188
  export declare const Switch: import("react").ForwardRefExoticComponent<ChoiceProps & import("react").RefAttributes<HTMLInputElement>>;
189
+ export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
190
+ required?: boolean;
191
+ }
192
+ /** A native label; the required marker never substitutes for the control's required prop. */
193
+ export declare const Label: import("react").ForwardRefExoticComponent<LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
194
+ export interface FieldsetProps extends FieldsetHTMLAttributes<HTMLFieldSetElement> {
195
+ legend: ReactNode;
196
+ /** @since 0.2.3 */
197
+ hideLabel?: boolean;
198
+ hint?: ReactNode;
199
+ error?: ReactNode;
200
+ message?: ReactNode;
201
+ status?: FieldStatus;
202
+ }
203
+ /** A native group: disabled, form ownership and nested fieldsets retain browser behavior. */
204
+ export declare const Fieldset: import("react").ForwardRefExoticComponent<FieldsetProps & import("react").RefAttributes<HTMLFieldSetElement>>;
205
+ export interface ChoiceOption {
206
+ value: string;
207
+ label: ReactNode;
208
+ disabled?: boolean;
209
+ }
210
+ export interface RadioGroupProps extends Omit<FieldsetProps, "children" | "defaultValue" | "role"> {
211
+ name: string;
212
+ options: readonly ChoiceOption[];
213
+ value?: string;
214
+ defaultValue?: string;
215
+ onValueChange?: (value: string) => void;
216
+ orientation?: "vertical" | "horizontal";
217
+ required?: boolean;
218
+ }
219
+ export declare const RadioGroup: import("react").ForwardRefExoticComponent<RadioGroupProps & import("react").RefAttributes<HTMLFieldSetElement>>;
220
+ export interface SegmentedControlProps extends Omit<RadioGroupProps, "legend" | "options"> {
221
+ label: string;
222
+ options: readonly ToggleOption[];
223
+ controlSize?: "sm" | "md" | "lg" | "xl";
224
+ iconOnly?: boolean;
225
+ fullWidth?: boolean;
226
+ }
227
+ /** A segmented presentation of native single-choice inputs, retaining forms and reset. */
228
+ export declare const SegmentedControl: import("react").ForwardRefExoticComponent<SegmentedControlProps & import("react").RefAttributes<HTMLFieldSetElement>>;
229
+ export interface CheckboxGroupProps extends Omit<FieldsetProps, "children" | "defaultValue" | "role"> {
230
+ name: string;
231
+ options: readonly ChoiceOption[];
232
+ value?: readonly string[];
233
+ defaultValue?: readonly string[];
234
+ onValueChange?: (value: string[]) => void;
235
+ orientation?: "vertical" | "horizontal";
236
+ }
237
+ export declare const CheckboxGroup: import("react").ForwardRefExoticComponent<CheckboxGroupProps & import("react").RefAttributes<HTMLFieldSetElement>>;
25
238
  export interface FieldControlProps {
26
239
  id: string;
27
240
  "aria-describedby"?: string;
28
241
  "aria-invalid"?: true;
242
+ status?: FieldStatus;
29
243
  required?: boolean;
30
244
  }
31
245
  export interface FieldProps {
246
+ status?: FieldStatus;
247
+ message?: ReactNode;
32
248
  id?: string;
33
249
  label: ReactNode;
250
+ /** @since 0.2.3 */
251
+ hideLabel?: boolean;
34
252
  hint?: ReactNode;
35
253
  error?: ReactNode;
36
254
  required?: boolean;
37
255
  describedBy?: string;
38
256
  children: (props: FieldControlProps) => ReactNode;
39
257
  }
40
- export declare function Field({ id: explicitId, label, hint, error, required, describedBy, children, }: FieldProps): import("react").JSX.Element;
41
- export declare const Link: import("react").ForwardRefExoticComponent<AnchorHTMLAttributes<HTMLAnchorElement> & import("react").RefAttributes<HTMLAnchorElement>>;
258
+ export declare function Field({ status, message, id: explicitId, label, hideLabel, hint, error, required, describedBy, children, }: FieldProps): import("react").JSX.Element;
259
+ export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
260
+ variant?: "text" | "solid" | "subtle" | "outline" | "ghost";
261
+ size?: ControlSize;
262
+ }
263
+ export declare const Link: import("react").ForwardRefExoticComponent<LinkProps & import("react").RefAttributes<HTMLAnchorElement>>;
42
264
  /** Explicit opt-in to the dark foundations profile; no global theme mutation. */
43
265
  export declare const FloodRoot: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
44
266
  export interface TextProps extends HTMLAttributes<HTMLElement> {
45
267
  as?: "span" | "p" | "div";
46
- size?: "body" | "compact" | "label" | "helper";
268
+ size?: "lead" | "body" | "compact" | "label" | "helper";
47
269
  tone?: "default" | "subtle" | "error" | "success" | "warning" | "info";
48
270
  }
49
271
  export declare const Text: import("react").ForwardRefExoticComponent<TextProps & import("react").RefAttributes<HTMLElement>>;
50
272
  export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
51
273
  level?: 1 | 2 | 3 | 4 | 5 | 6;
52
- size?: "large" | "page" | "section";
274
+ size?: "display" | "large" | "page" | "section";
53
275
  }
54
276
  export declare const Heading: import("react").ForwardRefExoticComponent<HeadingProps & import("react").RefAttributes<HTMLHeadingElement>>;
55
277
  export type Space = "0" | "1" | "2" | "3" | "4" | "6" | "8" | "12";
@@ -77,3 +299,15 @@ export interface IconButtonProps extends Omit<ButtonProps, "aria-label"> {
77
299
  }
78
300
  export declare const IconButton: import("react").ForwardRefExoticComponent<IconButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
79
301
  export declare const Divider: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLHRElement> & import("react").RefAttributes<HTMLHRElement>>;
302
+ export interface CodeSnippetProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
303
+ code: string;
304
+ action?: ReactNode;
305
+ }
306
+ /** Selectable code with an optional host-owned action; no clipboard side effects. */
307
+ export declare const CodeSnippet: import("react").ForwardRefExoticComponent<CodeSnippetProps & import("react").RefAttributes<HTMLDivElement>>;
308
+ export interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {
309
+ orientation?: "vertical" | "horizontal" | "both";
310
+ contentInset?: "none" | "scrollbar";
311
+ }
312
+ /** Native scrolling with inset overlay tracks; the ref targets the scrollable viewport. */
313
+ export declare const ScrollArea: import("react").ForwardRefExoticComponent<ScrollAreaProps & import("react").RefAttributes<HTMLDivElement>>;
package/dist/index.js CHANGED
@@ -1,15 +1,124 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { forwardRef, createElement, useId, } from "react";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, createElement, useId, useState, } from "react";
4
+ import { resolveFieldStatus, hasFieldContent, mergeDescriptions } from "./field-state.js";
5
+ import { SingleSelect } from "./select.js";
6
+ import { OverlayScrollArea } from "./scroll-area.js";
7
+ import { ChoiceGroup } from "./choice-group.js";
8
+ import { Disclosure } from "./disclosure.js";
9
+ import { AnchoredOverlay, ModalOverlay } from "./overlays.js";
10
+ import { TabNavigation, BreadcrumbNavigation, PageNavigation } from "./navigation.js";
4
11
  const classes = (...values) => values.filter(Boolean).join(" ");
5
12
  export const Button = forwardRef(function Button({ variant = "solid", size = "md", loading = false, disabled, type = "button", className, children, ...props }, ref) {
6
13
  return (_jsxs("button", { ...props, ref: ref, type: type, disabled: disabled || loading, "aria-busy": loading || undefined, className: classes("flood-button", `flood-button--${variant}`, `flood-button--${size}`, className), children: [loading && _jsx("span", { className: "flood-spinner", "aria-hidden": "true" }), children] }));
7
14
  });
8
- export const Input = forwardRef(function Input({ invalid, controlSize = "md", className, ...props }, ref) {
9
- return (_jsx("input", { ...props, ref: ref, "aria-invalid": invalid || props["aria-invalid"], className: classes("flood-input", `flood-control--${controlSize}`, className) }));
10
- });
11
- export const Textarea = forwardRef(function Textarea({ invalid, controlSize = "md", className, ...props }, ref) {
12
- return (_jsx("textarea", { ...props, ref: ref, "aria-invalid": invalid || props["aria-invalid"], className: classes("flood-input", "flood-textarea", `flood-control--${controlSize}`, className) }));
15
+ export const Tabs = forwardRef(function Tabs({ activationMode = "automatic", orientation = "horizontal", controlSize = "md", disabled = false, iconOnly = false, ...props }, ref) { return _jsx(TabNavigation, { ...props, ref: ref, activationMode: activationMode, orientation: orientation, controlSize: controlSize, disabled: disabled, iconOnly: iconOnly }); });
16
+ export const Breadcrumb = forwardRef(function Breadcrumb({ label = "Breadcrumb", separator = "/", ...props }, ref) { return _jsx(BreadcrumbNavigation, { ...props, ref: ref, label: label, separator: separator }); });
17
+ export const Pagination = forwardRef(function Pagination({ siblingCount = 1, controlSize = "sm", disabled = false, label = "Pagination", previousLabel = "Previous page", nextLabel = "Next page", ...props }, ref) { return _jsx(PageNavigation, { ...props, ref: ref, siblingCount: siblingCount, controlSize: controlSize, disabled: disabled, label: label, previousLabel: previousLabel, nextLabel: nextLabel }); });
18
+ export const Tooltip = forwardRef(function Tooltip({ side = "top", align = "center", delay = 300, disabled = false, ...props }, ref) { return _jsx(AnchoredOverlay, { ...props, ref: ref, kind: "tooltip", side: side, align: align, delay: delay, disabled: disabled }); });
19
+ export const Popover = forwardRef(function Popover({ side = "bottom", align = "start", defaultOpen = false, disabled = false, closeLabel = "Close", ...props }, ref) { return _jsx(AnchoredOverlay, { ...props, ref: ref, kind: "popover", side: side, align: align, defaultOpen: defaultOpen, disabled: disabled, closeLabel: closeLabel }); });
20
+ export const Dialog = forwardRef(function Dialog({ defaultOpen = false, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, ...props }, ref) { return _jsx(ModalOverlay, { ...props, ref: ref, defaultOpen: defaultOpen, disabled: disabled, closeLabel: closeLabel, closeOnOutsideClick: closeOnOutsideClick }); });
21
+ export const Collapsible = forwardRef(function Collapsible({ open, defaultOpen = false, onOpenChange, disabled = false, controlSize = "md", ...props }, ref) {
22
+ const [local, setLocal] = useState(defaultOpen);
23
+ return _jsx(Disclosure, { ...props, ref: ref, expanded: open ?? local, disabled: disabled, controlSize: controlSize, onExpandedChange: next => {
24
+ if (open === undefined)
25
+ setLocal(next);
26
+ onOpenChange?.(next);
27
+ } });
28
+ });
29
+ export const Accordion = forwardRef(function Accordion({ label, items, expansionMode = "single", value, defaultValue = [], onValueChange, disabled = false, controlSize = "md", headingLevel = 3, className, onKeyDown, ...props }, ref) {
30
+ const [local, setLocal] = useState(() => [...new Set(defaultValue)]);
31
+ const expanded = value ?? local;
32
+ if (new Set(items.map(item => item.value)).size !== items.length)
33
+ throw new Error("Flood accordion item values must be unique");
34
+ if (expansionMode === "single" && new Set(expanded).size > 1)
35
+ throw new Error("Flood single Accordion accepts at most one value");
36
+ return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, className: classes("flood-accordion", className), onKeyDown: event => {
37
+ onKeyDown?.(event);
38
+ if (event.defaultPrevented)
39
+ return;
40
+ const buttons = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-disclosure > .flood-disclosure__heading > .flood-disclosure__trigger:not(:disabled)"));
41
+ const index = buttons.indexOf(event.target);
42
+ if (index < 0)
43
+ return;
44
+ const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : event.key === "ArrowDown" ? (index + 1) % buttons.length : event.key === "ArrowUp" ? (index - 1 + buttons.length) % buttons.length : -1;
45
+ if (next >= 0) {
46
+ event.preventDefault();
47
+ buttons[next].focus();
48
+ }
49
+ }, children: items.map(item => _jsx(Collapsible, { title: item.title, open: expanded.includes(item.value), disabled: disabled || item.disabled, controlSize: controlSize, headingLevel: headingLevel, onOpenChange: active => {
50
+ const next = active ? expansionMode === "single" ? [item.value] : [...new Set([...expanded, item.value])] : expanded.filter(value => value !== item.value);
51
+ if (value === undefined)
52
+ setLocal(next);
53
+ onValueChange?.([...next]);
54
+ }, children: item.content }, item.value)) });
55
+ });
56
+ /** A native toggle button; its accessible name stays stable when pressed. */
57
+ export const Toggle = forwardRef(function Toggle({ pressed, defaultPressed = false, onPressedChange, iconOnly = false, size = "md", variant = "subtle", onClick, className, children, disabled, loading, ...props }, ref) {
58
+ const [local, setLocal] = useState(defaultPressed);
59
+ const selected = pressed ?? local;
60
+ return _jsx(Button, { ...props, ref: ref, type: "button", "aria-pressed": selected, size: size, variant: variant, disabled: disabled, loading: loading, className: classes("flood-toggle", iconOnly && "flood-icon-button", className), onClick: event => {
61
+ onClick?.(event);
62
+ if (event.defaultPrevented || disabled || loading)
63
+ return;
64
+ if (pressed === undefined)
65
+ setLocal(!selected);
66
+ onPressedChange?.(!selected);
67
+ }, children: iconOnly ? _jsx(IconSlot, { size: size, children: children }) : children });
68
+ });
69
+ /** Optional selections exposed as pressed buttons, not radios, tabs or a toolbar. */
70
+ export const ToggleGroup = forwardRef(function ToggleGroup({ label, options, selectionMode = "multiple", value, defaultValue = [], onValueChange, orientation = "horizontal", size = "md", variant = "subtle", disabled = false, iconOnly = false, className, onKeyDown, ...props }, ref) {
71
+ const [local, setLocal] = useState(() => [...new Set(defaultValue)]);
72
+ const selected = value ?? local;
73
+ if (new Set(options.map(option => option.value)).size !== options.length)
74
+ throw new Error("Flood toggle option values must be unique");
75
+ if (selectionMode === "single" && new Set(selected).size > 1)
76
+ throw new Error("Flood single ToggleGroup accepts at most one value");
77
+ if (iconOnly && options.some(option => !option.icon))
78
+ throw new Error("Flood icon-only toggle options require icons");
79
+ return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, "data-selection-mode": selectionMode, className: classes("flood-button-group", "flood-toggle-group", className), onKeyDown: event => {
80
+ onKeyDown?.(event);
81
+ if (event.defaultPrevented)
82
+ return;
83
+ const buttons = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-toggle:not(:disabled)"));
84
+ const index = buttons.indexOf(event.target);
85
+ if (index < 0)
86
+ return;
87
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
88
+ const nextKey = orientation === "vertical" ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight";
89
+ const previousKey = orientation === "vertical" ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
90
+ const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : event.key === nextKey ? (index + 1) % buttons.length : event.key === previousKey ? (index - 1 + buttons.length) % buttons.length : -1;
91
+ if (next >= 0) {
92
+ event.preventDefault();
93
+ buttons[next].focus();
94
+ }
95
+ }, children: options.map(option => _jsx(Toggle, { size: size, variant: variant, disabled: disabled || option.disabled, pressed: selected.includes(option.value), iconOnly: iconOnly, "aria-label": iconOnly ? option.label : undefined, onPressedChange: active => {
96
+ const next = active ? selectionMode === "single" ? [option.value] : [...new Set([...selected, option.value])] : selected.filter(item => item !== option.value);
97
+ if (value === undefined)
98
+ setLocal(next);
99
+ onValueChange?.([...next]);
100
+ }, children: iconOnly ? option.icon : _jsxs(_Fragment, { children: [option.icon && _jsx(IconSlot, { size: size, children: option.icon }), option.label] }) }, option.value)) });
101
+ });
102
+ /** Connected actions keep their ordinary Tab order; this is not a toolbar. */
103
+ export const ButtonGroup = forwardRef(function ButtonGroup({ label, orientation = "horizontal", className, ...props }, ref) {
104
+ return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, className: classes("flood-button-group", className) });
105
+ });
106
+ export const Input = forwardRef(function Input({ invalid, status, controlSize = "md", className, ...props }, ref) {
107
+ return (_jsx("input", { ...props, ref: ref, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", `flood-control--${controlSize}`, className) }));
108
+ });
109
+ /** One real Input with host-owned logical start/end adornments. Ref and native attributes target the input. */
110
+ export const InputGroup = forwardRef(function InputGroup({ start, end, controlSize = "md", className, style, invalid, status, disabled, readOnly, ...props }, ref) {
111
+ return _jsxs("div", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-input-group", `flood-control--${controlSize}`, className), style: style, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, children: [hasFieldContent(start) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--start", children: start }), _jsx(Input, { ...props, ref: ref, controlSize: controlSize, invalid: invalid, status: status, disabled: disabled, readOnly: readOnly }), hasFieldContent(end) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--end", children: end })] });
112
+ });
113
+ export const Textarea = forwardRef(function Textarea({ invalid, status, controlSize = "md", className, ...props }, ref) {
114
+ return (_jsx("textarea", { ...props, ref: ref, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", "flood-textarea", `flood-control--${controlSize}`, className) }));
115
+ });
116
+ /** Native select and option semantics with an independent visual control size. */
117
+ export const Select = forwardRef(function Select({ invalid, status, controlSize = "md", className, multiple, size, ...props }, ref) {
118
+ const listbox = multiple || (size !== undefined && size > 1);
119
+ if (!listbox)
120
+ return _jsx(SingleSelect, { ...props, status: status, invalid: invalid, controlSize: controlSize, className: className, size: size, ref: ref });
121
+ return (_jsx("span", { className: classes("flood-select", `flood-control--${controlSize}`, listbox && "flood-select--listbox"), children: _jsx("select", { ...props, ref: ref, multiple: multiple, size: size, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", "flood-select__control", `flood-control--${controlSize}`, className) }) }));
13
122
  });
14
123
  export const Checkbox = forwardRef(function Checkbox({ label, className, ...props }, ref) {
15
124
  return (_jsxs("label", { className: classes("flood-choice", className), children: [_jsx("input", { ...props, ref: ref, type: "checkbox", className: "flood-check" }), _jsx("span", { children: label })] }));
@@ -20,20 +129,66 @@ export const Radio = forwardRef(function Radio({ label, className, ...props }, r
20
129
  export const Switch = forwardRef(function Switch({ label, className, ...props }, ref) {
21
130
  return (_jsxs("label", { className: classes("flood-choice", "flood-switch", className), children: [_jsx("span", { children: label }), _jsx("input", { ...props, ref: ref, type: "checkbox", role: "switch", className: "flood-switch-input" })] }));
22
131
  });
23
- export function Field({ id: explicitId, label, hint, error, required, describedBy, children, }) {
132
+ /** A native label; the required marker never substitutes for the control's required prop. */
133
+ export const Label = forwardRef(function Label({ required, children, className, ...props }, ref) {
134
+ return _jsxs("label", { ...props, ref: ref, className: classes("flood-label", className), children: [children, required && _jsx("span", { "aria-hidden": "true", children: " *" })] });
135
+ });
136
+ /** A native group: disabled, form ownership and nested fieldsets retain browser behavior. */
137
+ export const Fieldset = forwardRef(function Fieldset({ legend, hideLabel = false, hint, error, message, status, children, className, id: explicitId, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }, ref) {
138
+ const generatedId = useId();
139
+ const id = explicitId ?? `flood-${generatedId}`;
140
+ const hasHint = hasFieldContent(hint), hasError = hasFieldContent(error), hasMessage = hasFieldContent(message) && !hasError;
141
+ const resolvedStatus = resolveFieldStatus(status, hasError, ariaInvalid);
142
+ const description = mergeDescriptions(describedBy, hasHint && `${id}-hint`, hasError && `${id}-error`, hasMessage && `${id}-message`);
143
+ return _jsxs("fieldset", { ...props, ref: ref, id: id, className: classes("flood-fieldset", className), "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, "aria-describedby": description, "aria-invalid": resolvedStatus === "error" || ariaInvalid || undefined, children: [_jsx("legend", { className: classes("flood-legend", hideLabel && "flood-visually-hidden"), children: legend }), _jsxs("div", { className: "flood-fieldset__body", children: [hasHint && _jsx("div", { id: `${id}-hint`, className: "flood-hint", children: hint }), children, hasMessage && _jsx("div", { id: `${id}-message`, className: "flood-message", role: resolvedStatus === "error" ? "alert" : undefined, children: message }), hasError && _jsx("div", { id: `${id}-error`, className: "flood-error", role: "alert", children: error })] })] });
144
+ });
145
+ export const RadioGroup = forwardRef(function RadioGroup({ value, defaultValue, onValueChange, orientation = "vertical", ...props }, ref) {
146
+ return _jsx(ChoiceGroup, { ...props, ref: ref, kind: "radio", orientation: orientation, selected: value === undefined ? undefined : [value], initial: defaultValue === undefined ? [] : [defaultValue], onSelectionChange: next => onValueChange?.(next[0] ?? ""), fieldsetComponent: Fieldset, choiceComponent: Radio });
147
+ });
148
+ /** A segmented presentation of native single-choice inputs, retaining forms and reset. */
149
+ export const SegmentedControl = forwardRef(function SegmentedControl({ label, options, controlSize = "md", iconOnly = false, fullWidth = false, orientation = "horizontal", className, onKeyDown, ...props }, ref) {
150
+ if (iconOnly && options.some(option => !option.icon))
151
+ throw new Error("Flood icon-only segments require icons");
152
+ return _jsx(RadioGroup, { ...props, ref: ref, legend: label, orientation: orientation, className: classes("flood-segmented", `flood-control--${controlSize}`, iconOnly && "flood-segmented--icons", fullWidth && "flood-segmented--full", className), options: options.map(option => ({ ...option, label: _jsxs(_Fragment, { children: [option.icon && _jsx(IconSlot, { size: controlSize, children: option.icon }), iconOnly ? _jsx("span", { className: "flood-segmented__name", children: option.label }) : option.label] }) })), onKeyDown: event => {
153
+ onKeyDown?.(event);
154
+ if (event.defaultPrevented)
155
+ return;
156
+ const inputs = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-fieldset__body > .flood-choice-group > .flood-choice > input[type=radio]:not(:disabled)"));
157
+ const index = inputs.indexOf(event.target);
158
+ if (index < 0)
159
+ return;
160
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
161
+ const next = event.key === "Home" ? 0 : event.key === "End" ? inputs.length - 1
162
+ : event.key === "ArrowDown" || event.key === (rtl ? "ArrowLeft" : "ArrowRight") ? (index + 1) % inputs.length
163
+ : event.key === "ArrowUp" || event.key === (rtl ? "ArrowRight" : "ArrowLeft") ? (index - 1 + inputs.length) % inputs.length : -1;
164
+ if (next >= 0) {
165
+ event.preventDefault();
166
+ inputs[next].focus();
167
+ inputs[next].click();
168
+ }
169
+ } });
170
+ });
171
+ export const CheckboxGroup = forwardRef(function CheckboxGroup({ value, defaultValue = [], onValueChange, orientation = "vertical", ...props }, ref) {
172
+ return _jsx(ChoiceGroup, { ...props, ref: ref, kind: "checkbox", orientation: orientation, selected: value, initial: defaultValue, onSelectionChange: onValueChange, fieldsetComponent: Fieldset, choiceComponent: Checkbox });
173
+ });
174
+ export function Field({ status, message, id: explicitId, label, hideLabel = false, hint, error, required, describedBy, children, }) {
24
175
  const generatedId = useId();
25
176
  const id = explicitId ?? `flood-${generatedId}`;
26
- const description = [describedBy, hint && `${id}-hint`, error && `${id}-error`].filter(Boolean).join(" ") ||
27
- undefined;
28
- return (_jsxs("div", { className: "flood-field", children: [_jsxs("label", { className: "flood-label", htmlFor: id, children: [label, required && _jsx("span", { "aria-hidden": "true", children: " *" })] }), children({
177
+ const hasMessage = hasFieldContent(message);
178
+ const hasHint = hasFieldContent(hint);
179
+ const hasError = hasFieldContent(error);
180
+ const resolvedStatus = hasError ? "error" : status;
181
+ const description = mergeDescriptions(describedBy, hasHint && `${id}-hint`, hasError && `${id}-error`, hasMessage && !hasError && `${id}-message`);
182
+ return (_jsxs("div", { className: "flood-field", "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, children: [_jsx(Label, { htmlFor: id, required: required, className: hideLabel ? "flood-visually-hidden" : undefined, children: label }), children({
29
183
  id,
30
184
  "aria-describedby": description,
31
- "aria-invalid": error ? true : undefined,
185
+ "aria-invalid": hasError || resolvedStatus === "error" ? true : undefined,
186
+ status: resolvedStatus,
32
187
  required,
33
- }), hint && (_jsx("div", { id: `${id}-hint`, className: "flood-hint", children: hint })), error && (_jsx("div", { id: `${id}-error`, className: "flood-error", role: "alert", children: error }))] }));
188
+ }), hasHint && (_jsx("div", { id: `${id}-hint`, className: "flood-hint", children: hint })), hasMessage && !hasError && _jsx("div", { id: `${id}-message`, className: "flood-message", role: resolvedStatus === "error" ? "alert" : undefined, children: message }), hasError && (_jsx("div", { id: `${id}-error`, className: "flood-error", role: "alert", children: error }))] }));
34
189
  }
35
- export const Link = forwardRef(function Link({ className, ...props }, ref) {
36
- return (_jsx("a", { ...props, ref: ref, className: classes("flood-link", className) }));
190
+ export const Link = forwardRef(function Link({ variant = "text", size = "md", className, ...props }, ref) {
191
+ return (_jsx("a", { ...props, ref: ref, className: classes("flood-link", variant !== "text" && "flood-link--button", variant !== "text" && "flood-button", variant !== "text" && `flood-button--${variant}`, variant !== "text" && `flood-button--${size}`, className) }));
37
192
  });
38
193
  /** Explicit opt-in to the dark foundations profile; no global theme mutation. */
39
194
  export const FloodRoot = forwardRef(function FloodRoot({ className, ...props }, ref) {
@@ -64,3 +219,11 @@ export const IconButton = forwardRef(function IconButton({ label, size = "md", v
64
219
  export const Divider = forwardRef(function Divider({ className, ...props }, ref) {
65
220
  return _jsx("hr", { ...props, ref: ref, className: classes("flood-divider", className) });
66
221
  });
222
+ /** Selectable code with an optional host-owned action; no clipboard side effects. */
223
+ export const CodeSnippet = forwardRef(function CodeSnippet({ code, action, className, ...props }, ref) {
224
+ return (_jsxs("div", { ...props, ref: ref, className: classes("flood-code-snippet", className), children: [_jsx("code", { className: "flood-code-snippet__code", children: code }), action != null && _jsx("div", { className: "flood-code-snippet__action", children: action })] }));
225
+ });
226
+ /** Native scrolling with inset overlay tracks; the ref targets the scrollable viewport. */
227
+ export const ScrollArea = forwardRef(function ScrollArea({ orientation = "vertical", contentInset = "scrollbar", ...props }, ref) {
228
+ return _jsx(OverlayScrollArea, { ...props, orientation: orientation, contentInset: contentInset, ref: ref });
229
+ });
@@ -0,0 +1,4 @@
1
+ import type { TabsProps, BreadcrumbProps, PaginationProps } from "./index.js";
2
+ export declare const TabNavigation: import("react").ForwardRefExoticComponent<TabsProps & import("react").RefAttributes<HTMLDivElement>>;
3
+ export declare const BreadcrumbNavigation: import("react").ForwardRefExoticComponent<BreadcrumbProps & import("react").RefAttributes<HTMLElement>>;
4
+ export declare const PageNavigation: import("react").ForwardRefExoticComponent<PaginationProps & import("react").RefAttributes<HTMLElement>>;
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
+ import { OverlayScrollArea } from "./scroll-area.js";
4
+ import { pageWindow } from "./page-window.js";
5
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
6
+ export const TabNavigation = forwardRef(function TabNavigation({ label, items, value, defaultValue, onValueChange, activationMode = "automatic", orientation = "horizontal", controlSize = "md", disabled = false, iconOnly = false, className, onKeyDown, id: explicitId, ...props }, ref) {
7
+ const generated = useId(), id = explicitId ?? `flood-tabs-${generated}`;
8
+ const [local, setLocal] = useState(defaultValue), [focused, setFocused] = useState();
9
+ const root = useRef(null), list = useRef(null), focusedPanel = useRef(undefined);
10
+ useImperativeHandle(ref, () => root.current, []);
11
+ if (new Set(items.map(item => item.value)).size !== items.length)
12
+ throw new Error("Flood tab values must be unique");
13
+ if (iconOnly && items.some(item => !item.icon))
14
+ throw new Error("Flood icon-only tabs require icons");
15
+ const available = items.filter(item => !item.disabled), enabled = disabled ? [] : available;
16
+ const selected = available.find(item => item.value === (value ?? local))?.value ?? available[0]?.value;
17
+ const tabStop = enabled.some(item => item.value === focused) ? focused : selected;
18
+ const select = (next) => { if (next === selected)
19
+ return; if (value === undefined)
20
+ setLocal(next); onValueChange?.(next); };
21
+ useBrowserLayoutEffect(() => {
22
+ if (focusedPanel.current === undefined || focusedPanel.current === selected)
23
+ return;
24
+ const index = items.findIndex(item => item.value === focusedPanel.current), panel = root.current?.ownerDocument.getElementById(`${id}-panel-${index}`);
25
+ const active = root.current?.ownerDocument.activeElement;
26
+ if (active === root.current?.ownerDocument.body || panel?.contains(active ?? null)) {
27
+ const next = items.findIndex(item => item.value === selected);
28
+ root.current?.ownerDocument.getElementById(`${id}-tab-${next}`)?.focus();
29
+ }
30
+ focusedPanel.current = undefined;
31
+ }, [selected, id, items]);
32
+ return _jsxs("div", { ...props, ref: root, id: id, className: ["flood-tabs", className].filter(Boolean).join(" "), "data-orientation": orientation, onKeyDown: event => {
33
+ onKeyDown?.(event);
34
+ if (event.defaultPrevented)
35
+ return;
36
+ const buttons = Array.from(list.current?.querySelectorAll(':scope > button[role="tab"]:not(:disabled)') ?? []), index = buttons.indexOf(event.target);
37
+ if (index < 0)
38
+ return;
39
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
40
+ const nextKey = orientation === "vertical" ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight", previousKey = orientation === "vertical" ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
41
+ const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : event.key === nextKey ? (index + 1) % buttons.length : event.key === previousKey ? (index - 1 + buttons.length) % buttons.length : -1;
42
+ if (next >= 0) {
43
+ event.preventDefault();
44
+ buttons[next].focus();
45
+ if (activationMode === "automatic")
46
+ select(buttons[next].dataset.value);
47
+ }
48
+ }, children: [_jsx(OverlayScrollArea, { orientation: orientation, tabIndex: -1, className: "flood-tabs__scroll", children: _jsx("div", { ref: list, role: "tablist", "aria-label": label, "aria-orientation": orientation, className: "flood-tabs__list", onBlur: event => { if (!event.currentTarget.contains(event.relatedTarget))
49
+ setFocused(undefined); }, children: items.map((item, index) => _jsxs("button", { id: `${id}-tab-${index}`, type: "button", role: "tab", "aria-selected": item.value === selected, "aria-controls": `${id}-panel-${index}`, "data-value": item.value, disabled: disabled || item.disabled, tabIndex: item.value === tabStop ? 0 : -1, "aria-label": iconOnly ? item.label : undefined, className: `flood-button flood-button--ghost flood-button--${controlSize} flood-tab${iconOnly ? " flood-icon-button" : ""}`, onFocus: () => setFocused(item.value), onClick: () => select(item.value), children: [item.icon && _jsx("span", { className: `flood-icon flood-icon--${controlSize}`, "aria-hidden": "true", children: item.icon }), !iconOnly && item.label] }, item.value)) }) }), items.map((item, index) => _jsx("div", { id: `${id}-panel-${index}`, role: "tabpanel", "aria-labelledby": `${id}-tab-${index}`, tabIndex: 0, hidden: item.value !== selected, className: "flood-tabs__panel", onFocusCapture: () => { focusedPanel.current = item.value; }, onBlurCapture: event => { if (!event.currentTarget.hidden)
50
+ focusedPanel.current = undefined; }, children: item.content }, item.value))] });
51
+ });
52
+ export const BreadcrumbNavigation = forwardRef(function BreadcrumbNavigation({ items, label = "Breadcrumb", separator = "/", className, ...props }, ref) {
53
+ return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: ["flood-breadcrumb", className].filter(Boolean).join(" "), children: _jsx("ol", { children: items.map((item, index) => _jsxs("li", { children: [index > 0 && _jsx("span", { className: "flood-breadcrumb__separator", "aria-hidden": "true", children: separator }), item.href !== undefined ? _jsx("a", { className: "flood-link", href: item.href, "aria-current": index === items.length - 1 ? "page" : undefined, children: item.label }) : _jsx("span", { "aria-current": index === items.length - 1 ? "page" : undefined, children: item.label })] }, index)) }) });
54
+ });
55
+ export const PageNavigation = forwardRef(function PageNavigation({ page, pageCount, onPageChange, getHref, siblingCount = 1, controlSize = "sm", disabled = false, label = "Pagination", previousLabel = "Previous page", nextLabel = "Next page", pageLabel = page => `Page ${page}`, className, ...props }, ref) {
56
+ const pages = pageWindow(page, pageCount, siblingCount);
57
+ const control = (target, text, name, unavailable = false) => {
58
+ const inactive = disabled || unavailable, current = target === page;
59
+ const shared = { className: `flood-button flood-button--subtle flood-button--${controlSize} flood-page`, "aria-label": name, "aria-current": current && !unavailable ? "page" : undefined };
60
+ if (getHref)
61
+ return inactive ? _jsx("span", { ...shared, role: "link", "aria-disabled": "true", children: text }) : _jsx("a", { ...shared, href: getHref(target), onClick: event => { if (!event.defaultPrevented && event.button === 0 && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey && !current)
62
+ onPageChange?.(target); }, children: text });
63
+ return _jsx("button", { ...shared, type: "button", disabled: inactive, onClick: () => { if (!current)
64
+ onPageChange?.(target); }, children: text });
65
+ };
66
+ const arrow = (previous) => _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: previous ? "m15 5-7 7 7 7" : "m9 5 7 7-7 7" }) });
67
+ return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: ["flood-pagination", className].filter(Boolean).join(" "), children: _jsxs("ul", { children: [_jsx("li", { children: control(page - 1, arrow(true), previousLabel, page === 1) }), pages.map(item => _jsx("li", { "aria-hidden": typeof item === "string" ? true : undefined, children: typeof item === "number" ? control(item, String(item), pageLabel(item)) : _jsx("span", { className: "flood-page-gap", children: "\u2026" }) }, item)), _jsx("li", { children: control(page + 1, arrow(false), nextLabel, page === pageCount) })] }) });
68
+ });