@flood-md/react 0.2.3 → 0.2.4
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 +308 -3
- package/dist/avatar.d.ts +5 -0
- package/dist/avatar.js +8 -0
- package/dist/checkbox.d.ts +4 -0
- package/dist/checkbox.js +15 -0
- package/dist/data-display.d.ts +3 -0
- package/dist/data-display.js +11 -0
- package/dist/disclosure.js +2 -1
- package/dist/feedback.d.ts +8 -0
- package/dist/feedback.js +29 -0
- package/dist/focusable-hidden.d.ts +3 -0
- package/dist/focusable-hidden.js +99 -0
- package/dist/fonts/InterVariable-Italic.woff2 +0 -0
- package/dist/fonts/README.md +9 -5
- package/dist/foundations.css +196 -12
- package/dist/index.d.ts +191 -2
- package/dist/index.js +145 -36
- package/dist/menu.d.ts +2 -0
- package/dist/menu.js +130 -0
- package/dist/navigation.js +8 -8
- package/dist/overlay-runtime.js +10 -4
- package/dist/overlays.js +12 -3
- package/dist/scroll-area.js +17 -2
- package/dist/select.js +96 -47
- package/dist/styles.css +9 -0
- package/dist/toolbar.d.ts +2 -0
- package/dist/toolbar.js +80 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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.
|
|
6
|
+
This package documents the **0.2.4** API. Keep Flood package versions aligned and follow the guide matching
|
|
7
7
|
your installed declarations; source changes do not modify an existing npm release.
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
@@ -314,11 +314,16 @@ Available in 0.2.3. Use matching installed declarations.
|
|
|
314
314
|
|
|
315
315
|
Field and Fieldset accept hideLabel (false by default). It visually hides the real
|
|
316
316
|
Label or legend, preserving its association and accessible name, while removing
|
|
317
|
-
the label's layout row and spacing
|
|
317
|
+
the label's layout row and spacing while it has no focused descendants. The
|
|
318
|
+
required label/legend prop remains required.
|
|
318
319
|
RadioGroup, CheckboxGroup and SegmentedControl inherit hideLabel. Required native
|
|
319
320
|
controls, descriptions, error/status, group names and form behavior are unchanged.
|
|
320
321
|
Keep essential instructions in a visible hint/message when they are needed.
|
|
321
|
-
|
|
322
|
+
Prefer textual label content. Hidden labels/legends reveal while a descendant
|
|
323
|
+
has focus, then hide again when focus leaves. A supporting link in a Field label
|
|
324
|
+
or a control in a Fieldset first legend therefore retains visible keyboard focus;
|
|
325
|
+
React child state, refs and native first-legend disabled exceptions are preserved.
|
|
326
|
+
Do not nest another labelable control (button/input) inside a Field label.
|
|
322
327
|
|
|
323
328
|
For Input/Textarea/Select/InputGroup apply hideLabel to their surrounding Field,
|
|
324
329
|
not the native control. Input aria-label/aria-labelledby remain available when
|
|
@@ -507,3 +512,303 @@ The native nav ref is HTMLElement. This component does not fetch data or own URL
|
|
|
507
512
|
```
|
|
508
513
|
|
|
509
514
|
All three are available in 0.2.3. Use Flood styles and FloodRoot for the neutral dark profile.
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
## Badge, Spinner, Skeleton and Avatar · 0.2.4
|
|
518
|
+
|
|
519
|
+
These original primitives are compatible additions in the 0.2.4.
|
|
520
|
+
Import Flood CSS and use FloodRoot. Verify registry publication before installing.
|
|
521
|
+
Native span props and refs target their roots; className/style remain host overrides.
|
|
522
|
+
|
|
523
|
+
Badge is a noninteractive span: size sm/md/lg, tone neutral/accent/error/warning/
|
|
524
|
+
success/info and variant subtle/outline. Its text carries the meaning; color is
|
|
525
|
+
supplementary. It has no selection, dismissal, click-action or live-region model.
|
|
526
|
+
Use a separate named action when needed instead of treating it as a button.
|
|
527
|
+
|
|
528
|
+
Spinner defaults to size md and label="Loading". Localize label for standalone
|
|
529
|
+
status. decorative hides it and its label from assistive technology. Button
|
|
530
|
+
loading uses the same Spinner decoratively, inherits its icon size and retains
|
|
531
|
+
its accessible button name and aria-busy. Announcements belong at the appropriate
|
|
532
|
+
host level; do not create one live region for each decorative loading graphic.
|
|
533
|
+
Reduced motion stops rotation. This indicator does not measure completion.
|
|
534
|
+
|
|
535
|
+
Skeleton is always aria-hidden. shape is text/rounded/circle; width and height
|
|
536
|
+
accept CSS dimensions (numbers are React CSS pixels), and style overrides them.
|
|
537
|
+
Prefer relative dimensions. For a circle supply equal dimensions. animate defaults
|
|
538
|
+
true; reduced motion and forced colors stop animation. Match the final geometry;
|
|
539
|
+
Skeleton does not fetch, detect loading or announce changes. The host owns busy
|
|
540
|
+
state, loading text and replacement with real content. Do not add interactive children.
|
|
541
|
+
|
|
542
|
+
Avatar requires name, accepts src, fallback text, size sm/md/lg, shape circle/
|
|
543
|
+
rounded and decorative. The root exposes one named image; the internal image has
|
|
544
|
+
empty alt and is hidden from assistive technology. Use decorative beside an
|
|
545
|
+
already visible equivalent name. Defaults use the first letters of the first two
|
|
546
|
+
whitespace-separated name parts; override fallback for your locale or naming rules.
|
|
547
|
+
The image overlays the fallback only after successful load; an error retains the
|
|
548
|
+
fallback. A new src remounts the image load state, including returning to an earlier
|
|
549
|
+
source. The root stays the same size. This API does not upload or crop images.
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
<Badge tone="success">Active</Badge>
|
|
553
|
+
<Spinner label="Loading workspace" />
|
|
554
|
+
<Skeleton width="12rem" />
|
|
555
|
+
<Avatar name="Alex Morgan" src="/team/alex.webp" />
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
## Progress, Meter and Alert · 0.2.4
|
|
560
|
+
|
|
561
|
+
Progress describes task completion. label is required; max defaults to 100 and
|
|
562
|
+
value is a finite number in 0..max. Omit value for indeterminate progress; its
|
|
563
|
+
progressbar then omits aria-valuenow. Meter describes a measurement in a bounded
|
|
564
|
+
range, such as storage or battery. It requires value, with min=0 and max=100 by
|
|
565
|
+
default. Require finite min < max, a finite range span, and min <= value <= max.
|
|
566
|
+
Invalid ranges/values throw RangeError; they are not silently clamped.
|
|
567
|
+
|
|
568
|
+
Both support size sm/md/lg, semantic tone, hideLabel and valueText. Tone defaults
|
|
569
|
+
to accent for Progress and neutral for Meter. valueText supplies localized,
|
|
570
|
+
human-readable units; it also appears beside the caption. hideLabel visually
|
|
571
|
+
hides the caption while retaining the accessible name and valueText. The bar's
|
|
572
|
+
role is progressbar or meter; neither becomes a live region. Native div props,
|
|
573
|
+
className/style and HTMLDivElement ref target the wrapper; the internal bar owns
|
|
574
|
+
its ARIA range and label associations. Wrapper ARIA props stay on the wrapper; use valueText for range-specific wording. No keyboard model,
|
|
575
|
+
threshold calculation, fetching, timers or task cancellation is built in.
|
|
576
|
+
Reduced motion stops indeterminate movement and value transitions. Forced colors
|
|
577
|
+
preserves the track boundary and fill. Use a host loading message when needed.
|
|
578
|
+
|
|
579
|
+
Alert is an inline message, with optional plain-text title and React children.
|
|
580
|
+
Tones are neutral/info/success/warning/error; info is the default. Meaning must
|
|
581
|
+
remain in the text. Original decorative status glyphs are included; icon accepts
|
|
582
|
+
noninteractive host artwork, and icon={null} removes it without an empty column.
|
|
583
|
+
The title is message content, not the root's HTML title tooltip or a document heading.
|
|
584
|
+
Native div props/className/style/ref target the root.
|
|
585
|
+
|
|
586
|
+
announcement defaults to off for static messages. polite uses role=status;
|
|
587
|
+
assertive uses role=alert. Choose urgency deliberately for dynamically inserted
|
|
588
|
+
content. Announcements depend on actual assistive technology; initial static
|
|
589
|
+
markup is not guaranteed to be read aloud. No focus movement, auto-dismiss,
|
|
590
|
+
timeout or built-in close/action button is supplied. Compose named host actions
|
|
591
|
+
inside children when needed. Alert is separate from Dialog and future Toast.
|
|
592
|
+
|
|
593
|
+
See the [WAI Meter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/meter/),
|
|
594
|
+
[Alert pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alert/) and
|
|
595
|
+
[progressbar definition](https://www.w3.org/TR/wai-aria-1.2/#progressbar).
|
|
596
|
+
|
|
597
|
+
```tsx
|
|
598
|
+
<Progress label="Uploading files" value={60} valueText="60%" />
|
|
599
|
+
<Progress label="Preparing workspace" />
|
|
600
|
+
<Meter label="Storage used" min={0} max={100} value={75} valueText="75 of 100 GB" />
|
|
601
|
+
<Alert tone="warning" title="Storage is almost full">
|
|
602
|
+
Free some space before your next upload.
|
|
603
|
+
</Alert>
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
## DescriptionList and Table · 0.2.4
|
|
608
|
+
|
|
609
|
+
DescriptionList renders native dl groups, each with dt and dd. items is a readonly
|
|
610
|
+
array of {id, term, description}; IDs must be unique and nonempty, and are React
|
|
611
|
+
keys rather than DOM anchors. term is text and description is ReactNode, so a
|
|
612
|
+
value can include a Badge, Link or other meaningful content. The host formats
|
|
613
|
+
values and supplies fallback text for missing data. An empty items array stays
|
|
614
|
+
an empty list, without invented placeholder content. Native dl props,
|
|
615
|
+
className/style and HTMLDListElement ref target the list.
|
|
616
|
+
|
|
617
|
+
layout defaults inline: term and value share a row. It becomes stacked below a
|
|
618
|
+
22rem component container width, independently of viewport width. layout=stacked
|
|
619
|
+
always puts the value below its term. Density defaults compact; comfortable adds
|
|
620
|
+
space between pairs, without inflating the term/value relationship. dividers is
|
|
621
|
+
false by default and adds quiet separators when enabled. This is not an editable
|
|
622
|
+
form and its terms do not label controls; use Field/Fieldset for form inputs.
|
|
623
|
+
|
|
624
|
+
Table wraps an actual HTML table in Flood's horizontal ScrollArea. caption is
|
|
625
|
+
required and must be meaningful; hideCaption visually hides it without removing
|
|
626
|
+
the accessible table name. children owns native thead/tbody/tfoot/colgroup markup
|
|
627
|
+
and th/td associations. Supply scope=col or scope=row for simple headers, and
|
|
628
|
+
explicit id/headers associations for complex spanning headers. Do not add another
|
|
629
|
+
caption child. See [WAI table guidance](https://www.w3.org/WAI/tutorials/tables/).
|
|
630
|
+
|
|
631
|
+
Table density defaults comfortable; compact reduces vertical cell padding.
|
|
632
|
+
striped is false by default. Native tfoot rows use a quiet tinted background and
|
|
633
|
+
medium-weight text to distinguish summaries; the top separator stays decorative.
|
|
634
|
+
data-numeric on a th or td uses logical end alignment,
|
|
635
|
+
tabular figures and no wrapping for numbers. data-align=start/center/end overrides
|
|
636
|
+
alignment explicitly. Text cells wrap; set style.minWidth to preserve room for a
|
|
637
|
+
wide table, and the containing ScrollArea handles overflow. The host owns dates,
|
|
638
|
+
units, totals, empty rows/colSpan and any cell actions. Avoid interactive whole rows.
|
|
639
|
+
|
|
640
|
+
Native table props, className/style and HTMLTableElement ref target the table,
|
|
641
|
+
not its viewport. dir/lang/hidden also govern the containing scroll area.
|
|
642
|
+
scrollLabel names that viewport independently and defaults to caption. The native
|
|
643
|
+
table keeps its own caption; viewport keyboard scrolling does not turn it into a
|
|
644
|
+
grid widget. Keep action controls inside cells normally focusable. No sorting,
|
|
645
|
+
selection, data fetching, pagination, sticky header or virtualization is implied.
|
|
646
|
+
This is the compositional table primitive, not a DataTable engine.
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
<DescriptionList items={[
|
|
650
|
+
{id:"project",term:"Project",description:"Flood"},
|
|
651
|
+
{id:"version",term:"Version",description:"0.2.4"},
|
|
652
|
+
]} />
|
|
653
|
+
<Table caption="Workspace projects" style={{minWidth:"32rem"}}>
|
|
654
|
+
<thead><tr><th scope="col">Project</th><th scope="col" data-numeric>Members</th></tr></thead>
|
|
655
|
+
<tbody><tr><th scope="row">Flood</th><td data-numeric>12</td></tr></tbody>
|
|
656
|
+
</Table>
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
## Menu · 0.2.4
|
|
661
|
+
|
|
662
|
+
Menu is an original action menu using Flood's positioning/layer stack and the
|
|
663
|
+
native Popover API. label names the menu; there is no extra visible caption.
|
|
664
|
+
trigger is a render function receiving OverlayTriggerProps: forward all props
|
|
665
|
+
and ref to one persistent Button/IconButton/native button, and compose existing
|
|
666
|
+
handlers rather than replacing the supplied ones. The trigger exposes
|
|
667
|
+
aria-haspopup=menu, aria-expanded and aria-controls. disabled affects the trigger,
|
|
668
|
+
not actions inside a menu the controlled host keeps open.
|
|
669
|
+
|
|
670
|
+
items is a readonly array of action items {id,label,icon?,shortcut?,disabled?,tone?}
|
|
671
|
+
or separators {id,type:"separator"}. IDs must be unique and nonempty, action
|
|
672
|
+
labels nonempty, and at least one action must exist. The optional item type is
|
|
673
|
+
item. Icons must be decorative, noninteractive artwork. shortcut is display-only,
|
|
674
|
+
not an installed keyboard binding or part of the accessible action name. Destructive
|
|
675
|
+
actions use tone=danger; that visual treatment does not perform confirmation or
|
|
676
|
+
execute any business operation. Disabled items remain focusable with aria-disabled,
|
|
677
|
+
but neither pointer nor keyboard activation calls onAction or closes the menu.
|
|
678
|
+
|
|
679
|
+
Enter/Space/ArrowDown open at the first action; ArrowUp opens at the last.
|
|
680
|
+
Inside: Up/Down wrap, Home/End choose edges, printable label-prefix typeahead
|
|
681
|
+
matches items (repeated characters cycle). Separators are not focusable. Enter
|
|
682
|
+
or Space activates through a native button. Escape closes the top menu and
|
|
683
|
+
returns focus. Tab moves to the next focusable control after the trigger;
|
|
684
|
+
Shift+Tab returns to the trigger. The menu does not trap focus. Outside pointer
|
|
685
|
+
or focus departure requests dismissal and preserves deliberately moved focus.
|
|
686
|
+
|
|
687
|
+
open/onOpenChange is controlled, or use defaultOpen for initial local state.
|
|
688
|
+
The host may refuse a close request. onAction(id) requests closing before calling
|
|
689
|
+
the host; the host owns business logic, asynchronous work and errors. If it moves
|
|
690
|
+
focus elsewhere or opens a Dialog, normal focus restoration must not steal it.
|
|
691
|
+
Actions mount while open and unmount on close. The persistent popup ref and native
|
|
692
|
+
div props/className/style target its panel. Engine-owned coordinates and available
|
|
693
|
+
space variables belong to positioning; side/align follow Popover conventions.
|
|
694
|
+
controlSize is sm/md/lg/xl (sm default); pointer-coarse targets retain touch size.
|
|
695
|
+
|
|
696
|
+
The popup uses Flood ScrollArea for long action lists, bounded to the available
|
|
697
|
+
viewport. Its inner corners follow shell padding and border geometry. Focus is
|
|
698
|
+
inset to stay visible. There is no href/navigation-item, submenu, checkbox/radio,
|
|
699
|
+
context-menu trigger or menubar contract in this first action-menu component.
|
|
700
|
+
Use ordinary Link for ordinary navigation; keep different selection models separate.
|
|
701
|
+
|
|
702
|
+
See [WAI Menu Button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) and
|
|
703
|
+
[Menu keyboard patterns](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/).
|
|
704
|
+
|
|
705
|
+
```tsx
|
|
706
|
+
<Menu label="Project actions"
|
|
707
|
+
trigger={props => <Button {...props}>Project actions</Button>}
|
|
708
|
+
items={[{id:"rename",label:"Rename…"},{id:"divider",type:"separator"},
|
|
709
|
+
{id:"delete",label:"Delete project",tone:"danger"}]}
|
|
710
|
+
onAction={id => chooseProjectAction(id)}
|
|
711
|
+
/>
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### Toolbar and Kbd (0.2.4)
|
|
715
|
+
|
|
716
|
+
Toolbar groups Button, IconButton, Toggle, native buttons/links and Menu triggers
|
|
717
|
+
under one named action panel. After hydration it uses one Tab entry, directional
|
|
718
|
+
arrows, Home/End and optional looping. Disabled/hidden actions are skipped. Arrows
|
|
719
|
+
move focus, not activation. Horizontal order follows writing direction. In vertical
|
|
720
|
+
toolbars open Menu with Enter/Space; horizontal menus retain Up/Down opening.
|
|
721
|
+
Keep text inputs and independent selection widgets outside this action-only API.
|
|
722
|
+
Native child handlers and refs remain attached; Toolbar owns their tabIndex while
|
|
723
|
+
mounted and restores original attributes on cleanup. Without JavaScript, native
|
|
724
|
+
child Tab order remains available. Use a meaningful required label.
|
|
725
|
+
|
|
726
|
+
Kbd renders native kbd markup with sm/md/lg sizes and relative keycap heights.
|
|
727
|
+
It displays a key or combination; the host implements real shortcuts. It is not
|
|
728
|
+
interactive. Menu shortcut hints reuse Kbd and remain hidden from item names.
|
|
729
|
+
|
|
730
|
+
```tsx
|
|
731
|
+
<Toolbar label="Editor actions">
|
|
732
|
+
<Button variant="ghost">Undo</Button>
|
|
733
|
+
<Toggle variant="ghost">Bold</Toggle>
|
|
734
|
+
<Button variant="ghost">Share</Button>
|
|
735
|
+
</Toolbar>
|
|
736
|
+
<Kbd>Ctrl</Kbd> + <Kbd>K</Kbd>
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Card (0.2.4)
|
|
740
|
+
|
|
741
|
+
Card composes Surface into a header, body and footer. Optional title, description,
|
|
742
|
+
actions and footer accept React content; children are the body. Missing slots
|
|
743
|
+
create no empty section. Use Surface when only a background/padding is needed.
|
|
744
|
+
|
|
745
|
+
Card is a noninteractive div. Put Button, Link or Menu in actions/footer and keep
|
|
746
|
+
its container out of the Tab order. Do not make the entire card clickable around
|
|
747
|
+
nested controls. Native div props, styles and HTMLDivElement ref target the root.
|
|
748
|
+
The title prop is heading content, not an HTML tooltip; supply phrasing content.
|
|
749
|
+
headingLevel defaults to 2; choose the correct hierarchy for the host page.
|
|
750
|
+
|
|
751
|
+
Compact/comfortable density controls existing spacing tokens. Header actions wrap
|
|
752
|
+
without clipping menus or focus indicators. Footer uses a quiet separator; a
|
|
753
|
+
footer-only card has no redundant separator. Elevation is default or raised.
|
|
754
|
+
No data access, selection or interactive-card behavior is implied.
|
|
755
|
+
|
|
756
|
+
```tsx
|
|
757
|
+
<Card title="Project details" description="Keep your team in sync."
|
|
758
|
+
footer={<Button variant="subtle">Open project</Button>}>
|
|
759
|
+
<Text as="p" size="compact">A calm place for the next idea.</Text>
|
|
760
|
+
</Card>
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
## Structural, media and accessible primitives · 0.2.4
|
|
764
|
+
|
|
765
|
+
Container centers a max-width box with logical gutters. size sm/md/lg uses
|
|
766
|
+
40/64/80rem bounds including padding; full removes the max. gutter uses Space.
|
|
767
|
+
Grid defaults to auto-fit with a16rem semantic minimum clamped to available
|
|
768
|
+
width. gap uses Space. minItemWidth overrides the auto minimum; explicit columns
|
|
769
|
+
(1/2/3/4/6/12) remain fixed. Grid is ordinary layout, never an ARIA grid widget.
|
|
770
|
+
|
|
771
|
+
VisuallyHidden preserves content in the accessibility tree using span/div. Default
|
|
772
|
+
is for noninteractive content. Use focusable for a skip link: focus-within reveals
|
|
773
|
+
its wrapper; style the actual link's focus. This is different from aria-hidden or
|
|
774
|
+
conditional rendering. Native captions/legends may use the same hiding utility
|
|
775
|
+
without an invalid generic wrapper. Spinner and hidden indicator captions compose
|
|
776
|
+
VisuallyHidden.
|
|
777
|
+
|
|
778
|
+
AspectRatio reserves responsive media geometry before loading, accepting a finite
|
|
779
|
+
positive ratio (16/9 default). Direct img/video/iframe children fill its box. It
|
|
780
|
+
does not clip focus or make arbitrary long content fit; the host owns alternative
|
|
781
|
+
text, captions, media controls and deliberate cropping. Image is a native img
|
|
782
|
+
with required alt (empty for decorative content), cover/contain fit and preserved
|
|
783
|
+
srcSet/sizes/loading/decoding/dimensions/events/ref. No optimizer, fetch engine or
|
|
784
|
+
fallback is implied. Avatar composes Image for its own loading lifecycle. Supply
|
|
785
|
+
image dimensions or AspectRatio to reserve space.
|
|
786
|
+
|
|
787
|
+
List renders native ul/ol with native li children and token gap. ordered selects
|
|
788
|
+
ol; start/reversed apply there. start must be a safe integer. marker=false retains
|
|
789
|
+
explicit list semantics for platforms that drop lists without visible markers.
|
|
790
|
+
This is content, not ListBox selection or arrow navigation.
|
|
791
|
+
|
|
792
|
+
EmptyState composes Stack, Heading, Text, IconSlot and Inline. Required title,
|
|
793
|
+
optional description, decorative icon and actions explain absent content; choose
|
|
794
|
+
headingLevel for the host hierarchy. It is static, with no automatic focus or
|
|
795
|
+
announcement. compact/comfortable density controls padding. The host implements
|
|
796
|
+
real actions and decides whether asynchronously changed results need announcing.
|
|
797
|
+
|
|
798
|
+
Checkbox adds indeterminate for a host-controlled mixed presentation after
|
|
799
|
+
hydration. checked independently determines form submission; mixed does not mean
|
|
800
|
+
a third submitted value. The host owns aggregate/select-all values. Native reset
|
|
801
|
+
and ref access remain available. Radio and Switch do not expose indeterminate.
|
|
802
|
+
|
|
803
|
+
ButtonGroup/ToggleGroup retain their keyboard contracts and locally scroll when
|
|
804
|
+
a connected row cannot fit. InputGroup actions keep words intact and can wrap to
|
|
805
|
+
a new row while the input retains usable space. Plain string adornments are bidi
|
|
806
|
+
isolated; for protocols/phones with explicit direction pass a bdi dir=ltr node.
|
|
807
|
+
Keep very long addon content concise. Hidden native choices hide their label too.
|
|
808
|
+
|
|
809
|
+
Dark foundations bundle both normal and italic Inter, including Cyrillic, with
|
|
810
|
+
original OFL notices. Color alone never communicates validation or progress.
|
|
811
|
+
|
|
812
|
+
CodeSnippet codeDirection defaults to ltr independently of RTL parent action layout; choose rtl/auto explicitly for content that needs it. Native dir still applies to the wrapper. Protected ScrollArea reveals focused descendants fully when needed, composes the host focus handler and avoids scrolling to nested top-layer controls.
|
|
813
|
+
|
|
814
|
+
Checkbox, Radio and Switch apply native hidden/dir/lang to their visible label wrapper as well as the input, so a component-local language/direction applies to its whole choice.
|
package/dist/avatar.d.ts
ADDED
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Image } from "./index.js";
|
|
4
|
+
/** Keyed by src in Avatar so each source starts with its own load lifecycle. */
|
|
5
|
+
export function AvatarArtwork({ src, fallback }) {
|
|
6
|
+
const [state, setState] = useState("loading");
|
|
7
|
+
return _jsxs(_Fragment, { children: [_jsx("span", { className: "flood-avatar__fallback", "aria-hidden": "true", children: fallback }), src && state !== "error" && _jsx(Image, { src: src, alt: "", "aria-hidden": "true", decoding: "async", className: "flood-avatar__image", "data-ready": state === "ready", onLoad: () => setState("ready"), onError: () => setState("error") })] });
|
|
8
|
+
}
|
package/dist/checkbox.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef } from "react";
|
|
3
|
+
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4
|
+
export const MixedCheckbox = forwardRef(function MixedCheckbox({ indeterminate, onChange, ...props }, ref) {
|
|
5
|
+
const node = useRef(null), mixed = useRef(indeterminate);
|
|
6
|
+
useImperativeHandle(ref, () => node.current, []);
|
|
7
|
+
useBrowserLayoutEffect(() => { mixed.current = indeterminate; if (node.current)
|
|
8
|
+
node.current.indeterminate = indeterminate; });
|
|
9
|
+
return _jsx("input", { ...props, ref: node, type: "checkbox", className: "flood-check", "aria-checked": indeterminate ? "mixed" : props["aria-checked"], onChange: event => { const control = event.currentTarget; try {
|
|
10
|
+
onChange?.(event);
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
control.indeterminate = mixed.current;
|
|
14
|
+
} } });
|
|
15
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DescriptionListProps, TableProps } from "./index.js";
|
|
2
|
+
export declare const DescribedData: import("react").ForwardRefExoticComponent<DescriptionListProps & import("react").RefAttributes<HTMLDListElement>>;
|
|
3
|
+
export declare const TabularData: import("react").ForwardRefExoticComponent<TableProps & import("react").RefAttributes<HTMLTableElement>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { ScrollArea } from "./index.js";
|
|
4
|
+
export const DescribedData = forwardRef(function DescribedData({ items, layout, density, dividers, className, ...props }, ref) {
|
|
5
|
+
if (items.some(item => !item.id.trim()) || new Set(items.map(item => item.id)).size !== items.length)
|
|
6
|
+
throw new Error("Flood description items require unique nonempty IDs");
|
|
7
|
+
return _jsx("dl", { ...props, ref: ref, className: ["flood-description-list", className].filter(Boolean).join(" "), "data-layout": layout, "data-density": density, "data-dividers": dividers, children: items.map(item => _jsxs("div", { className: "flood-description-list__item", children: [_jsx("dt", { children: item.term }), _jsx("dd", { children: item.description })] }, item.id)) });
|
|
8
|
+
});
|
|
9
|
+
export const TabularData = forwardRef(function TabularData({ caption, children, hideCaption, density, striped, scrollLabel, className, hidden, dir, lang, ...props }, ref) {
|
|
10
|
+
return _jsx("div", { className: "flood-table-container", hidden: hidden, dir: dir, lang: lang, children: _jsx(ScrollArea, { orientation: "horizontal", "aria-label": scrollLabel ?? caption, children: _jsxs("table", { ...props, ref: ref, hidden: hidden, dir: dir, lang: lang, className: ["flood-table", className].filter(Boolean).join(" "), "data-density": density, "data-striped": striped, children: [_jsx("caption", { className: hideCaption ? "flood-visually-hidden" : undefined, children: caption }), children] }) }) });
|
|
11
|
+
});
|
package/dist/disclosure.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createElement, forwardRef, useEffect, useId, useLayoutEffect, useRef, useImperativeHandle } from "react";
|
|
3
|
+
import { Button } from "./index.js";
|
|
3
4
|
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4
5
|
/** Shared original disclosure anatomy. The closed panel stays mounted but hidden. */
|
|
5
6
|
export const Disclosure = forwardRef(function Disclosure({ title, expanded, onExpandedChange, disabled, controlSize, headingLevel, className, children, id: explicitId, ...props }, ref) {
|
|
@@ -14,7 +15,7 @@ export const Disclosure = forwardRef(function Disclosure({ title, expanded, onEx
|
|
|
14
15
|
if (!expanded)
|
|
15
16
|
focusedInside.current = false;
|
|
16
17
|
}, [expanded, disabled]);
|
|
17
|
-
const button = _jsxs(
|
|
18
|
+
const button = _jsxs(Button, { ref: trigger, variant: "ghost", size: controlSize, id: `${id}-trigger`, type: "button", disabled: disabled, "aria-expanded": expanded, "aria-controls": `${id}-panel`, className: "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
19
|
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
20
|
focusedInside.current = false; }, children: children })] });
|
|
20
21
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AlertProps, ProgressProps } from "./index.js";
|
|
2
|
+
export declare function validateRange(value: number | undefined, min: number, max: number, allowUnknown: boolean): void;
|
|
3
|
+
export declare const ValueIndicator: import("react").ForwardRefExoticComponent<ProgressProps & {
|
|
4
|
+
kind: "progress" | "meter";
|
|
5
|
+
min: number;
|
|
6
|
+
max: number;
|
|
7
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
8
|
+
export declare const FeedbackMessage: import("react").ForwardRefExoticComponent<AlertProps & import("react").RefAttributes<HTMLDivElement>>;
|
package/dist/feedback.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useId } from "react";
|
|
3
|
+
import { VisuallyHidden } from "./index.js";
|
|
4
|
+
export function validateRange(value, min, max, allowUnknown) {
|
|
5
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min || !Number.isFinite(max - min))
|
|
6
|
+
throw new RangeError("Flood indicators require a finite increasing range");
|
|
7
|
+
if (value === undefined && allowUnknown)
|
|
8
|
+
return;
|
|
9
|
+
if (value === undefined || !Number.isFinite(value) || value < min || value > max)
|
|
10
|
+
throw new RangeError("Flood indicator value must be finite and within its range");
|
|
11
|
+
}
|
|
12
|
+
export const ValueIndicator = forwardRef(function ValueIndicator({ kind, label, value, min, max, valueText, size, tone, hideLabel, className, ...props }, ref) {
|
|
13
|
+
const labelId = `flood-indicator-${useId()}`;
|
|
14
|
+
validateRange(value, min, max, kind === "progress");
|
|
15
|
+
const unknown = value === undefined;
|
|
16
|
+
const percent = unknown ? undefined : ((value - min) / (max - min)) * 100;
|
|
17
|
+
const caption = _jsxs(_Fragment, { children: [_jsx("span", { id: labelId, children: label }), valueText && _jsx("span", { className: "flood-indicator__value", "aria-hidden": "true", children: valueText })] });
|
|
18
|
+
return _jsxs("div", { ...props, ref: ref, className: ["flood-indicator", className].filter(Boolean).join(" "), "data-kind": kind, "data-size": size, "data-tone": tone, "data-indeterminate": unknown, children: [hideLabel ? _jsx(VisuallyHidden, { as: "div", children: caption }) : _jsx("div", { className: "flood-indicator__caption", children: caption }), _jsx("div", { className: "flood-indicator__track", role: kind === "meter" ? "meter" : "progressbar", "aria-labelledby": labelId, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": value, "aria-valuetext": valueText, children: _jsx("span", { className: "flood-indicator__fill", style: { inlineSize: percent === undefined ? undefined : `${percent}%` } }) })] });
|
|
19
|
+
});
|
|
20
|
+
function StatusGlyph({ tone }) {
|
|
21
|
+
return _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", focusable: "false", children: tone === "success" ? _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "m8 12 3 3 5-6" })] }) :
|
|
22
|
+
tone === "warning" ? _jsxs(_Fragment, { children: [_jsx("path", { d: "m12 3 10 17H2L12 3Z" }), _jsx("path", { d: "M12 9v4m0 3h.01" })] }) :
|
|
23
|
+
tone === "error" ? _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "m9 9 6 6m0-6-6 6" })] }) :
|
|
24
|
+
_jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 11v5m0-9h.01" })] }) });
|
|
25
|
+
}
|
|
26
|
+
export const FeedbackMessage = forwardRef(function FeedbackMessage({ title, tone, icon, announcement, children, className, ...props }, ref) {
|
|
27
|
+
const artwork = icon === undefined ? _jsx(StatusGlyph, { tone: tone }) : icon;
|
|
28
|
+
return _jsxs("div", { ...props, ref: ref, className: ["flood-alert", className].filter(Boolean).join(" "), "data-tone": tone, role: announcement === "assertive" ? "alert" : announcement === "polite" ? "status" : undefined, "aria-atomic": announcement === "off" ? undefined : true, children: [artwork != null && _jsx("span", { className: "flood-alert__icon", "aria-hidden": "true", children: artwork }), _jsxs("div", { className: "flood-alert__content", children: [title && _jsx("p", { className: "flood-alert__title", children: title }), children != null && _jsx("div", { className: "flood-alert__body", children: children })] })] });
|
|
29
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
|
|
2
|
+
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
3
|
+
const heldAttribute = "data-flood-focus-held";
|
|
4
|
+
const scopes = new WeakMap();
|
|
5
|
+
/** Keep in-flow focus reveal stable until the native pointer gesture completes. */
|
|
6
|
+
function createScope(document) {
|
|
7
|
+
const view = document.defaultView;
|
|
8
|
+
const entries = new Set(), held = new Set(), pointers = new Set();
|
|
9
|
+
let releaseTask;
|
|
10
|
+
const cancelTask = () => { if (releaseTask !== undefined)
|
|
11
|
+
view.clearTimeout(releaseTask); releaseTask = undefined; };
|
|
12
|
+
const release = () => { cancelTask(); held.forEach(node => node.removeAttribute(heldAttribute)); held.clear(); };
|
|
13
|
+
const releaseAfterDispatch = () => {
|
|
14
|
+
if (pointers.size || !held.size)
|
|
15
|
+
return;
|
|
16
|
+
cancelTask();
|
|
17
|
+
// A task, rather than a microtask, keeps the reveal through all native click
|
|
18
|
+
// listeners. Pointerup also schedules this fallback when no click is fired.
|
|
19
|
+
releaseTask = view.setTimeout(() => { releaseTask = undefined; if (!pointers.size)
|
|
20
|
+
release(); }, 0);
|
|
21
|
+
};
|
|
22
|
+
const down = (event) => {
|
|
23
|
+
if (event.button !== 0)
|
|
24
|
+
return;
|
|
25
|
+
cancelTask();
|
|
26
|
+
pointers.add(event.pointerId);
|
|
27
|
+
entries.forEach(entry => {
|
|
28
|
+
if (entry.enabled() && entry.node.matches(":focus-within")) {
|
|
29
|
+
entry.node.setAttribute(heldAttribute, "");
|
|
30
|
+
held.add(entry.node);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const up = (event) => { pointers.delete(event.pointerId); releaseAfterDispatch(); };
|
|
35
|
+
const click = () => releaseAfterDispatch();
|
|
36
|
+
const cancel = (event) => { pointers.delete(event.pointerId); if (!pointers.size)
|
|
37
|
+
release(); };
|
|
38
|
+
const blur = () => { pointers.clear(); release(); };
|
|
39
|
+
document.addEventListener("pointerdown", down, true);
|
|
40
|
+
document.addEventListener("pointerup", up, true);
|
|
41
|
+
document.addEventListener("click", click, true);
|
|
42
|
+
document.addEventListener("pointercancel", cancel, true);
|
|
43
|
+
view.addEventListener("blur", blur);
|
|
44
|
+
const scope = { add(entry) {
|
|
45
|
+
entries.add(entry);
|
|
46
|
+
return () => {
|
|
47
|
+
entries.delete(entry);
|
|
48
|
+
held.delete(entry.node);
|
|
49
|
+
entry.node.removeAttribute(heldAttribute);
|
|
50
|
+
if (entries.size)
|
|
51
|
+
return;
|
|
52
|
+
pointers.clear();
|
|
53
|
+
release();
|
|
54
|
+
document.removeEventListener("pointerdown", down, true);
|
|
55
|
+
document.removeEventListener("pointerup", up, true);
|
|
56
|
+
document.removeEventListener("click", click, true);
|
|
57
|
+
document.removeEventListener("pointercancel", cancel, true);
|
|
58
|
+
view.removeEventListener("blur", blur);
|
|
59
|
+
scopes.delete(document);
|
|
60
|
+
};
|
|
61
|
+
} };
|
|
62
|
+
return scope;
|
|
63
|
+
}
|
|
64
|
+
/** A native ref adapter; does not replace handlers or clone rich child content. */
|
|
65
|
+
export function useFocusableHidden(enabled, forwardedRef) {
|
|
66
|
+
const node = useRef(null), active = useRef(enabled), disconnect = useRef(() => { });
|
|
67
|
+
const forwardedCleanup = useRef(undefined);
|
|
68
|
+
useBrowserLayoutEffect(() => {
|
|
69
|
+
active.current = enabled;
|
|
70
|
+
if (!enabled)
|
|
71
|
+
node.current?.removeAttribute(heldAttribute);
|
|
72
|
+
}, [enabled]);
|
|
73
|
+
return useCallback((element) => {
|
|
74
|
+
disconnect.current();
|
|
75
|
+
disconnect.current = () => { };
|
|
76
|
+
node.current = element;
|
|
77
|
+
if (element?.ownerDocument.defaultView) {
|
|
78
|
+
const document = element.ownerDocument;
|
|
79
|
+
let scope = scopes.get(document);
|
|
80
|
+
if (!scope) {
|
|
81
|
+
scope = createScope(document);
|
|
82
|
+
scopes.set(document, scope);
|
|
83
|
+
}
|
|
84
|
+
disconnect.current = scope.add({ node: element, enabled: () => active.current });
|
|
85
|
+
}
|
|
86
|
+
if (typeof forwardedRef === "function") {
|
|
87
|
+
const previousCleanup = forwardedCleanup.current;
|
|
88
|
+
forwardedCleanup.current = undefined;
|
|
89
|
+
previousCleanup?.();
|
|
90
|
+
if (element !== null || !previousCleanup) {
|
|
91
|
+
const cleanup = forwardedRef(element);
|
|
92
|
+
if (typeof cleanup === "function")
|
|
93
|
+
forwardedCleanup.current = cleanup;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (forwardedRef)
|
|
97
|
+
forwardedRef.current = element;
|
|
98
|
+
}, [forwardedRef]);
|
|
99
|
+
}
|
|
Binary file
|
package/dist/fonts/README.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
# Inter font asset
|
|
2
2
|
|
|
3
|
-
`InterVariable.woff2`
|
|
3
|
+
`InterVariable.woff2` and `InterVariable-Italic.woff2` are unmodified third-party fonts by the Inter Project
|
|
4
4
|
Authors, distributed under the [SIL Open Font License 1.1](LICENSE.txt).
|
|
5
|
-
|
|
5
|
+
They are not original Flood artwork. Retain the license with redistributed copies.
|
|
6
6
|
|
|
7
7
|
- Official source: <https://github.com/rsms/inter/releases/tag/v4.1>
|
|
8
8
|
- Archive: <https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip>
|
|
9
|
-
-
|
|
9
|
+
- Normal archive member: `web/InterVariable.woff2`
|
|
10
|
+
- Italic archive member: `web/InterVariable-Italic.woff2`
|
|
10
11
|
- Font internal version: `Version 4.001;git-9221beed3`
|
|
11
12
|
- Variable axes: weight 100–900; optical size 14–32.
|
|
12
13
|
- SHA-256: `693b77d4f32ee9b8bfc995589b5fad5e99adf2832738661f5402f9978429a8e3`
|
|
14
|
+
- Italic SHA-256: `e564f652916db6c139570fefb9524a77c4d48f30c92928de9db19b6b5c7a262a`
|
|
13
15
|
- License SHA-256: `262481e844521b326f5ecd053e59b98c8b2da78c8ee1bdbb6e8174305e54935a`
|
|
14
16
|
|
|
15
17
|
The full upstream font includes Cyrillic; it has not been subsetted or converted.
|
|
16
18
|
The original `LICENSE.txt` is copied verbatim from the same release archive.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
Both assets supply weight 100–900 and optical size 14–32, including Flood's 400/500
|
|
20
|
+
roles. CSS registers separate normal and italic faces under Inter; native em/i
|
|
21
|
+
content uses the real italic asset instead of synthetic slant. Both files include
|
|
22
|
+
Latin and Cyrillic and retain the same unmodified upstream license.
|