@flood-md/react 0.2.10 → 0.2.12

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.
Files changed (57) hide show
  1. package/README.md +275 -5
  2. package/dist/app-shell.d.ts +3 -0
  3. package/dist/app-shell.js +66 -0
  4. package/dist/bulk-action-bar.d.ts +3 -0
  5. package/dist/bulk-action-bar.js +17 -0
  6. package/dist/collection-toolbar.d.ts +3 -0
  7. package/dist/collection-toolbar.js +26 -0
  8. package/dist/collection-view-switcher.d.ts +3 -0
  9. package/dist/collection-view-switcher.js +19 -0
  10. package/dist/combobox.d.ts +6 -1
  11. package/dist/combobox.js +77 -29
  12. package/dist/command-list.d.ts +5 -1
  13. package/dist/command-list.js +2 -2
  14. package/dist/data-display.js +28 -4
  15. package/dist/data-table-columns.d.ts +25 -0
  16. package/dist/data-table-columns.js +271 -0
  17. package/dist/data-table-expansion.d.ts +21 -0
  18. package/dist/data-table-expansion.js +79 -0
  19. package/dist/data-table.js +140 -12
  20. package/dist/date-picker.js +2 -2
  21. package/dist/date-range-picker.js +2 -2
  22. package/dist/date-time-picker.js +2 -2
  23. package/dist/date-time-range-picker.js +2 -2
  24. package/dist/disclosure.d.ts +2 -1
  25. package/dist/disclosure.js +4 -4
  26. package/dist/editable.js +57 -18
  27. package/dist/filter-condition.d.ts +3 -0
  28. package/dist/filter-condition.js +21 -0
  29. package/dist/filter-group.d.ts +2 -0
  30. package/dist/filter-group.js +62 -0
  31. package/dist/foundations.css +1131 -93
  32. package/dist/index.d.ts +469 -5
  33. package/dist/index.js +72 -9
  34. package/dist/menu.js +5 -4
  35. package/dist/menubar.d.ts +2 -0
  36. package/dist/menubar.js +85 -0
  37. package/dist/navigation-group.d.ts +3 -0
  38. package/dist/navigation-group.js +13 -0
  39. package/dist/navigation-item.d.ts +3 -0
  40. package/dist/navigation-item.js +68 -0
  41. package/dist/navigation-layout.d.ts +2 -1
  42. package/dist/navigation-layout.js +12 -4
  43. package/dist/navigation-tree.js +51 -7
  44. package/dist/navigation.js +2 -2
  45. package/dist/overflow-actions.d.ts +2 -0
  46. package/dist/overflow-actions.js +75 -0
  47. package/dist/overlay-runtime.d.ts +6 -2
  48. package/dist/overlay-runtime.js +41 -5
  49. package/dist/overlays.d.ts +1 -0
  50. package/dist/overlays.js +34 -18
  51. package/dist/select.js +4 -1
  52. package/dist/sidebar.d.ts +2 -0
  53. package/dist/sidebar.js +58 -0
  54. package/dist/sparkline.d.ts +2 -0
  55. package/dist/sparkline.js +65 -0
  56. package/dist/toolbar.js +2 -2
  57. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,7 +1,14 @@
1
1
  "use client";
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { forwardRef, createElement, useId, useState, } from "react";
3
+ import { forwardRef, createElement, useId, useRef, useState, } from "react";
4
4
  import { useFocusableHidden } from "./focusable-hidden.js";
5
+ import { PredicateRow } from "./filter-condition.js";
6
+ import { PredicateGroup } from "./filter-group.js";
7
+ import { CollectionQueryBar } from "./collection-toolbar.js";
8
+ import { CollectionViewSelector } from "./collection-view-switcher.js";
9
+ import { WorkspaceSidebar } from "./sidebar.js";
10
+ import { NavigationRow } from "./navigation-item.js";
11
+ import { NavigationSection } from "./navigation-group.js";
5
12
  import { resolveFieldStatus, hasFieldContent, mergeDescriptions } from "./field-state.js";
6
13
  import { SingleSelect } from "./select.js";
7
14
  import { MixedCheckbox } from "./checkbox.js";
@@ -18,7 +25,8 @@ import { RovingToolbar } from "./toolbar.js";
18
25
  import { NumericStepper } from "./numeric-input.js";
19
26
  import { RangeControl } from "./slider.js";
20
27
  import { GroupedAvatars, NamedStatus, CaptionedFigure, AttributedQuote, InlineCodeText, MeasuredValue, EventTimeline } from "./content-elements.js";
21
- import { NavigationMenu, StepSequence, ContentSection, SplitLayout, CenterLayout, SkipNavigation } from "./navigation-layout.js";
28
+ import { NavigationMenu, StepSequence, ContentSection, PageHeading, SplitLayout, CenterLayout, SkipNavigation } from "./navigation-layout.js";
29
+ import { ApplicationShell, ApplicationTopBar } from "./app-shell.js";
22
30
  import { CollectionListBox, SegmentedPinInput, StarRating, FileDropzone, SupportingCharacterCount } from "./form-controls.js";
23
31
  import { ClipboardAction, NotificationRegion } from "./feedback-actions.js";
24
32
  import { ConfirmationDialog } from "./alert-dialog.js";
@@ -43,12 +51,30 @@ import { ClockTimePicker } from "./time-picker.js";
43
51
  import { CivilDateTimeRangePicker } from "./date-time-range-picker.js";
44
52
  import { FileRecord, UploadRecord, ModalImagePreview, FileMetadata } from "./file-records.js";
45
53
  import { ManagedDataTable } from "./data-table.js";
54
+ import { TrendPlot } from "./sparkline.js";
46
55
  import { CheckableCard, ExclusiveCard } from "./collection-cards.js";
47
56
  import { ColorPreview, ColorEditor } from "./color-controls.js";
48
57
  import { HoverPreview } from "./hover-card.js";
49
58
  import { HierarchicalSelect } from "./tree-select.js";
50
59
  import { CivilDateTimePicker } from "./date-time-picker.js";
51
60
  import { HierarchicalNavigation } from "./navigation-tree.js";
61
+ import { AdaptiveActions } from "./overflow-actions.js";
62
+ import { SelectionActionBar } from "./bulk-action-bar.js";
63
+ import { ApplicationMenubar } from "./menubar.js";
64
+ /** Priority-based overflow composed from Toolbar, Button and Menu. @since 0.2.11 */
65
+ export const OverflowActions = /* @__PURE__ */ forwardRef(function OverflowActions(props, ref) { return _jsx(AdaptiveActions, { ...props, ref: ref }); });
66
+ /** One finite-choice filter predicate; the host owns application and query semantics. @since 0.2.11 */
67
+ export const FilterCondition = /* @__PURE__ */ forwardRef(function FilterCondition(props, ref) { return _jsx(PredicateRow, { ...props, ref: ref }); });
68
+ /** A flat controlled group of finite-choice predicates, without query execution or Apply/Cancel ownership. @since 0.2.11 */
69
+ export const FilterGroup = /* @__PURE__ */ forwardRef(function FilterGroup(props, ref) { return _jsx(PredicateGroup, { ...props, ref: ref }); });
70
+ /** Search, accepted summaries and staged filter disclosure; saved views and query semantics stay host-owned. @since 0.2.11 */
71
+ export const CollectionToolbar = /* @__PURE__ */ forwardRef(function CollectionToolbar(props, ref) { return _jsx(CollectionQueryBar, { ...props, ref: ref }); });
72
+ /** Controlled saved/preset view choice with optional adaptive commands. @since 0.2.11 */
73
+ export const CollectionViewSwitcher = /* @__PURE__ */ forwardRef(function CollectionViewSwitcher(props, ref) { return _jsx(CollectionViewSelector, { ...props, ref: ref }); });
74
+ /** A bounded single-row selection command capsule. The host owns positioning and updates. @since 0.2.11 */
75
+ export const BulkActionBar = /* @__PURE__ */ forwardRef(function BulkActionBar(props, ref) { return _jsx(SelectionActionBar, { ...props, ref: ref }); });
76
+ /** Desktop application commands; destinations use native links instead. @since 0.2.11 */
77
+ export const Menubar = /* @__PURE__ */ forwardRef(function Menubar(props, ref) { return _jsx(ApplicationMenubar, { ...props, ref: ref }); });
52
78
  /** @since 0.2.8 */
53
79
  export const TimePicker = /* @__PURE__ */ forwardRef(function TimePicker(props, ref) { return _jsx(ClockTimePicker, { ...props, ref: ref }); });
54
80
  /** Four atomic civil date/time form values, without timezone conversion. @since 0.2.8 */
@@ -71,6 +97,10 @@ export const UploadList = /* @__PURE__ */ forwardRef(function UploadList(props,
71
97
  export const ImagePreview = /* @__PURE__ */ forwardRef(function ImagePreview(props, ref) { return _jsx(ModalImagePreview, { ...props, ref: ref }); });
72
98
  /** A client-side table composed from native table, checkbox and pagination. @since 0.2.8 */
73
99
  export const DataTable = /* @__PURE__ */ forwardRef(function DataTable(props, ref) { return _jsx(ManagedDataTable, { ...props, ref: ref }); });
100
+ /** @since 0.2.11 */
101
+ export const Sparkline = /* @__PURE__ */ forwardRef(function Sparkline({ showLabel = false, size = "md", tone = "accent", variant = "area", ...props }, ref) {
102
+ return _jsx(TrendPlot, { ...props, showLabel: showLabel, size: size, tone: tone, variant: variant, ref: ref });
103
+ });
74
104
  /** A native list; links and actions stay independent. @since 0.2.8 */
75
105
  export const DataList = /* @__PURE__ */ forwardRef(function DataList({ density = "comfortable", dividers = true, className, ...props }, ref) {
76
106
  return _jsx("ul", { ...props, ref: ref, role: "list", "data-density": density, "data-dividers": dividers, className: classes("flood-data-list", className) });
@@ -99,10 +129,16 @@ export const DateTimePicker = /* @__PURE__ */ forwardRef(function DateTimePicker
99
129
  export const Tree = /* @__PURE__ */ forwardRef(function Tree(props, ref) {
100
130
  return _jsx(SelectionTree, { ...props, ref: ref });
101
131
  });
132
+ /** Shared navigation row geometry and native destination/action behavior. @since 0.2.11 */
133
+ export const NavigationItem = /* @__PURE__ */ forwardRef(function NavigationItem(props, ref) { return _jsx(NavigationRow, { ...props, ref: ref }); });
134
+ /** Related navigation with independently configurable presentation. @since 0.2.11 */
135
+ export const NavigationGroup = /* @__PURE__ */ forwardRef(function NavigationGroup(props, ref) { return _jsx(NavigationSection, { ...props, ref: ref }); });
102
136
  /** @since 0.2.7 */
103
137
  export const NavigationTree = /* @__PURE__ */ forwardRef(function NavigationTree(props, ref) {
104
138
  return _jsx(HierarchicalNavigation, { ...props, ref: ref });
105
139
  });
140
+ /** Bounded desktop navigation composed from NavigationTree and ScrollArea. @since 0.2.11 */
141
+ export const Sidebar = /* @__PURE__ */ forwardRef(function Sidebar(props, ref) { return _jsx(WorkspaceSidebar, { ...props, ref: ref }); });
106
142
  /** @since 0.2.7 */
107
143
  export const ErrorSummary = /* @__PURE__ */ forwardRef(function ErrorSummary({ announcement = "off", autoFocus = false, ...props }, ref) { return _jsx(ValidationErrorSummary, { ...props, ref: ref, announcement: announcement, autoFocus: autoFocus }); });
108
144
  /** @since 0.2.7 */
@@ -128,8 +164,8 @@ export const TagInput = /* @__PURE__ */ forwardRef(function TagInput({ controlSi
128
164
  /** @since 0.2.7 */
129
165
  export const Editable = /* @__PURE__ */ forwardRef(function Editable({ controlSize = "md", blurAction = "commit", showActions = true, ...props }, ref) { return _jsx(InlineEditable, { ...props, ref: ref, controlSize: controlSize, blurAction: blurAction, showActions: showActions }); });
130
166
  const classes = (...values) => values.filter(Boolean).join(" ");
131
- export const Button = /* @__PURE__ */ forwardRef(function Button({ variant = "solid", size = "md", loading = false, disabled, type = "button", className, children, ...props }, ref) {
132
- 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(Spinner, { decorative: true }), children] }));
167
+ export const Button = /* @__PURE__ */ forwardRef(function Button({ variant = "solid", tone = "default", size = "md", loading = false, disabled, type = "button", className, children, ...props }, ref) {
168
+ return (_jsxs("button", { ...props, ref: ref, type: type, "data-tone": tone === "danger" ? tone : undefined, disabled: disabled || loading, "aria-busy": loading || undefined, className: classes("flood-button", `flood-button--${variant}`, `flood-button--${size}`, className), children: [loading && _jsx(Spinner, { decorative: true }), children] }));
133
169
  });
134
170
  /** @since 0.2.5 */
135
171
  export const Tag = /* @__PURE__ */ forwardRef(function Tag({ size = "md", icon, disabled = false, onRemove, removeLabel, removeButtonProps, className, children, ...props }, ref) {
@@ -153,8 +189,8 @@ export const DescriptionList = /* @__PURE__ */ forwardRef(function DescriptionLi
153
189
  return _jsx(DescribedData, { ...props, ref: ref, items: items, layout: layout, density: density, dividers: dividers });
154
190
  });
155
191
  /** @since 0.2.4 */
156
- export const Table = /* @__PURE__ */ forwardRef(function Table({ caption, children, hideCaption = false, density = "comfortable", striped = false, scrollLabel, cellVerticalAlign = "middle", ...props }, ref) {
157
- return _jsx(TabularData, { ...props, ref: ref, caption: caption, hideCaption: hideCaption, density: density, striped: striped, scrollLabel: scrollLabel, cellVerticalAlign: cellVerticalAlign, children: children });
192
+ export const Table = /* @__PURE__ */ forwardRef(function Table({ caption, children, hideCaption = false, density = "comfortable", striped = false, scrollLabel, cellVerticalAlign = "middle", stickyHeader = false, ...props }, ref) {
193
+ return _jsx(TabularData, { ...props, ref: ref, caption: caption, hideCaption: hideCaption, density: density, striped: striped, scrollLabel: scrollLabel, cellVerticalAlign: cellVerticalAlign, stickyHeader: stickyHeader, children: children });
158
194
  });
159
195
  /** @since 0.2.4 */
160
196
  export const Badge = /* @__PURE__ */ forwardRef(function Badge({ size = "md", tone = "neutral", variant = "subtle", className, ...props }, ref) {
@@ -197,9 +233,24 @@ export const Menu = /* @__PURE__ */ forwardRef(function Menu({ defaultOpen = fal
197
233
  export const Tooltip = /* @__PURE__ */ 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 }); });
198
234
  export const Popover = /* @__PURE__ */ 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 }); });
199
235
  export const Dialog = /* @__PURE__ */ 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 }); });
236
+ /** @since 0.2.11 */
237
+ export const CommandPalette = /* @__PURE__ */ forwardRef(function CommandPalette({ searchLabel, items, density = "comfortable", query, defaultQuery, onQueryChange, onAction, loading, placeholder, emptyMessage, loadingMessage, open, defaultOpen = false, onOpenChange, className, ...props }, ref) {
238
+ const input = useRef(null);
239
+ const [localOpen, setLocalOpen] = useState(defaultOpen);
240
+ const requestOpen = (next) => {
241
+ if (open === undefined)
242
+ setLocalOpen(next);
243
+ onOpenChange?.(next);
244
+ };
245
+ return _jsxs(Dialog, { ...props, ref: ref, open: open ?? localOpen, onOpenChange: requestOpen, initialFocusRef: input, className: classes("flood-command-palette", className), children: [_jsx("div", { className: "flood-command-palette__search-icon", children: _jsx(IconSlot, { size: "sm", children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "10.5", cy: "10.5", r: "5.75" }), _jsx("path", { d: "m15 15 4 4" })] }) }) }), _jsx(SearchCommandList, { ref: input, label: searchLabel, hideLabel: true, integrated: true, items: items, controlSize: density === "compact" ? "sm" : "md", query: query, defaultQuery: defaultQuery, onQueryChange: onQueryChange, loading: loading, placeholder: placeholder, emptyMessage: emptyMessage, loadingMessage: loadingMessage, onAction: id => { requestOpen(false); onAction(id); } })] });
246
+ });
200
247
  export const Drawer = /* @__PURE__ */ forwardRef(function Drawer({ side = "end", size = "md", defaultOpen = false, closeLabel = "Close", closeOnOutsideClick = true, className, footer, ...props }, ref) {
201
248
  return _jsx(ModalOverlay, { ...props, ref: ref, defaultOpen: defaultOpen, closeLabel: closeLabel, closeOnOutsideClick: closeOnOutsideClick, className: classes("flood-drawer", className), "data-side": side, "data-size": size, scrollBody: true, footer: footer });
202
249
  });
250
+ /** A modal bottom sheet for a short task or a list of choices. @since 0.2.12 */
251
+ export const Sheet = /* @__PURE__ */ forwardRef(function Sheet({ className, size = "md", ...props }, ref) {
252
+ return _jsx(ModalOverlay, { ...props, ref: ref, "data-side": "bottom", "data-size": size, className: classes("flood-drawer", "flood-sheet", className), scrollBody: true, bodyContentInset: "none" });
253
+ });
203
254
  export const AlertDialog = /* @__PURE__ */ forwardRef(function AlertDialog({ defaultOpen = false, confirmLabel = "Continue", cancelLabel = "Cancel", confirmDisabled = false, ...props }, ref) {
204
255
  return _jsx(ConfirmationDialog, { ...props, ref: ref, defaultOpen: defaultOpen, confirmLabel: confirmLabel, cancelLabel: cancelLabel, confirmDisabled: confirmDisabled });
205
256
  });
@@ -304,6 +355,10 @@ export const SearchInput = /* @__PURE__ */ forwardRef(function SearchInput({ cle
304
355
  export const PasswordInput = /* @__PURE__ */ forwardRef(function PasswordInput({ defaultVisible = false, showPasswordLabel = "Show password", hidePasswordLabel = "Hide password", controlSize = "md", ...props }, ref) {
305
356
  return _jsx(PasswordField, { ...props, ref: ref, defaultVisible: defaultVisible, showPasswordLabel: showPasswordLabel, hidePasswordLabel: hidePasswordLabel, controlSize: controlSize, group: InputGroup, action: IconButton, icon: IconSlot });
306
357
  });
358
+ /** A free text field with suggestions; FormData contains text rather than an option ID. @since 0.2.11 */
359
+ export const Autocomplete = /* @__PURE__ */ forwardRef(function Autocomplete({ value, defaultValue = "", onValueChange, onOptionSelect, ...props }, ref) {
360
+ return _jsx(SearchSelection, { ...props, ref: ref, freeText: true, textValue: value, initialText: defaultValue, onTextChange: onValueChange, onOptionSelect: onOptionSelect, initialValues: [] });
361
+ });
307
362
  /** @since 0.2.5 */
308
363
  export const Combobox = /* @__PURE__ */ forwardRef(function Combobox({ value, defaultValue = "", onValueChange, clearable = false, emptyMessage = "No results found.", listLabel = "Options", toggleLabel = "Show options", clearLabel = "Clear selection", invalidSelectionMessage = "Select an option.", controlSize = "md", ...props }, ref) {
309
364
  return _jsx(SearchSelection, { ...props, ref: ref, multiple: false, selectedValues: value === undefined ? undefined : value ? [value] : [], initialValues: defaultValue ? [defaultValue] : [], onSelectionChange: next => onValueChange?.(next[0] ?? ""), clearable: clearable, emptyMessage: emptyMessage, listLabel: listLabel, toggleLabel: toggleLabel, clearLabel: clearLabel, invalidSelectionMessage: invalidSelectionMessage, controlSize: controlSize });
@@ -360,7 +415,9 @@ export const RadioGroup = /* @__PURE__ */ forwardRef(function RadioGroup({ value
360
415
  export const SegmentedControl = /* @__PURE__ */ forwardRef(function SegmentedControl({ label, options, controlSize = "md", iconOnly = false, fullWidth = false, orientation = "horizontal", className, onKeyDown, ...props }, ref) {
361
416
  if (iconOnly && options.some(option => !option.icon))
362
417
  throw new Error("Flood icon-only segments require icons");
363
- 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 => {
418
+ 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 })
419
+ : option.count == null || option.count === "" ? option.label
420
+ : _jsxs("span", { className: "flood-segmented__counted-label", children: [option.label, " ", _jsx("span", { className: "flood-segmented__count", children: option.count })] })] }) })), onKeyDown: event => {
364
421
  onKeyDown?.(event);
365
422
  if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
366
423
  return;
@@ -476,8 +533,8 @@ export const Card = /* @__PURE__ */ forwardRef(function Card({ title, descriptio
476
533
  export const IconSlot = /* @__PURE__ */ forwardRef(function IconSlot({ size = "md", label, className, children, ...props }, ref) {
477
534
  return _jsx("span", { ...props, ref: ref, role: label ? "img" : undefined, "aria-label": label, "aria-hidden": label ? undefined : true, className: classes("flood-icon", `flood-icon--${size}`, className), children: children });
478
535
  });
479
- export const IconButton = /* @__PURE__ */ forwardRef(function IconButton({ label, size = "md", variant = "subtle", className, children, ...props }, ref) {
480
- return _jsx(Button, { ...props, ref: ref, size: size, variant: variant, "aria-label": label, className: classes("flood-icon-button", className), children: _jsx(IconSlot, { size: size, children: children }) });
536
+ export const IconButton = /* @__PURE__ */ forwardRef(function IconButton({ label, size = "md", iconSize = size, variant = "subtle", className, children, ...props }, ref) {
537
+ return _jsx(Button, { ...props, ref: ref, size: size, variant: variant, "aria-label": label, className: classes("flood-icon-button", className), children: _jsx(IconSlot, { size: iconSize, children: children }) });
481
538
  });
482
539
  export const Divider = /* @__PURE__ */ forwardRef(function Divider({ className, ...props }, ref) {
483
540
  return _jsx("hr", { ...props, ref: ref, className: classes("flood-divider", className) });
@@ -510,6 +567,12 @@ export const Timeline = /* @__PURE__ */ forwardRef(function Timeline(props, ref)
510
567
  export const NavigationList = /* @__PURE__ */ forwardRef(function NavigationList({ size = "md", ...props }, ref) { return _jsx(NavigationMenu, { ...props, ref: ref, size: size }); });
511
568
  export const Steps = /* @__PURE__ */ forwardRef(function Steps({ orientation = "horizontal", ...props }, ref) { return _jsx(StepSequence, { ...props, ref: ref, orientation: orientation }); });
512
569
  export const Section = /* @__PURE__ */ forwardRef(function Section({ headingLevel = 2, headingSize = "section", ...props }, ref) { return _jsx(ContentSection, { ...props, ref: ref, headingLevel: headingLevel, headingSize: headingSize }); });
570
+ /** The primary heading and associated actions inside a page's main region. @since 0.2.11 */
571
+ export const PageHeader = /* @__PURE__ */ forwardRef(function PageHeader({ headingSize = "large", headingLevel = 1, ...props }, ref) { return _jsx(PageHeading, { ...props, ref: ref, headingSize: headingSize, headingLevel: headingLevel }); });
572
+ /** Persistent application chrome with independent context and action slots. @since 0.2.11 */
573
+ export const AppTopBar = /* @__PURE__ */ forwardRef(function AppTopBar({ bordered = false, ...props }, ref) { return _jsx(ApplicationTopBar, { ...props, ref: ref, bordered: bordered }); });
574
+ /** Composes application navigation and a main landmark with optional bounded scrolling. @since 0.2.11 */
575
+ export const AppShell = /* @__PURE__ */ forwardRef(function AppShell({ navigationSide = "start", scroll = "content", mainAs = "main", ...props }, ref) { return _jsx(ApplicationShell, { ...props, ref: ref, navigationSide: navigationSide, scroll: scroll, mainAs: mainAs }); });
513
576
  export const Split = /* @__PURE__ */ forwardRef(function Split({ side = "end", ratio = "main", collapseAt = "md", gap = "6", ...props }, ref) { return _jsx(SplitLayout, { ...props, ref: ref, side: side, ratio: ratio, collapseAt: collapseAt, gap: gap }); });
514
577
  export const Center = /* @__PURE__ */ forwardRef(function Center({ inline = false, ...props }, ref) { return _jsx(CenterLayout, { ...props, ref: ref, inline: inline }); });
515
578
  export const SkipLink = /* @__PURE__ */ forwardRef(function SkipLink({ children = "Skip to content", ...props }, ref) { return _jsx(SkipNavigation, { ...props, ref: ref, children: children }); });
package/dist/menu.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Fragment, forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
3
  import { Button, Kbd, ScrollArea } from "./index.js";
4
- import { positionLayer, registerLayer, returnFocus, tabbables } from "./overlay-runtime.js";
4
+ import { positionLayer, registerLayer, returnFocus, tabbables, useLayerExitContent } from "./overlay-runtime.js";
5
5
  const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
6
6
  /** Validate each level while retaining globally unique action IDs. */
7
7
  export function validateMenuTree(items) {
@@ -171,6 +171,7 @@ export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ l
171
171
  element.hidePopover();
172
172
  };
173
173
  }, [open, side, align, nested]);
174
+ const showContent = useLayerExitContent(panel, open);
174
175
  useBrowserLayoutEffect(() => {
175
176
  if (openChild && !items.some(item => item.id === openChild && item.type === "submenu" && !item.disabled))
176
177
  setOpenChild(undefined);
@@ -211,7 +212,7 @@ export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ l
211
212
  if (item.type === "separator")
212
213
  return _jsx("div", { role: "separator", className: "flood-menu__separator" }, item.id);
213
214
  const submenu = item.type === "submenu", childId = `${id}-submenu-${encodeURIComponent(item.id)}`;
214
- return _jsxs(Fragment, { children: [_jsxs(Button, { ref: anchorRef(item.id), role: item.type === "checkbox" ? "menuitemcheckbox" : item.type === "radio" ? "menuitemradio" : "menuitem", "aria-checked": item.type === "checkbox" || item.type === "radio" ? item.checked : undefined, "aria-haspopup": submenu ? "menu" : undefined, "aria-expanded": submenu ? openChild === item.id && !item.disabled : undefined, "aria-controls": submenu ? childId : undefined, tabIndex: -1, variant: "ghost", size: controlSize, "aria-label": item.label, "aria-disabled": item.disabled || undefined, "data-label": item.label, "data-menu-id": item.id, "data-tone": "tone" in item ? item.tone : undefined, "data-submenu": submenu || undefined, className: "flood-menu__item", onPointerDown: event => { pointerType.current = event.pointerType; }, onPointerEnter: event => { if (event.pointerType === "mouse")
215
+ return _jsxs(Fragment, { children: [_jsxs(Button, { ref: anchorRef(item.id), role: item.type === "checkbox" ? "menuitemcheckbox" : item.type === "radio" ? "menuitemradio" : "menuitem", "aria-checked": item.type === "checkbox" || item.type === "radio" ? item.checked : undefined, "aria-haspopup": submenu ? "menu" : undefined, "aria-expanded": submenu ? openChild === item.id && !item.disabled : undefined, "aria-controls": submenu ? childId : undefined, tabIndex: -1, variant: "ghost", size: controlSize, "aria-label": item.label, "aria-disabled": item.disabled || undefined, "data-label": item.label, "data-menu-id": item.id, tone: "tone" in item ? item.tone : undefined, "data-submenu": submenu || undefined, className: "flood-menu__item", onPointerDown: event => { pointerType.current = event.pointerType; }, onPointerEnter: event => { if (event.pointerType === "mouse")
215
216
  scheduleHover(item, event.currentTarget); }, onPointerLeave: () => clearHover(), onPointerMove: event => {
216
217
  if (event.pointerType === "touch" || openChild)
217
218
  return;
@@ -262,7 +263,7 @@ export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ l
262
263
  group.push(items[++index]);
263
264
  rows.push(_jsxs("div", { role: "group", "aria-label": item.group, children: [_jsx("div", { className: "flood-menu__group-label", "aria-hidden": "true", children: item.group }), group.map(renderItem)] }, `radio-${item.id}`));
264
265
  }
265
- return _jsx("div", { ...props, ref: panel, id: id, popover: "manual", role: "menu", "aria-label": label, "aria-orientation": "vertical", tabIndex: -1, className: ["flood-overlay-panel", "flood-menu", nested && "flood-menu--submenu", className].filter(Boolean).join(" "), onKeyDown: event => {
266
+ return _jsx("div", { ...props, ref: panel, id: id, popover: "manual", role: "menu", "aria-label": label, "aria-orientation": "vertical", "aria-hidden": !open || undefined, tabIndex: -1, className: ["flood-overlay-panel", "flood-menu", nested && "flood-menu--submenu", className].filter(Boolean).join(" "), onKeyDown: event => {
266
267
  if (event.target.closest('[role="menu"]') !== panel.current)
267
268
  return;
268
269
  onKeyDown?.(event);
@@ -325,7 +326,7 @@ export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ l
325
326
  all[next].focus({ preventScroll: true });
326
327
  all[next].scrollIntoView({ block: "nearest", inline: "nearest" });
327
328
  }
328
- }, children: open && _jsx(ScrollArea, { orientation: "vertical", role: "group", tabIndex: -1, className: "flood-menu__viewport", children: rows }) });
329
+ }, children: showContent && _jsx(ScrollArea, { orientation: "vertical", role: "group", tabIndex: -1, className: "flood-menu__viewport", children: rows }) });
329
330
  });
330
331
  export const ActionMenu = /* @__PURE__ */ forwardRef(function ActionMenu({ label, trigger, items, onAction, open: controlled, defaultOpen, onOpenChange, disabled, side = "bottom", align = "start", controlSize = "sm", ...props }, forwardedRef) {
331
332
  if (!label.trim())
@@ -0,0 +1,2 @@
1
+ import { type MenubarProps } from "./index.js";
2
+ export declare const ApplicationMenubar: import("react").ForwardRefExoticComponent<MenubarProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
+ import { Button, Menu } from "./index.js";
4
+ import { tabbables } from "./overlay-runtime.js";
5
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
6
+ export const ApplicationMenubar = /* @__PURE__ */ forwardRef(function ApplicationMenubar({ label, menus, openValue, defaultOpenValue = null, onOpenValueChange, onAction, controlSize = "sm", disabled = false, className, onKeyDown, onKeyDownCapture, ...props }, ref) {
7
+ if (!label.trim() || menus.some(menu => !menu.id.trim() || !menu.label.trim()) || new Set(menus.map(menu => menu.id)).size !== menus.length)
8
+ throw new Error("Flood Menubar requires unique named menus");
9
+ const root = useRef(null);
10
+ useImperativeHandle(ref, () => root.current, []);
11
+ const [local, setLocal] = useState(defaultOpenValue), [active, setActive] = useState();
12
+ const requested = openValue === undefined ? local : openValue;
13
+ const enabled = menus.filter(menu => !disabled && !menu.disabled), open = enabled.some(menu => menu.id === requested) ? requested : null;
14
+ const current = useRef(open);
15
+ current.current = open;
16
+ const activeId = enabled.some(menu => menu.id === active) ? active : enabled[0]?.id;
17
+ const request = (next) => { if (openValue === undefined)
18
+ setLocal(next); onOpenValueChange?.(next); };
19
+ const trigger = (id) => [...root.current?.querySelectorAll('[data-menubar-trigger]') ?? []].find(node => node.dataset.menubarTrigger === id);
20
+ const move = (id, delta) => {
21
+ const at = enabled.findIndex(menu => menu.id === id), next = enabled[(at + delta + enabled.length) % enabled.length];
22
+ if (!next)
23
+ return;
24
+ setActive(next.id);
25
+ if (open)
26
+ request(next.id);
27
+ else
28
+ trigger(next.id)?.focus({ preventScroll: true });
29
+ };
30
+ useBrowserLayoutEffect(() => { if (open)
31
+ setActive(open); }, [open]);
32
+ useEffect(() => { if (requested && !open)
33
+ request(null); }, [requested, open]);
34
+ return _jsx("div", { ...props, ref: root, role: "menubar", "aria-label": label, "aria-orientation": "horizontal", "aria-disabled": disabled || undefined, tabIndex: enabled.length ? undefined : 0, className: ["flood-menubar", className].filter(Boolean).join(" "), onKeyDownCapture: event => {
35
+ onKeyDownCapture?.(event);
36
+ if (event.defaultPrevented || event.key !== "Tab" || event.altKey || event.ctrlKey || event.metaKey || !open)
37
+ return;
38
+ // Capture also sees nested menus; their ordinary Menu Tab contract returns
39
+ // to its trigger, while an application menubar exits the whole bar.
40
+ const anchor = trigger(open), scope = anchor?.closest('dialog[open]') ?? anchor?.ownerDocument.body;
41
+ if (anchor && scope) {
42
+ const stops = tabbables(scope), at = stops.indexOf(anchor);
43
+ const next = event.shiftKey ? stops.slice(0, at).reverse().find(node => !root.current?.contains(node)) : stops.slice(at + 1).find(node => !root.current?.contains(node));
44
+ if (next) {
45
+ event.preventDefault();
46
+ event.stopPropagation();
47
+ next.focus();
48
+ request(null);
49
+ }
50
+ }
51
+ }, onKeyDown: event => {
52
+ onKeyDown?.(event);
53
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
54
+ return;
55
+ const source = event.target.closest('[data-menubar-trigger]');
56
+ if (!source)
57
+ return;
58
+ const id = source.dataset.menubarTrigger, rtl = getComputedStyle(event.currentTarget).direction === "rtl";
59
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
60
+ event.preventDefault();
61
+ move(id, event.key === (rtl ? "ArrowLeft" : "ArrowRight") ? 1 : -1);
62
+ }
63
+ else if (event.key === "Home" || event.key === "End") {
64
+ event.preventDefault();
65
+ const next = event.key === "Home" ? enabled[0] : enabled.at(-1);
66
+ if (next) {
67
+ setActive(next.id);
68
+ trigger(next.id)?.focus();
69
+ }
70
+ }
71
+ }, children: menus.map(menu => _jsx(Menu, { label: menu.label, items: menu.items, open: open === menu.id, disabled: disabled || menu.disabled, controlSize: controlSize, onOpenChange: next => { if (next)
72
+ request(menu.id);
73
+ else if (current.current === menu.id)
74
+ request(null); }, onAction: id => onAction?.(menu.id, id), onKeyDown: event => {
75
+ if (event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
76
+ return;
77
+ const rtl = getComputedStyle(root.current).direction === "rtl", forward = event.key === (rtl ? "ArrowLeft" : "ArrowRight");
78
+ if ((forward || (event.key === (rtl ? "ArrowRight" : "ArrowLeft"))) && !(forward && event.target.closest('[aria-haspopup="menu"]'))) {
79
+ event.preventDefault();
80
+ event.stopPropagation();
81
+ move(menu.id, forward ? 1 : -1);
82
+ }
83
+ }, trigger: binding => _jsx(Button, { ...binding, role: "menuitem", "data-menubar-trigger": menu.id, tabIndex: menu.id === activeId ? 0 : -1, variant: "ghost", size: controlSize, onFocus: () => setActive(menu.id), onPointerEnter: () => { if (open && open !== menu.id && !disabled && !menu.disabled)
84
+ request(menu.id); }, children: menu.label }) }, menu.id)) });
85
+ });
@@ -0,0 +1,3 @@
1
+ import type { NavigationGroupProps } from "./index.js";
2
+ /** Related navigation with optional presentation and an independent disclosure owner. */
3
+ export declare const NavigationSection: import("react").ForwardRefExoticComponent<NavigationGroupProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,13 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { Collapsible, Text } from "./index.js";
4
+ /** Related navigation with optional presentation and an independent disclosure owner. */
5
+ export const NavigationSection = /* @__PURE__ */ forwardRef(function NavigationSection({ label, hideLabel = false, headingAction, separator = false, collapsible = false, expanded, defaultExpanded = true, onExpandedChange, compact = false, size = "md", collapseDisabled = false, className, children, ...props }, ref) {
6
+ if (label !== undefined && !label.trim())
7
+ throw new Error("Flood NavigationGroup label must be nonempty when provided");
8
+ if (collapsible && !label?.trim())
9
+ throw new Error("Flood collapsible NavigationGroup requires a nonempty label");
10
+ const action = headingAction == null ? undefined : _jsx("div", { className: "flood-navigation-group__action", children: headingAction });
11
+ const body = _jsx("div", { className: "flood-navigation-group__body", children: children });
12
+ return _jsx("div", { ...props, ref: ref, role: label ? "group" : props.role, "aria-label": props["aria-label"] ?? label, "data-size": size, "data-compact": compact || undefined, "data-hide-label": hideLabel || undefined, "data-separator": separator || undefined, "data-collapsible": collapsible || undefined, className: ["flood-navigation-group", className].filter(Boolean).join(" "), children: collapsible ? _jsx(Collapsible, { title: label, open: compact ? true : expanded, defaultOpen: defaultExpanded, onOpenChange: compact ? undefined : onExpandedChange, disabled: collapseDisabled, controlSize: size, headerAction: action, className: "flood-navigation-group__disclosure", children: body }) : _jsxs(_Fragment, { children: [(!compact && label && !hideLabel || action) && _jsxs("div", { className: "flood-navigation-group__heading", children: [!compact && label && !hideLabel && _jsx(Text, { size: "helper", tone: "subtle", className: "flood-navigation-group__label", children: label }), action] }), body] }) });
13
+ });
@@ -0,0 +1,3 @@
1
+ import type { NavigationItemProps } from "./index.js";
2
+ /** Shared anatomy for destinations, commands and navigation disclosures. */
3
+ export declare const NavigationRow: import("react").ForwardRefExoticComponent<NavigationItemProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,68 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useCallback, useRef } from 'react';
3
+ import { Button, IconButton, IconSlot, Link, Text, Tooltip } from './index.js';
4
+ function assignRef(ref, node) { if (typeof ref === 'function')
5
+ ref(node);
6
+ else if (ref)
7
+ ref.current = node; }
8
+ const classes = (...values) => values.filter(Boolean).join(' ');
9
+ /** Shared anatomy for destinations, commands and navigation disclosures. */
10
+ export const NavigationRow = /* @__PURE__ */ forwardRef(function NavigationRow({ label, description, icon, reserveIcon = false, trailing, trailingAction, href, linkProps, onAction, actionProps, disclosure, current = false, currentKind = 'page', ancestorCurrent = false, disabled = false, size = 'md', compact = false, variant = 'quiet', tooltip = true, tooltipSide = 'right', controlRef, disclosureRef, className, ...props }, ref) {
11
+ if (!label.trim())
12
+ throw new Error('Flood NavigationItem requires a nonempty label');
13
+ if (href !== undefined && !href.trim())
14
+ throw new Error('Flood NavigationItem requires a nonempty destination');
15
+ if (href !== undefined && onAction)
16
+ throw new Error('Flood NavigationItem uses either a destination or a command');
17
+ if (compact && disclosure && (href !== undefined || onAction || actionProps?.onClick))
18
+ throw new Error('Flood compact rows with separate actions and disclosure require a branch composition such as NavigationTree');
19
+ if (disclosure && !disclosure.label.trim())
20
+ throw new Error('Flood NavigationItem requires a named disclosure');
21
+ const toggleOnly = Boolean(disclosure && href === undefined && !onAction);
22
+ const hasAction = !compact && trailingAction != null;
23
+ const unavailable = disabled || Boolean(toggleOnly && disclosure?.disabled) || Boolean(href === undefined && actionProps?.disabled);
24
+ const hintRef = useRef(undefined);
25
+ const setControl = useCallback((node) => { assignRef(controlRef, node); hintRef.current?.(node); }, [controlRef]);
26
+ const glyph = icon ?? (compact ? _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", "aria-hidden": "true", children: _jsx("rect", { x: "5", y: "5", width: "14", height: "14", rx: "4" }) }) : null);
27
+ const chevron = _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m9 6 6 6-6 6" }) });
28
+ const content = _jsxs(_Fragment, { children: [(icon != null || reserveIcon || compact) && _jsx(IconSlot, { size: size, children: glyph }), _jsxs("span", { className: "flood-navigation-item__content", children: [_jsx(Text, { as: "span", className: "flood-navigation-item__label", children: label }), !compact && description && _jsx(Text, { as: "span", size: "helper", tone: "subtle", className: "flood-navigation-item__description", children: description })] }), !compact && !hasAction && trailing != null && _jsx("span", { className: "flood-navigation-item__trailing", children: trailing }), !compact && toggleOnly && _jsx("span", { className: "flood-navigation-item__indicator", children: _jsx(IconSlot, { size: size === 'lg' ? 'xs' : '2xs', children: chevron }) })] });
29
+ const primary = (hint) => {
30
+ hintRef.current = hint?.ref;
31
+ const hintEvents = {
32
+ onPointerEnter: (event) => { if (href !== undefined)
33
+ linkProps?.onPointerEnter?.(event);
34
+ else
35
+ actionProps?.onPointerEnter?.(event); if (!event.defaultPrevented)
36
+ hint?.onPointerEnter?.(event); },
37
+ onPointerLeave: (event) => { if (href !== undefined)
38
+ linkProps?.onPointerLeave?.(event);
39
+ else
40
+ actionProps?.onPointerLeave?.(event); hint?.onPointerLeave?.(event); },
41
+ onFocus: (event) => { if (href !== undefined)
42
+ linkProps?.onFocus?.(event);
43
+ else
44
+ actionProps?.onFocus?.(event); if (!event.defaultPrevented)
45
+ hint?.onFocus?.(event); },
46
+ onBlur: (event) => { if (href !== undefined)
47
+ linkProps?.onBlur?.(event);
48
+ else
49
+ actionProps?.onBlur?.(event); hint?.onBlur?.(event); },
50
+ };
51
+ const source = href !== undefined ? linkProps : actionProps;
52
+ const descriptionIds = [source?.['aria-describedby'], hint?.['aria-describedby']].filter(Boolean).join(' ') || undefined;
53
+ const controlClass = classes('flood-navigation-item__control', source?.className);
54
+ if (href !== undefined) {
55
+ if (unavailable)
56
+ return _jsx("span", { ref: setControl, role: "link", "aria-disabled": "true", "aria-label": compact ? linkProps?.['aria-label'] ?? label : linkProps?.['aria-label'], "aria-current": current ? currentKind : undefined, className: controlClass, children: content });
57
+ return _jsx(Link, { ...linkProps, ...hintEvents, ref: setControl, href: href, variant: "ghost", size: size, className: controlClass, title: hint ? undefined : linkProps?.title, "aria-label": compact ? linkProps?.['aria-label'] ?? label : linkProps?.['aria-label'], "aria-describedby": descriptionIds, "aria-current": current ? currentKind : undefined, children: content });
58
+ }
59
+ if (onAction || toggleOnly || actionProps?.onClick)
60
+ return _jsx(Button, { ...actionProps, ...hintEvents, ref: setControl, type: "button", variant: "ghost", size: size, className: controlClass, disabled: unavailable || actionProps?.disabled, "aria-label": actionProps?.['aria-label'] ?? (toggleOnly ? disclosure.label : compact ? label : undefined), "aria-describedby": descriptionIds, "aria-current": current ? currentKind : undefined, "aria-expanded": toggleOnly ? disclosure.expanded : actionProps?.['aria-expanded'], "aria-controls": toggleOnly ? disclosure.controls : actionProps?.['aria-controls'], onClick: event => { actionProps?.onClick?.(event); if (event.defaultPrevented)
61
+ return; if (toggleOnly)
62
+ disclosure.onExpandedChange(!disclosure.expanded);
63
+ else
64
+ onAction?.(); }, children: content });
65
+ return _jsx("span", { ref: setControl, className: controlClass, "aria-disabled": unavailable || undefined, "aria-current": current ? currentKind : undefined, children: content });
66
+ };
67
+ return _jsxs("div", { ...props, ref: ref, className: classes('flood-navigation-item', className), "data-size": size, "data-variant": variant, "data-compact": compact || undefined, "data-current": current || undefined, "data-ancestor-current": ancestorCurrent || undefined, "data-disabled": unavailable || undefined, "data-disclosure": disclosure && !toggleOnly && !compact || undefined, "data-trailing-action": hasAction || undefined, "data-expanded": disclosure?.expanded, children: [compact ? _jsx(Tooltip, { content: label, side: tooltipSide, disabled: !tooltip || unavailable, trigger: hint => primary(hint) }) : primary(), hasAction && _jsxs("div", { className: "flood-navigation-item__accessories", children: [trailing != null && _jsx("span", { className: "flood-navigation-item__metadata", children: trailing }), _jsx("div", { className: "flood-navigation-item__action", children: trailingAction })] }), disclosure && !toggleOnly && !compact && _jsx(IconButton, { ref: disclosureRef, label: disclosure.label, size: size, iconSize: size === 'lg' ? 'xs' : '2xs', variant: "ghost", className: "flood-navigation-item__disclosure", disabled: disabled || disclosure.disabled, "aria-expanded": disclosure.expanded, "aria-controls": disclosure.controls, onClick: () => disclosure.onExpandedChange(!disclosure.expanded), children: chevron })] });
68
+ });
@@ -1,6 +1,7 @@
1
- import type { NavigationListProps, StepsProps, SectionProps, SplitProps, CenterProps, SkipLinkProps } from "./index.js";
1
+ import type { NavigationListProps, StepsProps, SectionProps, PageHeaderProps, SplitProps, CenterProps, SkipLinkProps } from "./index.js";
2
2
  export declare const NavigationMenu: import("react").ForwardRefExoticComponent<NavigationListProps & import("react").RefAttributes<HTMLElement>>;
3
3
  export declare const StepSequence: import("react").ForwardRefExoticComponent<StepsProps & import("react").RefAttributes<HTMLOListElement>>;
4
+ export declare const PageHeading: import("react").ForwardRefExoticComponent<PageHeaderProps & import("react").RefAttributes<HTMLElement>>;
4
5
  export declare const ContentSection: import("react").ForwardRefExoticComponent<SectionProps & import("react").RefAttributes<HTMLElement>>;
5
6
  export declare const SplitLayout: import("react").ForwardRefExoticComponent<SplitProps & import("react").RefAttributes<HTMLDivElement>>;
6
7
  export declare const CenterLayout: import("react").ForwardRefExoticComponent<CenterProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useId } from "react";
3
- import { IconSlot, Heading } from "./index.js";
3
+ import { Heading, NavigationGroup, NavigationItem } from "./index.js";
4
4
  const classes = (...names) => names.filter(Boolean).join(" ");
5
5
  function uniqueValues(items, component) {
6
6
  if (items.some(item => !item.value.trim()) || new Set(items.map(item => item.value)).size !== items.length)
@@ -19,8 +19,8 @@ export const NavigationMenu = /* @__PURE__ */ forwardRef(function NavigationMenu
19
19
  else
20
20
  groups.push({ label: item.group ?? "", items: [item] });
21
21
  }
22
- const links = (entries) => entries.map(item => _jsx("li", { children: _jsxs("a", { ...item.linkProps, href: item.href, "aria-current": item.value === currentValue ? "page" : undefined, className: classes("flood-navigation-list__link", item.linkProps?.className), children: [item.icon != null && _jsx(IconSlot, { size: size, children: item.icon }), _jsxs("span", { className: "flood-navigation-list__text", children: [_jsx("span", { className: "flood-navigation-list__label", children: item.label }), item.description && _jsx("span", { className: "flood-navigation-list__description", children: item.description })] })] }) }, item.value));
23
- return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: classes("flood-navigation-list", className), "data-size": size, children: _jsx("ul", { role: "list", className: "flood-navigation-list__groups", children: groups.map((group, index) => group.label ? _jsxs("li", { className: "flood-navigation-list__group", children: [_jsx("p", { className: "flood-navigation-list__group-title", id: `${id}-${index}`, children: group.label }), _jsx("ul", { role: "list", "aria-labelledby": `${id}-${index}`, children: links(group.items) })] }, index) : links(group.items)) }) });
22
+ const links = (entries) => entries.map(item => _jsx("li", { children: _jsx(NavigationItem, { label: item.label, description: item.description, href: item.href, icon: item.icon, reserveIcon: entries.some(entry => entry.icon != null), current: item.value === currentValue, size: size, linkProps: { ...item.linkProps, className: classes("flood-navigation-list__link", item.linkProps?.className) } }) }, item.value));
23
+ return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: classes("flood-navigation-list", className), "data-size": size, children: _jsx("ul", { role: "list", className: "flood-navigation-list__groups", children: groups.map((group, index) => group.label ? _jsx("li", { className: "flood-navigation-list__group", children: _jsx(NavigationGroup, { label: group.label, id: `${id}-${index}`, size: size, children: _jsx("ul", { role: "list", "aria-labelledby": `${id}-${index}`, children: links(group.items) }) }) }, index) : links(group.items)) }) });
24
24
  });
25
25
  export const StepSequence = /* @__PURE__ */ forwardRef(function StepSequence({ items, label, currentValue = items[0]?.value, orientation = "horizontal", onStepChange, statusLabels = { complete: "Completed", current: "Current step", upcoming: "Upcoming", error: "Needs attention" }, className, ...props }, ref) {
26
26
  uniqueValues(items, "Steps");
@@ -36,11 +36,19 @@ export const StepSequence = /* @__PURE__ */ forwardRef(function StepSequence({ i
36
36
  return _jsx("li", { "data-state": state, "aria-current": current ? "step" : undefined, children: onStepChange ? _jsx("button", { type: "button", className: "flood-steps__content flood-steps__action", disabled: item.disabled, onClick: () => onStepChange(item.value), children: content }) : _jsx("div", { className: "flood-steps__content", children: content }) }, item.value);
37
37
  }) });
38
38
  });
39
+ const ContentHeader = /* @__PURE__ */ forwardRef(function ContentHeader({ title, description, actions, headingId, headingLevel, headingSize, className, ...props }, ref) {
40
+ return _jsxs("header", { ...props, ref: ref, className: classes("flood-section__header", className), children: [_jsxs("div", { className: "flood-section__heading", children: [_jsx(Heading, { id: headingId, level: headingLevel, size: headingSize, className: "flood-section__title", children: title }), description != null && _jsx("div", { className: "flood-section__description", children: description })] }), actions != null && _jsx("div", { className: "flood-section__actions", children: actions })] });
41
+ });
42
+ export const PageHeading = /* @__PURE__ */ forwardRef(function PageHeader({ title, headingSize = "large", headingLevel = 1, className, ...props }, ref) {
43
+ if (!title.trim())
44
+ throw new Error("Flood PageHeader requires a nonempty title");
45
+ return _jsx(ContentHeader, { ...props, ref: ref, title: title, headingLevel: headingLevel, headingSize: headingSize, className: classes("flood-page-header", className) });
46
+ });
39
47
  export const ContentSection = /* @__PURE__ */ forwardRef(function ContentSection({ title, description, actions, headingLevel = 2, headingSize = "section", children, id: explicitId, className, "aria-labelledby": labelledBy, ...props }, ref) {
40
48
  if (!title.trim() || ![2, 3, 4, 5, 6].includes(headingLevel))
41
49
  throw new Error("Flood Section requires a nonempty title and headingLevel from 2 to 6");
42
50
  const generated = useId(), headingId = `${explicitId ?? `flood-section-${generated}`}-title`;
43
- return _jsxs("section", { ...props, ref: ref, id: explicitId, "aria-labelledby": labelledBy ?? headingId, className: classes("flood-section", className), children: [_jsxs("header", { className: "flood-section__header", children: [_jsxs("div", { className: "flood-section__heading", children: [_jsx(Heading, { id: headingId, level: headingLevel, size: headingSize, className: "flood-section__title", children: title }), description != null && _jsx("div", { className: "flood-section__description", children: description })] }), actions != null && _jsx("div", { className: "flood-section__actions", children: actions })] }), children != null && _jsx("div", { className: "flood-section__body", children: children })] });
51
+ return _jsxs("section", { ...props, ref: ref, id: explicitId, "aria-labelledby": labelledBy ?? headingId, className: classes("flood-section", className), children: [_jsx(ContentHeader, { title: title, description: description, actions: actions, headingId: headingId, headingLevel: headingLevel, headingSize: headingSize }), children != null && _jsx("div", { className: "flood-section__body", children: children })] });
44
52
  });
45
53
  export const SplitLayout = /* @__PURE__ */ forwardRef(function SplitLayout({ main, aside, side = "end", ratio = "main", collapseAt = "md", gap = "6", className, style, ...props }, ref) {
46
54
  const primary = _jsx("div", { className: "flood-split__main", children: main }, "main"), secondary = _jsx("div", { className: "flood-split__aside", children: aside }, "aside");