@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/README.md +463 -4
- package/dist/choice-group.d.ts +16 -0
- package/dist/choice-group.js +52 -0
- package/dist/disclosure.d.ts +12 -0
- package/dist/disclosure.js +20 -0
- package/dist/field-state.d.ts +6 -0
- package/dist/field-state.js +8 -0
- package/dist/foundations.css +309 -6
- package/dist/index.d.ts +239 -5
- package/dist/index.js +178 -15
- package/dist/navigation.d.ts +4 -0
- package/dist/navigation.js +68 -0
- package/dist/overlay-runtime.d.ts +38 -0
- package/dist/overlay-runtime.js +104 -0
- package/dist/overlays.d.ts +7 -0
- package/dist/overlays.js +170 -0
- package/dist/page-window.d.ts +1 -0
- package/dist/page-window.js +15 -0
- package/dist/scroll-area.d.ts +3 -0
- package/dist/scroll-area.js +220 -0
- package/dist/select.d.ts +3 -0
- package/dist/select.js +284 -0
- package/dist/styles.css +2 -0
- package/package.json +2 -2
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
|
|
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
|
|
9
|
-
|
|
10
|
-
});
|
|
11
|
-
export const
|
|
12
|
-
|
|
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
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
}),
|
|
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
|
+
});
|