@flood-md/react 0.2.2 → 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 +453 -3
- 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 +272 -7
- package/dist/index.d.ts +231 -3
- package/dist/index.js +174 -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/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Original React 18.3/19 components for TypeScript and Next.js App Router. Native
|
|
4
4
|
HTML semantics and refs, no third-party component library or Tailwind dependency.
|
|
5
5
|
|
|
6
|
-
This package documents the **0.2.
|
|
7
|
-
|
|
6
|
+
This package documents the **0.2.3** API. Keep Flood package versions aligned and follow the guide matching
|
|
7
|
+
your installed declarations; source changes do not modify an existing npm release.
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
10
|
import "@flood-md/react/styles.css";
|
|
@@ -30,10 +30,20 @@ included. Legacy controls outside FloodRoot keep their earlier theme behavior.
|
|
|
30
30
|
|
|
31
31
|
Exports: FloodRoot, Text, Heading, Stack, Inline, Surface, Divider, IconSlot,
|
|
32
32
|
IconButton, CodeSnippet, Button, Input, Textarea, Checkbox, Radio, Switch, Field and Link,
|
|
33
|
-
plus TypeScript prop types.
|
|
33
|
+
plus TypeScript prop types. Version 0.2.3 also exports Label, Fieldset, RadioGroup, CheckboxGroup, ButtonGroup, InputGroup, Toggle, ToggleGroup, SegmentedControl, Collapsible, Accordion, Tooltip, Popover, Dialog, Tabs, Breadcrumb, Pagination, Select
|
|
34
|
+
and ScrollArea. Button variants are solid/subtle/outline/ghost;
|
|
34
35
|
sizes are 2xs/xs/sm/md/lg/xl/2xl. Prefer subtle for standalone secondary actions;
|
|
35
36
|
ghost belongs in a clear parent context. Loading disables activation.
|
|
36
37
|
|
|
38
|
+
Version 0.2.3 adds LinkProps with `variant="text" | "solid" | "subtle" |
|
|
39
|
+
"outline" | "ghost"` and the same seven `size` values as Button. The default
|
|
40
|
+
text link keeps its existing appearance and inherits text size; size affects
|
|
41
|
+
only button variants. These variants reuse Button's geometry, tokens and states
|
|
42
|
+
while retaining a real anchor, href, ref, target and browser modifier navigation.
|
|
43
|
+
Use Button for actions; do not add role=button or nest a Button in a Link.
|
|
44
|
+
There is no disabled/loading navigation API. This compatible opt-in extension
|
|
45
|
+
is available in 0.2.3.
|
|
46
|
+
|
|
37
47
|
Heading adds `size="display"` for large marketing headings: 5.5rem / 1.12,
|
|
38
48
|
weight 500, tracking -.04em. Existing heading sizes retain their metrics.
|
|
39
49
|
Text adds `size="lead"` for introductory copy: 1.375rem / 1.45, weight 400,
|
|
@@ -44,11 +54,54 @@ font stack; the host owns copy actions and status messages. Both additions are
|
|
|
44
54
|
compatible extensions in 0.2.2; they do not replace existing defaults.
|
|
45
55
|
|
|
46
56
|
Input/Textarea controlSize is sm/md/lg/xl; native Input size is preserved.
|
|
57
|
+
Version 0.2.3 adds `status="error" | "warning" | "success" | "info"` to Input,
|
|
58
|
+
Textarea, Select and Field. Use Field's `message` for a description associated
|
|
59
|
+
with the control; spreading its callback props forwards status too. Existing
|
|
60
|
+
`error`, `invalid` or affirmative `aria-invalid` override positive statuses.
|
|
61
|
+
Only error sets aria-invalid; warning, success and info do not reject a value.
|
|
62
|
+
These visual/ARIA states do not replace native constraints or host validation.
|
|
63
|
+
|
|
64
|
+
Input/Textarea `readOnly` preserves focus, selection, copying and form submission
|
|
65
|
+
while preventing edits. Inside dark foundations it uses a canvas background and
|
|
66
|
+
quiet dashed perimeter at rest; focus retains the solid 2px accent. It differs
|
|
67
|
+
from disabled, which prevents ordinary focus and omits the value from submission.
|
|
68
|
+
Native HTML readOnly applies to text-like input types and Textarea; it is not a
|
|
69
|
+
Select or checkbox state. Explain why a value is read only in the surrounding UI.
|
|
70
|
+
|
|
71
|
+
Dark fields use one quiet uniform `color.field.border` perimeter; focus replaces it with a 2px
|
|
72
|
+
accent without a gap or layout shift. Textarea scrolls its content without a
|
|
73
|
+
native resize grip by default; hosts can opt into resizing through the native
|
|
74
|
+
style prop, for example `style={{ resize: "vertical" }}`.
|
|
47
75
|
Field uses `{props => <Input {...props} />}`. Its required prop forwards native
|
|
48
76
|
required and displays a marker; describedBy merges external IDs with hint/error
|
|
49
77
|
associations. Checkbox/Radio/Switch require a visible label. IconSlot accepts
|
|
50
78
|
host SVG artwork without bundling an icon pack.
|
|
51
79
|
|
|
80
|
+
## Select and choice layout
|
|
81
|
+
|
|
82
|
+
Version 0.2.3 adds **Select**. It accepts native select props/ref, options and optgroups,
|
|
83
|
+
plus `invalid` and `controlSize="sm" | "md" | "lg" | "xl"` (md by default).
|
|
84
|
+
Use Field's callback props on Select to associate the label and descriptions.
|
|
85
|
+
Single selection uses a custom Flood trigger/listbox popup with a native select
|
|
86
|
+
bridge for form values and the HTMLSelectElement ref. Native numeric `size` and
|
|
87
|
+
`multiple` retain an inline listbox without a dropdown chevron or OS popup.
|
|
88
|
+
For single selection, arrows/Home/End highlight without committing; Enter,
|
|
89
|
+
Space or Tab commits, and Escape closes without changing the value. Typeahead
|
|
90
|
+
matches option labels. Focus stays on the trigger; `ref.current.focus()` targets
|
|
91
|
+
it while the ref itself remains the native select. Read changes from
|
|
92
|
+
`event.currentTarget.value` and keep required/disabled/form props on Select.
|
|
93
|
+
Use ordinary option/optgroup children with text labels; an option is not an
|
|
94
|
+
arbitrary interactive slot. See the usage guide for reset and controlled values.
|
|
95
|
+
|
|
96
|
+
Inside FloodRoot, Checkbox/Radio use Flood's custom tick/dot appearance on real
|
|
97
|
+
native inputs. Values, checked/defaultChecked, keyboard, forms and refs remain
|
|
98
|
+
native; forced colors restores native indicators. The glyph stays 1.125rem square,
|
|
99
|
+
with a .5rem label gap and centered alignment. Switch also keeps its label close.
|
|
100
|
+
Use `Inline gap="4"` for a 1rem gap between separate choices; Inline's default
|
|
101
|
+
gap="2" is unchanged. Buttons retain intrinsic width; full width is a deliberate
|
|
102
|
+
host opt-in. See [layout rules](../../docs/layout.md) and
|
|
103
|
+
[Select usage](../../docs/components.md#select).
|
|
104
|
+
|
|
52
105
|
Import CSS once in Next app/layout.tsx. Add "use client" to host components
|
|
53
106
|
using state/callbacks or Field's function child. Override semantic --flood-sys-*
|
|
54
107
|
variables on the scope after the import. Surface overlay is visual elevation,
|
|
@@ -57,3 +110,400 @@ not dialog behavior. The focus accent remains configurable and provisional.
|
|
|
57
110
|
See [components](../../docs/components.md), [foundations](../../docs/foundations.md)
|
|
58
111
|
and [Inter provenance](src/fonts/README.md). Verify contrast, focus, keyboard,
|
|
59
112
|
narrow layouts and text scaling in your actual application.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Label and Fieldset
|
|
116
|
+
|
|
117
|
+
Label forwards native label attributes and an HTMLLabelElement ref. Connect its
|
|
118
|
+
htmlFor to a unique control ID, or nest one labelable control. Its required marker
|
|
119
|
+
is decorative; required belongs on the actual control. Field composes this Label.
|
|
120
|
+
|
|
121
|
+
Fieldset forwards native fieldset attributes/ref and takes a required legend.
|
|
122
|
+
It provides hint/message/error/status and merges their IDs with aria-describedby.
|
|
123
|
+
Error wins over advisory feedback. Descriptions belong to the group rather than
|
|
124
|
+
being copied onto every child. Disabled uses native descendant behavior: controls
|
|
125
|
+
in the first legend are exempt. Avoid interactive legend content unless intended.
|
|
126
|
+
Disabled values are omitted from native FormData; nested fieldsets preserve HTML
|
|
127
|
+
rules. Styling removes the browser border but retains the actual fieldset/legend.
|
|
128
|
+
It does not own choice values, implement a selection model or validate children.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<Fieldset legend="Notifications" hint="Choose the updates you want.">
|
|
132
|
+
<Checkbox label="Product updates" name="updates" defaultChecked />
|
|
133
|
+
<Checkbox label="Release notes" name="releases" />
|
|
134
|
+
</Fieldset>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## ButtonGroup and InputGroup
|
|
138
|
+
|
|
139
|
+
ButtonGroup requires a label and direct Button/IconButton children of compatible
|
|
140
|
+
sizes. It joins visible edges with finite field radii and one full separator along the joined edge, supports
|
|
141
|
+
horizontal/vertical orientation and keeps normal Tab/disabled/action behavior.
|
|
142
|
+
It does not implement a toolbar or selection model. Do not wrap actions in extra
|
|
143
|
+
DOM elements; fragments are fine. Avoid overflow clipping around focused actions.
|
|
144
|
+
|
|
145
|
+
InputGroup accepts text-like Input props plus start/end ReactNode adornments.
|
|
146
|
+
The ref, id, name, value, handlers and ARIA attributes target the actual input;
|
|
147
|
+
className/style target the wrapper. dir/lang/hidden apply to the shell as well.
|
|
148
|
+
Use Field's callback or an associated Label. Adornments are visual and are not
|
|
149
|
+
included in the submitted value or used as a replacement for a label.
|
|
150
|
+
Native required, readOnly and disabled remain input states; host-owned addon
|
|
151
|
+
buttons need their own names, callbacks and disabled policy. Direct Flood Button/IconButton addons inherit their height from the group, with
|
|
152
|
+
an equal space-1 inset against the top, bottom and nearest logical edge; their
|
|
153
|
+
finite radius follows the shell radius minus that inset. Their size prop still
|
|
154
|
+
controls text, icon and horizontal padding. Coarse pointers grow the shell to
|
|
155
|
+
preserve the nested action's touch target. Arbitrary addon content can grow it too.
|
|
156
|
+
Input focus uses the shared perimeter, while an addon action keeps its own focus.
|
|
157
|
+
Use scoped host layout for group width. Textarea/Select require other compositions.
|
|
158
|
+
Both exports are available in 0.2.3.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<Field label="Website">
|
|
162
|
+
{control => <InputGroup {...control} start="https://" placeholder="example.com" />}
|
|
163
|
+
</Field>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## RadioGroup and CheckboxGroup
|
|
167
|
+
|
|
168
|
+
Both compose Fieldset and the existing native Radio/Checkbox controls. Supply a
|
|
169
|
+
legend, a name for the inputs and an options array with unique string values,
|
|
170
|
+
visible labels and optional disabled flags. The ref targets HTMLFieldSetElement.
|
|
171
|
+
Orientation is vertical by default; horizontal wraps. Disabled, hint, status,
|
|
172
|
+
message and error follow Fieldset. These exports are available in 0.2.3.
|
|
173
|
+
Choice groups use compact sm-height rows without an additional vertical gap.
|
|
174
|
+
Coarse pointers retain the touch-target minimum. Wrapping labels can grow a row;
|
|
175
|
+
the indicator remains explicitly sized. Horizontal groups keep space-4 between
|
|
176
|
+
columns. Legend/helper spacing belongs to the group rather than each option.
|
|
177
|
+
|
|
178
|
+
RadioGroup uses a string value/defaultValue and onValueChange(string).
|
|
179
|
+
CheckboxGroup uses an array value/defaultValue and onValueChange(string[]).
|
|
180
|
+
Omitting value selects uncontrolled mode; otherwise update value in the host.
|
|
181
|
+
Native form reset restores the latest defaultValue in uncontrolled mode and
|
|
182
|
+
preserves the host's controlled value. A canceled reset does nothing; reset does
|
|
183
|
+
not call onValueChange. Values for missing options remain in state but are not
|
|
184
|
+
submitted. Disabled options are omitted from FormData.
|
|
185
|
+
|
|
186
|
+
Name applies to inputs, not the fieldset. CheckboxGroup submits repeated entries;
|
|
187
|
+
read them with FormData.getAll(name). The form prop associates every input with
|
|
188
|
+
an external form. RadioGroup required uses native single-choice validity.
|
|
189
|
+
CheckboxGroup intentionally has no required/min/max prop: validate the array in
|
|
190
|
+
the host and describe errors through status/message. Empty/fully disabled options
|
|
191
|
+
need a host decision about whether submission is meaningful.
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
<RadioGroup name="visibility" legend="Visibility" defaultValue="private"
|
|
195
|
+
options={[{ value: "private", label: "Private" }, { value: "team", label: "Team" }]} />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## overlay ScrollArea
|
|
199
|
+
|
|
200
|
+
ScrollArea keeps native wheel, touch and keyboard scrolling while drawing inset
|
|
201
|
+
tracks above content. Set height/maxHeight on its viewport through style; choose
|
|
202
|
+
orientation vertical (default), horizontal or both. The HTMLDivElement ref and
|
|
203
|
+
native div props/className/style target that actual scroll viewport. Supply an
|
|
204
|
+
accessible label when it is a standalone scroll region. Tracks do not reserve
|
|
205
|
+
layout width; inner content padding keeps text clear of them by default. Forced colors and unavailable
|
|
206
|
+
enhancement restore native scrollbars rather than hide scrolling access.
|
|
207
|
+
|
|
208
|
+
Rails automatically shorten near rounded corners on the viewport or a clipping
|
|
209
|
+
ancestor, including asymmetric corners. This does not add a gutter. The default
|
|
210
|
+
`contentInset="scrollbar"` protects text and full-width interactive rows: while
|
|
211
|
+
an overlay rail is present, the inner content receives logical end padding for
|
|
212
|
+
that axis (rail size plus two insets). The viewport keeps its full width; row
|
|
213
|
+
backgrounds and text stop before the rail. Explicit `contentInset="none"` preserves
|
|
214
|
+
edge-to-edge content when the host already provides safe padding. The default
|
|
215
|
+
padding can wrap text and change content height. Native
|
|
216
|
+
fallback scrollbars retain their platform behavior.
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
<ScrollArea aria-label="Activity" style={{ maxHeight: "12rem" }}>
|
|
220
|
+
<Stack gap="4">{/* Long activity content */}</Stack>
|
|
221
|
+
</ScrollArea>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
This export is available in 0.2.3. The website
|
|
225
|
+
uses the same component for code, tables and navigation; Select reuses its internal
|
|
226
|
+
scroll viewport. See [layout](../../docs/layout.md) for constrained compositions.
|
|
227
|
+
|
|
228
|
+
InputGroup start/end are independent slots: use short prefixes (`https://`, `+7`,
|
|
229
|
+
`@`, currency), suffixes (`kg`, `%`, `USD`) or named Button/IconButton actions on
|
|
230
|
+
either side. Plain addons use subtle content color; actions retain their action
|
|
231
|
+
color. Include necessary units/format in the accessible label or description.
|
|
232
|
+
Prefixes are not masks and are not prepended to submitted values automatically;
|
|
233
|
+
normalization and complete phone/URL values belong to the host. Do not hide an
|
|
234
|
+
interactive addon from assistive technology. ButtonGroup supports text buttons,
|
|
235
|
+
IconButton or a mixture; every icon-only action needs its own accessible name.
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
## Toggle and ToggleGroup
|
|
239
|
+
|
|
240
|
+
Toggle uses a native button with aria-pressed and type=button. Use defaultPressed
|
|
241
|
+
for local state or pressed/onPressedChange for controlled state. Keep its visible
|
|
242
|
+
or accessible name stable across changes. Native onClick runs first; calling
|
|
243
|
+
preventDefault cancels the state change. Disabled/loading prevent activation.
|
|
244
|
+
iconOnly wraps host artwork in IconSlot; supply aria-label for that action.
|
|
245
|
+
Sizes and variants follow Button. The ref targets HTMLButtonElement.
|
|
246
|
+
|
|
247
|
+
ToggleGroup takes a label and unique-value options with label, optional icon and
|
|
248
|
+
disabled. selectionMode is multiple by default; single permits at most one value
|
|
249
|
+
and allows clearing it. Both modes use string arrays: defaultValue for local
|
|
250
|
+
state, value/onValueChange for host state. Controlled values do not update unless
|
|
251
|
+
the host updates them. Keep the selection mode stable, or remount/reset state
|
|
252
|
+
when changing it. Missing-option values remain in host/local state; trim them in
|
|
253
|
+
the host if needed. Empty options render an empty named group.
|
|
254
|
+
|
|
255
|
+
Tab visits each enabled toggle. Orientation-aware arrows (including RTL), Home
|
|
256
|
+
and End move focus, skip disabled options and do not select. Enter/Space activate.
|
|
257
|
+
The group retains intrinsic action widths rather than breaking words or compressing
|
|
258
|
+
icons. In constrained layouts use an enclosing horizontal ScrollArea, icon-only
|
|
259
|
+
actions or vertical orientation. The ref targets HTMLDivElement. iconOnly requires icons and uses option labels as
|
|
260
|
+
accessible names. Text+icon and text-only options are supported too.
|
|
261
|
+
|
|
262
|
+
These are pressed action buttons, not tabs, radio inputs or switches. No hidden
|
|
263
|
+
inputs, automatic form submission or native form-reset behavior is added. For
|
|
264
|
+
form fields use RadioGroup/CheckboxGroup; for panels use Tabs.
|
|
265
|
+
Uncontrolled default props initialize state only. Both exports are available in 0.2.3.
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
<Toggle defaultPressed onPressedChange={active => console.log(active)}>Bold</Toggle>
|
|
269
|
+
<ToggleGroup label="Text formatting" defaultValue={["bold"]}
|
|
270
|
+
options={[{value: "bold", label: "Bold"}, {value: "italic", label: "Italic"}]} />
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
## SegmentedControl
|
|
275
|
+
|
|
276
|
+
SegmentedControl is a segmented presentation of Flood RadioGroup with native
|
|
277
|
+
radio inputs, a visible label/legend and shared inset surface. Supply label,
|
|
278
|
+
name and unique-value options (string label, optional icon/disabled). Use short
|
|
279
|
+
labels for a small set of modes. Default orientation is horizontal; vertical,
|
|
280
|
+
iconOnly and fullWidth are supported. iconOnly requires each option's icon and
|
|
281
|
+
keeps its label available to assistive technology. fullWidth distributes equal
|
|
282
|
+
width to horizontal segments. controlSize is sm/md/lg/xl; touch targets may grow
|
|
283
|
+
the shared shell. Intrinsic groups retain readable labels rather than compressing
|
|
284
|
+
icons; use a constrained ScrollArea or another orientation if needed.
|
|
285
|
+
|
|
286
|
+
value/onValueChange use a single string; defaultValue initializes local state.
|
|
287
|
+
An absent or unmatched initial value leaves the group unselected. Once selected,
|
|
288
|
+
clicking the current segment cannot clear it; controlled host state can clear it.
|
|
289
|
+
Keep names unique among independent groups in the same form. Native name, form,
|
|
290
|
+
required, disabled fieldsets and reset are preserved through RadioGroup. Reset
|
|
291
|
+
restores the latest defaultValue for local state, preserves controlled host
|
|
292
|
+
values, respects cancellation and does not call onValueChange. Empty/missing
|
|
293
|
+
options are not invented. No automatic fallback to the first option is applied.
|
|
294
|
+
|
|
295
|
+
Tab reaches the selected enabled radio (browser defaults apply if none is
|
|
296
|
+
selected). Arrows focus and select, wrapping and skipping disabled options;
|
|
297
|
+
horizontal left/right follow RTL. Home/End select the first/last enabled item.
|
|
298
|
+
Space selects without clearing. A canceled native onKeyDown skips Flood's
|
|
299
|
+
keyboard enhancement. Enter retains native radio/form behavior. The ref targets
|
|
300
|
+
HTMLFieldSetElement. Hint/message/error/status follow Fieldset.
|
|
301
|
+
|
|
302
|
+
Use SegmentedControl for one value/mode, ToggleGroup for optional pressed actions
|
|
303
|
+
and actual tabs for associated tab panels. This does not implement tab panels.
|
|
304
|
+
Available in 0.2.3. Use matching installed declarations.
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
<SegmentedControl label="Time range" name="period" defaultValue="day"
|
|
308
|
+
options={[{value: "day", label: "Day"}, {value: "week", label: "Week"},
|
|
309
|
+
{value: "month", label: "Month"}]} />
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
## visually hidden external labels
|
|
314
|
+
|
|
315
|
+
Field and Fieldset accept hideLabel (false by default). It visually hides the real
|
|
316
|
+
Label or legend, preserving its association and accessible name, while removing
|
|
317
|
+
the label's layout row and spacing. The required label/legend prop remains required.
|
|
318
|
+
RadioGroup, CheckboxGroup and SegmentedControl inherit hideLabel. Required native
|
|
319
|
+
controls, descriptions, error/status, group names and form behavior are unchanged.
|
|
320
|
+
Keep essential instructions in a visible hint/message when they are needed.
|
|
321
|
+
Use textual label content; do not put interactive elements inside hidden labels.
|
|
322
|
+
|
|
323
|
+
For Input/Textarea/Select/InputGroup apply hideLabel to their surrounding Field,
|
|
324
|
+
not the native control. Input aria-label/aria-labelledby remain available when
|
|
325
|
+
the host deliberately provides its own labeling. Placeholder is not a label.
|
|
326
|
+
ButtonGroup/ToggleGroup already expose their group label as an accessible name;
|
|
327
|
+
this flag does not hide button text or the per-option labels of Checkbox/Radio/
|
|
328
|
+
Switch. Standalone icon actions still need an accessible name.
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
<Field label="Search projects" hideLabel>
|
|
332
|
+
{control => <Input {...control} type="search" />}
|
|
333
|
+
</Field>
|
|
334
|
+
<SegmentedControl label="Time range" hideLabel name="period" defaultValue="day"
|
|
335
|
+
options={[{value:"day",label:"Day"},{value:"week",label:"Week"}]} />
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
This compatible prop addition is available in 0.2.3. It requires Flood CSS.
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
## Collapsible and Accordion
|
|
342
|
+
|
|
343
|
+
Collapsible takes a plain-text title, React children and open/onOpenChange or
|
|
344
|
+
local defaultOpen. Default closed; native Enter/Space activate its named button.
|
|
345
|
+
The trigger owns aria-expanded and aria-controls with stable instance IDs. Its
|
|
346
|
+
optional headingLevel adds a native h2–h6 around the button. Disabled affects the
|
|
347
|
+
trigger only; content inside an open panel remains interactive. controlSize is
|
|
348
|
+
sm/md/lg/xl. Native div props, className/style and HTMLDivElement ref target the
|
|
349
|
+
root; title names the trigger rather than the root's HTML title attribute.
|
|
350
|
+
|
|
351
|
+
Accordion takes an accessible group label and unique-value items with title,
|
|
352
|
+
content and optional disabled. expansionMode is single by default (one or none
|
|
353
|
+
open), or multiple. Both use string-array value/onValueChange or defaultValue.
|
|
354
|
+
The default headingLevel is 3; choose 2–6 to match surrounding headings. Up/Down,
|
|
355
|
+
Home/End focus enabled top-level triggers without changing expansion. Tab reaches
|
|
356
|
+
triggers and open panel controls normally; Enter/Space open/close. Nested accordions
|
|
357
|
+
keep their own keyboard navigation. The ref targets the group div. There is no
|
|
358
|
+
visible group caption to hide; headings and trigger titles remain visible.
|
|
359
|
+
|
|
360
|
+
Defaults initialize local state only. Controlled state needs host updates; keep
|
|
361
|
+
expansionMode stable or reconcile/remount when switching. Duplicate item values
|
|
362
|
+
and multiple expanded values in single mode are rejected. Missing-item values
|
|
363
|
+
remain in state until the host removes them. Disabled does not force panels shut.
|
|
364
|
+
|
|
365
|
+
Both share the same original disclosure anatomy. Panels stay mounted with native
|
|
366
|
+
hidden when closed, preserving local content state while removing hidden content
|
|
367
|
+
from focus/navigation. Existing form values and constraints remain native even
|
|
368
|
+
when hidden; manage inactive form fields in the host. If programmatic closing
|
|
369
|
+
hides focused content, focus returns to the enabled trigger unless already moved
|
|
370
|
+
elsewhere. Hosts own focus if the trigger is disabled or its whole item is removed.
|
|
371
|
+
There is no built-in Escape-to-close, form reset or persistence. Panels change
|
|
372
|
+
visibility immediately; only the chevron animates, with reduced motion disabling
|
|
373
|
+
that transition. No extra landmark regions are created per panel.
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
<Collapsible title="Advanced settings"><Input aria-label="Project prefix" /></Collapsible>
|
|
377
|
+
<Accordion label="Questions" headingLevel={2} items={[
|
|
378
|
+
{value:"install",title:"How do I install Flood?",content:"Install the packages."},
|
|
379
|
+
]} />
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Available in 0.2.3. Import Flood CSS and use FloodRoot for dark foundations.
|
|
383
|
+
|
|
384
|
+
Disclosure corner geometry: Accordion headers have square internal seams. Only
|
|
385
|
+
the first header has upper shell corners, and only a closed last header has lower
|
|
386
|
+
shell corners. An open Collapsible header has square lower corners where its
|
|
387
|
+
panel begins. Hover backgrounds follow that same shape without clipping focus.
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
## Tooltip, Popover and Dialog
|
|
391
|
+
|
|
392
|
+
All three use a trigger render function: `trigger={props => <Button {...props}>Open</Button>}`.
|
|
393
|
+
Forward every supplied prop and ref to one actual Button/native button in the same
|
|
394
|
+
document. Keep it mounted and visible while its overlay is open. Compose any host
|
|
395
|
+
handlers with the supplied ones, rather than replacing them; merge existing
|
|
396
|
+
aria-describedby with Tooltip's supplied value if adding another description.
|
|
397
|
+
These client interactions require modern native Popover API / dialog showModal
|
|
398
|
+
support. No older-browser polyfill is bundled. Native top-layer placement keeps
|
|
399
|
+
the panel in its original DOM/theme scope and avoids overflow clipping.
|
|
400
|
+
|
|
401
|
+
Tooltip takes text-only content, side (top default), align (center default),
|
|
402
|
+
delay in milliseconds (300 default) and disabled. Keyboard focus shows immediately;
|
|
403
|
+
mouse/pen hover uses the delay. Moving onto the tooltip keeps it visible. Escape
|
|
404
|
+
dismisses until a new focus/hover interaction. It never moves focus; content is
|
|
405
|
+
associated via aria-describedby. disabled suppresses the tooltip, not its trigger
|
|
406
|
+
action. Touch hover is not relied on; essential instructions belong in visible
|
|
407
|
+
content. Use Popover for interactive content; tooltip text is not a button's name.
|
|
408
|
+
|
|
409
|
+
Popover takes a visible title, children, trigger, side (bottom), align (start),
|
|
410
|
+
open/onOpenChange or local defaultOpen. It is a nonmodal dialog: focus enters its
|
|
411
|
+
content, but Tab is not trapped. Escape, outside pointer and focus leaving request
|
|
412
|
+
closing. The upper-end close action is an IconButton with a decorative cross; closeLabel
|
|
413
|
+
configures its accessible name. initialFocusRef can
|
|
414
|
+
choose an enabled element inside the panel; otherwise content gets initial focus.
|
|
415
|
+
Disabled prevents trigger activation. The div ref/className/style/native props
|
|
416
|
+
target the actual panel. Side flips and coordinates clamp near viewport edges;
|
|
417
|
+
scroll, viewport resize and layout changes reposition it. Native hidePopover requests state changes and a controlled owner can refuse
|
|
418
|
+
closure. Engine owns top/left,
|
|
419
|
+
position and available-size variables. Logical start/end alignment follows RTL.
|
|
420
|
+
|
|
421
|
+
Dialog uses native showModal for backdrop, inert background and nested modal
|
|
422
|
+
behavior. It takes title, optional plain description, children, trigger and the
|
|
423
|
+
same controlled/local state, disabled trigger, closeLabel and initialFocusRef.
|
|
424
|
+
Its upper-end close action also uses IconButton.
|
|
425
|
+
Escape requests closure of the topmost layer. closeOnOutsideClick defaults true;
|
|
426
|
+
turning it off preserves Escape and the explicit close action. Tab/Shift+Tab cycle
|
|
427
|
+
inside the modal, including nested nonmodal content; background scrolling is
|
|
428
|
+
locked without changing layout width and restored when the last modal closes.
|
|
429
|
+
Root props/style and HTMLDialogElement ref target the actual dialog. Native
|
|
430
|
+
close/method=dialog requests state changes; a controlled owner can refuse them.
|
|
431
|
+
|
|
432
|
+
Popover/Dialog children mount while open and unmount when closed. Keep persistent
|
|
433
|
+
form data in the host. Defaults initialize once; controlled owners must update
|
|
434
|
+
open. On closing, focus returns to the connected enabled trigger when appropriate;
|
|
435
|
+
focus deliberately moved elsewhere is preserved for nonmodal dismissal. Hosts own
|
|
436
|
+
fallback focus if a trigger disappears or is disabled. Choose safe initial focus
|
|
437
|
+
for consequential actions. Complex dialog content need not be flattened into a
|
|
438
|
+
single description; use its visible structure. No menu/listbox semantics are
|
|
439
|
+
implied by Popover. No opening/closing animation is applied in this first slice.
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
<Tooltip content="Additional context" trigger={p => <Button {...p}>Help</Button>} />
|
|
443
|
+
<Popover title="Settings" trigger={p => <Button {...p}>Settings</Button>}>
|
|
444
|
+
<Input aria-label="Project name" />
|
|
445
|
+
</Popover>
|
|
446
|
+
<Dialog title="Create a project" trigger={p => <Button {...p}>Open dialog</Button>}>
|
|
447
|
+
<Field label="Project name">{p => <Input {...p} />}</Field>
|
|
448
|
+
</Dialog>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Available in 0.2.3. Use Flood CSS and FloodRoot. These are original native
|
|
452
|
+
implementations, without a third-party component/positioning library.
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
## Tabs, Breadcrumb and Pagination
|
|
456
|
+
|
|
457
|
+
Tabs connects unique-value items (label, content, optional icon/disabled) to a
|
|
458
|
+
named tablist and retained panels. Use defaultValue for local state or
|
|
459
|
+
value/onValueChange for controlled state. Missing, invalid or individually disabled
|
|
460
|
+
selection derives the first available item without a change callback. Reconcile
|
|
461
|
+
host values when changing items. Global disabled disables tab controls while
|
|
462
|
+
leaving the selected panel visible. iconOnly requires all icons and keeps labels
|
|
463
|
+
as accessible names. Native div props/ref target the root.
|
|
464
|
+
|
|
465
|
+
One enabled tab is in the Tab sequence. Orientation-aware arrows, Home/End move
|
|
466
|
+
focus, wrapping and skipping disabled tabs; horizontal start/end follows RTL.
|
|
467
|
+
activationMode is automatic (arrow navigation activates) or manual (Enter/Space
|
|
468
|
+
activate). Manual focus resets to active tab on leaving the list. Panels are
|
|
469
|
+
focusable, labelled by their tabs and retained with hidden when inactive, preserving
|
|
470
|
+
state and native form constraints. Hosts own inactive-field validation. External
|
|
471
|
+
selection changes return focus from a hidden panel to the selected tab if needed.
|
|
472
|
+
Horizontal overflow uses Flood ScrollArea; vertical layout wraps its tab labels.
|
|
473
|
+
Sizes are controlSize sm/md/lg/xl. There is no tab deletion/loading/fetch model.
|
|
474
|
+
The dark recipe is one contained surface: the active tab shares the panel's
|
|
475
|
+
background, while inactive tabs sit on the recessed navigation strip. Both
|
|
476
|
+
orientations share a single outer perimeter, without a separate panel border or
|
|
477
|
+
selection underline. Keep panel content free of redundant enclosing surfaces.
|
|
478
|
+
|
|
479
|
+
Breadcrumb uses an accessible nav label (Breadcrumb by default), an ordered items
|
|
480
|
+
array of label/optional href, and a decorative separator (/ by default). The last
|
|
481
|
+
item always has aria-current=page, as a native link or text. Parent links preserve
|
|
482
|
+
browser navigation; host routes and client interception remain host-owned. It
|
|
483
|
+
wraps and supports long labels. Native nav props/ref target HTMLElement.
|
|
484
|
+
|
|
485
|
+
Pagination is controlled: page/pageCount are required positive safe integers,
|
|
486
|
+
1 <= page <= pageCount; omit the component for empty datasets. Host must reconcile
|
|
487
|
+
page when count changes. onPageChange requests local updates; getHref renders
|
|
488
|
+
native route links instead of buttons. In link mode onPageChange is notification,
|
|
489
|
+
not navigation cancellation; modifier clicks retain native behavior. Host routing
|
|
490
|
+
can intercept the bubbling nav click. Current page uses aria-current, and boundary
|
|
491
|
+
Previous/Next controls have no active href or disabled native buttons. disabled
|
|
492
|
+
makes every control noninteractive. No callback fires for the current page.
|
|
493
|
+
|
|
494
|
+
siblingCount 0/1/2 gives at most 5/7/9 numeric/ellipsis slots plus Previous/Next,
|
|
495
|
+
independent of pageCount magnitude. Ellipses are decorative. controlSize matches
|
|
496
|
+
field sizes; wrapping prevents narrow-width overflow and touch controls retain
|
|
497
|
+
44px targets. label, previousLabel, nextLabel and pageLabel localize naming.
|
|
498
|
+
The native nav ref is HTMLElement. This component does not fetch data or own URLs.
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
<Tabs label="Project sections" items={[
|
|
502
|
+
{value:"overview",label:"Overview",content:"Project context."},
|
|
503
|
+
{value:"activity",label:"Activity",content:"Recent updates."},
|
|
504
|
+
]} />
|
|
505
|
+
<Breadcrumb items={[{label:"Projects",href:"/projects"},{label:"Flood"}]} />
|
|
506
|
+
<Pagination page={1} pageCount={20} getHref={page => `?page=${page}`} />
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
All three are available in 0.2.3. Use Flood styles and FloodRoot for the neutral dark profile.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Checkbox, Fieldset, FieldsetProps, ChoiceOption } from "./index.js";
|
|
2
|
+
interface Props extends Omit<FieldsetProps, "children" | "defaultValue"> {
|
|
3
|
+
name: string;
|
|
4
|
+
options: readonly ChoiceOption[];
|
|
5
|
+
kind: "radio" | "checkbox";
|
|
6
|
+
selected?: readonly string[];
|
|
7
|
+
initial: readonly string[];
|
|
8
|
+
onSelectionChange?: (value: string[]) => void;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
orientation: "vertical" | "horizontal";
|
|
11
|
+
fieldsetComponent: typeof Fieldset;
|
|
12
|
+
choiceComponent: typeof Checkbox;
|
|
13
|
+
}
|
|
14
|
+
/** Private selection model; native inputs own keyboard, focus, forms and validity. */
|
|
15
|
+
export declare const ChoiceGroup: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<HTMLFieldSetElement>>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useLayoutEffect, useImperativeHandle, useRef, useState } from "react";
|
|
3
|
+
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4
|
+
/** Private selection model; native inputs own keyboard, focus, forms and validity. */
|
|
5
|
+
export const ChoiceGroup = forwardRef(function ChoiceGroup({ kind, name, options, selected, initial, onSelectionChange, required, orientation, fieldsetComponent: Group, choiceComponent: Choice, legend, ...props }, forwardedRef) {
|
|
6
|
+
if (new Set(options.map(option => option.value)).size !== options.length)
|
|
7
|
+
throw new Error("Flood choice group option values must be unique");
|
|
8
|
+
const root = useRef(null);
|
|
9
|
+
const inputs = useRef(new Map());
|
|
10
|
+
const [local, setLocal] = useState(() => [...new Set(initial)]);
|
|
11
|
+
const controlled = selected !== undefined;
|
|
12
|
+
const values = controlled ? selected : local;
|
|
13
|
+
const latest = useRef({ controlled, selected, initial });
|
|
14
|
+
useBrowserLayoutEffect(() => { latest.current = { controlled, selected, initial }; });
|
|
15
|
+
useImperativeHandle(forwardedRef, () => root.current, []);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const fieldset = root.current;
|
|
18
|
+
if (!fieldset)
|
|
19
|
+
return;
|
|
20
|
+
const eventRoot = fieldset.getRootNode();
|
|
21
|
+
const timers = new Set();
|
|
22
|
+
const reset = (event) => {
|
|
23
|
+
if (!fieldset.form || event.target !== fieldset.form)
|
|
24
|
+
return;
|
|
25
|
+
// Wait for the whole dispatch/default action, including React's delegated
|
|
26
|
+
// reset handler. A microtask can run between native event listeners.
|
|
27
|
+
const timer = setTimeout(() => {
|
|
28
|
+
timers.delete(timer);
|
|
29
|
+
if (event.defaultPrevented || root.current !== fieldset || event.target !== fieldset.form)
|
|
30
|
+
return;
|
|
31
|
+
const current = latest.current;
|
|
32
|
+
const next = [...new Set(current.controlled ? current.selected : current.initial)];
|
|
33
|
+
if (!current.controlled)
|
|
34
|
+
setLocal(next);
|
|
35
|
+
// Controlled groups must also undo native DOM reset if the host keeps its value.
|
|
36
|
+
inputs.current.forEach(input => { input.checked = next.includes(input.value); });
|
|
37
|
+
}, 0);
|
|
38
|
+
timers.add(timer);
|
|
39
|
+
};
|
|
40
|
+
eventRoot.addEventListener("reset", reset, true);
|
|
41
|
+
return () => { eventRoot.removeEventListener("reset", reset, true); timers.forEach(clearTimeout); };
|
|
42
|
+
}, []);
|
|
43
|
+
return _jsx(Group, { ...props, ref: root, legend: required ? _jsxs(_Fragment, { children: [legend, _jsx("span", { "aria-hidden": "true", children: " *" })] }) : legend, role: kind === "radio" ? "radiogroup" : "group", "aria-required": kind === "radio" ? required || undefined : undefined, "aria-orientation": kind === "radio" ? orientation : undefined, children: _jsxs("div", { className: "flood-choice-group", "data-orientation": orientation, children: [options.length === 0 && _jsx("span", { className: "flood-hint", children: "No options available." }), options.map(option => _jsx(Choice, { ref: input => { if (input)
|
|
44
|
+
inputs.current.set(option.value, input);
|
|
45
|
+
else
|
|
46
|
+
inputs.current.delete(option.value); }, name: name, value: option.value, label: option.label, form: props.form, disabled: option.disabled, required: kind === "radio" ? required : undefined, checked: values.includes(option.value), onChange: event => {
|
|
47
|
+
const next = kind === "radio" ? [option.value] : [...new Set(event.currentTarget.checked ? [...values, option.value] : values.filter(value => value !== option.value))];
|
|
48
|
+
if (!controlled)
|
|
49
|
+
setLocal(next);
|
|
50
|
+
onSelectionChange?.([...next]);
|
|
51
|
+
} }, option.value))] }) });
|
|
52
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
3
|
+
title: string;
|
|
4
|
+
expanded: boolean;
|
|
5
|
+
onExpandedChange: (open: boolean) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
controlSize: "sm" | "md" | "lg" | "xl";
|
|
8
|
+
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
|
9
|
+
}
|
|
10
|
+
/** Shared original disclosure anatomy. The closed panel stays mounted but hidden. */
|
|
11
|
+
export declare const Disclosure: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<HTMLDivElement>>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createElement, forwardRef, useEffect, useId, useLayoutEffect, useRef, useImperativeHandle } from "react";
|
|
3
|
+
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4
|
+
/** Shared original disclosure anatomy. The closed panel stays mounted but hidden. */
|
|
5
|
+
export const Disclosure = forwardRef(function Disclosure({ title, expanded, onExpandedChange, disabled, controlSize, headingLevel, className, children, id: explicitId, ...props }, ref) {
|
|
6
|
+
const generatedId = useId(), id = explicitId ?? `flood-disclosure-${generatedId}`;
|
|
7
|
+
const root = useRef(null), panel = useRef(null), trigger = useRef(null);
|
|
8
|
+
const focusedInside = useRef(false);
|
|
9
|
+
useImperativeHandle(ref, () => root.current, []);
|
|
10
|
+
useBrowserLayoutEffect(() => {
|
|
11
|
+
const active = panel.current?.ownerDocument.activeElement;
|
|
12
|
+
if (!expanded && focusedInside.current && !disabled && (panel.current?.contains(active ?? null) || active === panel.current?.ownerDocument.body))
|
|
13
|
+
trigger.current?.focus();
|
|
14
|
+
if (!expanded)
|
|
15
|
+
focusedInside.current = false;
|
|
16
|
+
}, [expanded, disabled]);
|
|
17
|
+
const button = _jsxs("button", { ref: trigger, id: `${id}-trigger`, type: "button", disabled: disabled, "aria-expanded": expanded, "aria-controls": `${id}-panel`, className: `flood-button flood-button--ghost flood-button--${controlSize} flood-disclosure__trigger`, onClick: () => onExpandedChange(!expanded), children: [_jsx("span", { className: "flood-disclosure__title", children: title }), _jsx("span", { className: "flood-disclosure__indicator", "aria-hidden": "true" })] });
|
|
18
|
+
return _jsxs("div", { ...props, id: id, ref: root, "data-open": expanded, className: ["flood-disclosure", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), children: [createElement(headingLevel ? `h${headingLevel}` : "div", { className: "flood-disclosure__heading" }, button), _jsx("div", { ref: panel, id: `${id}-panel`, className: "flood-disclosure__panel", hidden: !expanded, onFocusCapture: () => { focusedInside.current = true; }, onBlurCapture: event => { if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget))
|
|
19
|
+
focusedInside.current = false; }, children: children })] });
|
|
20
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AriaAttributes, ReactNode } from "react";
|
|
2
|
+
import type { FieldStatus } from "./index.js";
|
|
3
|
+
/** Existing invalid semantics always take precedence over a positive status. */
|
|
4
|
+
export declare function resolveFieldStatus(status?: FieldStatus, invalid?: boolean, ariaInvalid?: AriaAttributes["aria-invalid"]): FieldStatus | undefined;
|
|
5
|
+
export declare const hasFieldContent: (value: ReactNode) => boolean;
|
|
6
|
+
export declare function mergeDescriptions(...values: Array<string | false | undefined>): string | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Existing invalid semantics always take precedence over a positive status. */
|
|
2
|
+
export function resolveFieldStatus(status, invalid, ariaInvalid) {
|
|
3
|
+
return invalid || (ariaInvalid !== undefined && ariaInvalid !== false && ariaInvalid !== "false") ? "error" : status;
|
|
4
|
+
}
|
|
5
|
+
export const hasFieldContent = (value) => value !== null && value !== undefined && value !== false && value !== true && value !== "";
|
|
6
|
+
export function mergeDescriptions(...values) {
|
|
7
|
+
return [...new Set(values.filter(Boolean).join(" ").split(/\s+/).filter(Boolean))].join(" ") || undefined;
|
|
8
|
+
}
|