@justanarthur/modo-ui 0.6.0-canary.0 → 0.6.0-canary.2

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 (124) hide show
  1. package/README.md +14 -14
  2. package/components/inputs/date-time/calendar/index.tsx +40 -21
  3. package/components/inputs/date-time/date-picker/examples.mdx +59 -0
  4. package/components/inputs/date-time/date-picker/index.tsx +302 -211
  5. package/components/inputs/date-time/date-picker/masked-date-input.tsx +9 -0
  6. package/components/inputs/date-time/date-range-picker/examples.mdx +52 -0
  7. package/components/inputs/date-time/date-range-picker/index.tsx +377 -70
  8. package/components/inputs/date-time/duration-picker/examples.mdx +24 -0
  9. package/components/inputs/date-time/duration-picker/index.tsx +111 -47
  10. package/components/inputs/date-time/month-picker/examples.mdx +29 -0
  11. package/components/inputs/date-time/month-picker/index.tsx +109 -45
  12. package/components/inputs/date-time/picker-shell.tsx +197 -15
  13. package/components/inputs/date-time/time-columns.tsx +5 -2
  14. package/components/inputs/date-time/time-picker/examples.mdx +31 -0
  15. package/components/inputs/date-time/time-picker/index.tsx +98 -37
  16. package/components/inputs/slider/index.tsx +13 -4
  17. package/components/navigation/pagination/examples.mdx +4 -0
  18. package/components/navigation/pagination/index.tsx +8 -2
  19. package/components/overlays/alert-dialog/index.tsx +24 -6
  20. package/components/overlays/command/index.tsx +15 -6
  21. package/components/overlays/dialog/examples.mdx +21 -0
  22. package/components/overlays/dialog/index.tsx +100 -15
  23. package/components/overlays/dropdown/index.tsx +17 -16
  24. package/components/overlays/popover/examples.mdx +17 -0
  25. package/components/overlays/popover/index.tsx +3 -1
  26. package/components/overlays/sheet/examples.mdx +12 -0
  27. package/components/overlays/sheet/index.tsx +72 -12
  28. package/components/overlays/toast/examples.mdx +17 -0
  29. package/components/overlays/toast/index.tsx +73 -3
  30. package/components/overlays/tooltip/index.tsx +22 -2
  31. package/components/table/examples.mdx +7 -7
  32. package/components/table/index.tsx +26 -11
  33. package/dist/blocks/sidebar-app/index.js +8 -8
  34. package/dist/components/AI/input-message/index.js +3 -3
  35. package/dist/components/copy/index.js +3 -3
  36. package/dist/components/inputs/color-picker/index.js +4 -4
  37. package/dist/components/inputs/date-time/calendar/index.d.ts +24 -18
  38. package/dist/components/inputs/date-time/calendar/index.js +3 -3
  39. package/dist/components/inputs/date-time/date-picker/index.d.ts +57 -12
  40. package/dist/components/inputs/date-time/date-picker/index.js +10 -9
  41. package/dist/components/inputs/date-time/date-range-picker/index.d.ts +104 -12
  42. package/dist/components/inputs/date-time/date-range-picker/index.js +10 -8
  43. package/dist/components/inputs/date-time/duration-picker/index.d.ts +64 -13
  44. package/dist/components/inputs/date-time/duration-picker/index.js +8 -8
  45. package/dist/components/inputs/date-time/month-picker/index.d.ts +58 -10
  46. package/dist/components/inputs/date-time/month-picker/index.js +7 -7
  47. package/dist/components/inputs/date-time/time-picker/index.d.ts +56 -10
  48. package/dist/components/inputs/date-time/time-picker/index.js +8 -8
  49. package/dist/components/inputs/field/index.js +2 -2
  50. package/dist/components/inputs/search/index.d.ts +3 -3
  51. package/dist/components/inputs/search/index.js +4 -4
  52. package/dist/components/inputs/select/index.js +2 -2
  53. package/dist/components/inputs/slider/index.d.ts +2 -0
  54. package/dist/components/inputs/slider/index.js +1 -1
  55. package/dist/components/navigation/pagination/index.d.ts +4 -2
  56. package/dist/components/navigation/pagination/index.js +3 -3
  57. package/dist/components/navigation/sidebar/index.js +5 -5
  58. package/dist/components/overlays/alert-dialog/index.d.ts +19 -6
  59. package/dist/components/overlays/alert-dialog/index.js +3 -3
  60. package/dist/components/overlays/command/index.d.ts +25 -20
  61. package/dist/components/overlays/command/index.js +3 -3
  62. package/dist/components/overlays/context-menu/index.js +3 -3
  63. package/dist/components/overlays/dialog/index.d.ts +42 -16
  64. package/dist/components/overlays/dialog/index.js +4 -2
  65. package/dist/components/overlays/dropdown/index.d.ts +5 -5
  66. package/dist/components/overlays/dropdown/index.js +2 -2
  67. package/dist/components/overlays/popover/index.d.ts +7 -3
  68. package/dist/components/overlays/popover/index.js +2 -2
  69. package/dist/components/overlays/sheet/index.d.ts +25 -8
  70. package/dist/components/overlays/sheet/index.js +3 -3
  71. package/dist/components/overlays/toast/index.d.ts +103 -20
  72. package/dist/components/overlays/toast/index.js +6 -2
  73. package/dist/components/overlays/tooltip/index.d.ts +15 -5
  74. package/dist/components/overlays/tooltip/index.js +2 -2
  75. package/dist/components/table/index.d.ts +4 -4
  76. package/dist/components/table/index.js +2 -1
  77. package/dist/index.d.ts +1160 -877
  78. package/dist/index.js +38 -31
  79. package/dist/lib/date-locale.d.ts +13 -1
  80. package/dist/lib/date-locale.js +11 -3
  81. package/dist/lib/scroll-column.d.ts +5 -1
  82. package/dist/lib/scroll-column.js +1 -1
  83. package/dist/lib/slot.d.ts +9 -2
  84. package/dist/lib/slot.js +1 -1
  85. package/dist/shared/{components-q03cktx8.js → components-0x6mx1p5.js} +8 -2
  86. package/dist/shared/{components-gzesg7hy.js → components-30zcc9pe.js} +39 -18
  87. package/dist/shared/components-54nq988h.js +123 -0
  88. package/dist/shared/{components-3sp623ae.js → components-5nsn4w8c.js} +46 -7
  89. package/dist/shared/{components-vsa625vk.js → components-5t2f1kq7.js} +4 -3
  90. package/dist/shared/{components-6zg1yft7.js → components-79q3xxwr.js} +16 -4
  91. package/dist/shared/{components-ds9fqat9.js → components-7xew2ewk.js} +44 -19
  92. package/dist/shared/{components-yz84gpkq.js → components-ajhdkhjz.js} +2 -2
  93. package/dist/shared/{components-jp5v0m0f.js → components-d6vf38m5.js} +7 -4
  94. package/dist/shared/{components-wawnmkph.js → components-dy5w47zg.js} +8 -227
  95. package/dist/shared/{components-9mys9jna.js → components-gjd5v54y.js} +29 -13
  96. package/dist/shared/{components-y6578b8h.js → components-h9vrr1j2.js} +1 -1
  97. package/dist/shared/{components-4n4f5m3e.js → components-hbk69y68.js} +3 -2
  98. package/dist/shared/{components-paa08427.js → components-j71xp9xx.js} +118 -11
  99. package/dist/shared/{components-658b26nb.js → components-jamcmdsd.js} +39 -10
  100. package/dist/shared/{components-52r5763g.js → components-jaw36d5v.js} +39 -18
  101. package/dist/shared/{components-65vwfjs6.js → components-k1b0vhcq.js} +1 -1
  102. package/dist/shared/{components-egy4tyjt.js → components-kr40s3z0.js} +6 -5
  103. package/dist/shared/{components-c96g82th.js → components-m1m46jdv.js} +9 -9
  104. package/dist/shared/{components-k74j4hwh.js → components-mc4d4296.js} +19 -7
  105. package/dist/shared/{components-xcwvgcrd.js → components-mjnzv5vj.js} +3 -3
  106. package/dist/shared/components-mkezgkgk.js +10 -0
  107. package/dist/shared/{components-r6gbmkew.js → components-nbvtmgme.js} +1 -1
  108. package/dist/shared/{components-3wxkjgqn.js → components-ncdgtg6b.js} +40 -2
  109. package/dist/shared/components-peqej82e.js +277 -0
  110. package/dist/shared/{components-bn42vq6d.js → components-q3pbax3p.js} +2 -2
  111. package/dist/shared/{components-jjrj6ymn.js → components-q5h7v6rr.js} +1 -1
  112. package/dist/shared/components-s79c7tq3.js +336 -0
  113. package/dist/shared/{components-fjkpbg6n.js → components-tarz14gk.js} +1 -1
  114. package/dist/shared/{components-1k0wv53d.js → components-v0nz1m4t.js} +41 -12
  115. package/dist/shared/{components-gh0qeey3.js → components-vqpzqwcg.js} +1 -1
  116. package/dist/styles.css +7 -2
  117. package/lib/date-locale.ts +151 -6
  118. package/lib/scroll-column.tsx +53 -24
  119. package/lib/slot.ts +14 -4
  120. package/package.json +1 -1
  121. package/dist/shared/components-559tf7yb.js +0 -28
  122. package/dist/shared/components-rm02h9nt.js +0 -144
  123. package/dist/shared/components-vfjc9jzz.js +0 -8
  124. /package/dist/shared/{components-6j2fh74k.js → components-faww1hy7.js} +0 -0
package/README.md CHANGED
@@ -173,12 +173,12 @@ positioning) are ported.
173
173
  | — (new) | `lib/reduced-motion.ts` | Local addition: `useReduceMotion`, the one reduced-motion read for the morph layer and the liquid indicators — the OS setting, or a `MotionConfig` asking for it (motion's `useReducedMotionConfig` alone ignores the OS without `<MotionConfig reducedMotion="user">`) |
174
174
  | — (new) | `lib/field-classes.ts` | Local addition: the field ladder FF repeats per text field (rest → muted fill on hover or a group's `data-active` → card fill on focus; `data-invalid` keeps a destructive ring), as `fieldVariants` + `fieldIconClasses`, shared by Input, Textarea, Combobox and Sidebar's input; `useFieldBox` (variant, state attributes incl. `readOnly` / `required`, radius, inset) + `focusControlOnPress`, the box Input, Textarea, NumberInput, Combobox's fields and the date/time pickers' field build on, so each takes `variant`; `FieldStateContext`, which hands a `Field`'s state to its control; the pure class tables live in `field-variants.ts` and are re-exported here |
175
175
  | — (new) | `lib/field-variants.ts` | Local addition: the field ladder's pure class tables (box, icon, adornment, action, label/description insets), split from `field-classes.ts` so a React Server Component can call them |
176
- | — (new) | `lib/date-locale.ts` | Local addition: what the date and time pickers read off a react-day-picker (date-fns) locale — the hour cycle (Intl), typed dates, times and months in the locale's formats (date-fns `parse` with `P`…`PPPP`), field names for column labels (`Intl.DisplayNames`) |
176
+ | — (new) | `lib/date-locale.ts` | Local addition: what the date and time pickers read off a react-day-picker (date-fns) locale — the hour cycle (Intl), typed dates, times and months in the locale's formats (date-fns `parse` with `P`…`PPPP`), field names for column labels (`Intl.DisplayNames`); `resolveLocale` (a BCP 47 tag → an Intl-backed date-fns Locale, after utegsk/ui's `localeFromTag`), `formatTime` on an explicit hour cycle, `parseDateTimeText` / `parseMonthNameText` |
177
177
  | — (new) | `lib/row-order.tsx` | Local addition: document-order row numbering for menus and lists (`useRowOrder`, `useRowSlot`, `RowOrderContext`), so a row's `index` is optional; unlike `row-index.tsx` (direct children) it reaches rows anywhere below the panel; labels, separators and groups are numbered without a rect, so a selection run never melts across them. Lifted out of Dropdown |
178
178
  | — (new) | `lib/inert.ts` | Local addition: `inert` that holds on React 18 and 19 (18 passes it through as an unknown attribute and needs `''`, 19 reads a boolean and drops `''`): `inertProps(on)` to spread, `markInert` as a ref for an element that is always inert; Sidebar's folded and collapsed parts, Tabs' ghost label |
179
179
  | — (new) | `lib/focus-ring.tsx` | Local addition: `FocusRing`, the one keyboard ring a list panel draws, 2px outside the focused row and springing between rows (a snap under reduced motion); radius from the surrounding shape, or pinned with `shape` (popups pin `rounded`); Dropdown and its submenus, NavigationMenu, Select |
180
- | — (new) | `lib/scroll-column.tsx` | Local addition: `ScrollColumn`, the scrolling option list beside a calendar (months, years, hours, minutes) or a strip above it: the pick gets a dot, a run of `inView` options one band, the followed option stays centred; fluid hover, listbox semantics, arrow keys; `dividedColumns` puts a hairline between neighbours |
181
- | — (new) | `lib/slot.ts` | Local addition: Dialog's `render` / `asChild` slot shape (`SlotProps`, `slotRender`), shared by every overlay part that wraps a control |
180
+ | — (new) | `lib/scroll-column.tsx` | Local addition: `ScrollColumn`, the scrolling option list beside a calendar (months, years, hours, minutes) or a strip above it: the pick gets a dot, a run of `inView` options one band, the followed option stays centred; fluid hover, listbox semantics, arrow keys; `dividedColumns` puts a hairline between neighbours; `layout="grid"` (`columns` 2–6, 2-D fluid hover, row-wise arrow keys) after utegsk/ui's 0.14 grid picker |
181
+ | — (new) | `lib/slot.ts` | Local addition: Dialog's `render` / `asChild` slot shape (`SlotProps`, `slotRender`), shared by every overlay part that wraps a control; `children` fill a `render` element that has none of its own (Base UI semantics), and `nativeButton` reaches the Base UI triggers and closes (`false` for a `<div>` or link) |
182
182
  | — (new) | `lib/morph-part.tsx` | Local addition: `MorphPart` (shared parts over a motion `layoutId`, after motion-primitives' morphing dialog) and the scope contexts overlays provide |
183
183
  | — (new) | `lib/goo-indicator.tsx` | Local addition: `GooIndicator` (melts between items, a lingering blob follows it) and `GooLayer` (goo over the shapes' coverage, filled with their own token, on only while moving), on the engine's `GOO_MATRIX` / `GOO_BLUR_RATIO` |
184
184
  | — (new) | `lib/morph-layers.tsx` | Local addition: `MorphSurface`, the renderer (shapes layer with beUI's goo filter plus a `--shadow-color` hairline around the melted shape, surface background and shadow, clipped content) |
@@ -233,21 +233,21 @@ only what is specific to it:
233
233
  | Item | Upstream | Local | Specific modifications |
234
234
  |---|---|---|---|
235
235
  | `Accordion` | `registry/base/accordion.tsx` | `components/accordion/index.tsx` | open state runs through `useControllableState` (see [Behavior additions](#behavior-additions)); standalone `Accordion` accepts `highlight` and passes it to its items as their default; the panel morphs out of its row (a shape grows from the press point into the item, content clipped by it, layout height on its bottom edge; the `"item"` tint is that shape melted into the row through `GooFilter`), `effect` / `tier` on `Accordion.Content`; the group's hover highlight and focus ring snap with reflowed rows; statics `.Group/.Item/.Trigger/.Content` |
236
- | `AlertDialog` | — (local) | `components/overlays/alert-dialog/index.tsx` (+ `pending-demo.tsx`) | Base UI AlertDialog root (always modal, no outside-press dismissal) over Dialog's own parts and `DialogState`, so it morphs like Dialog; `AlertDialog.Content` drops the ✕ by default; statics as Dialog's; `AlertDialog.Action` (promise-aware confirm: Button `loading`, every close blocked via `details.cancel()` while pending, closes on resolve, `onError` on reject; a controlled `pending` makes a plain `onClick` close once `pending` goes true and back to false) and `AlertDialog.Cancel` (Close as a ghost Button, disabled while pending); `pending-demo.tsx` is the docs stage |
236
+ | `AlertDialog` | — (local) | `components/overlays/alert-dialog/index.tsx` (+ `pending-demo.tsx`) | Base UI AlertDialog root (always modal, no outside-press dismissal) over Dialog's own parts and `DialogState`, so it morphs like Dialog; `AlertDialog.Content` drops the ✕ by default; statics as Dialog's; `AlertDialog.Action` (promise-aware confirm: Button `loading`, every close blocked via `details.cancel()` while pending, closes on resolve, `onError` on reject; a controlled `pending` makes a plain `onClick` close once `pending` goes true and back to false) and `AlertDialog.Cancel` (Close as a ghost Button, disabled while pending); `pending-demo.tsx` is the docs stage; `onOpenChange(open, details)` with Base UI's change details (an action's own close reports `'imperative-action'`) |
237
237
  | `Avatar` | — (local) | `components/avatar/index.tsx` | Base UI Avatar (image loading status, the fallback's `delay`); one control tall (36 / 28px compact), `shape` circle or square; a 1px inset image edge after FF's media outline (`outline-image-edge`); the image fades in on the fast tier; statics `.Image/.Fallback` |
238
238
  | `Badge` | `registry/default/badge.tsx` | `components/badge/index.tsx` | `variant` re-declared (it came from cva's `VariantProps` upstream); changes animate: a new label or variant springs the width (inner content span, `spring.moderate`) while the label blur-crossfades, a new color fades, the dot scales in and out; "Changes" example over a local `change-demo.tsx` stage; `badgeColors`, `badgeVariants` and the size aliases moved to `variants.ts` (server-callable) and re-exported; local variant `outline` (the dot badge's neutral hairline without the dot; `color` has no effect) |
239
239
  | `Button` | `registry/base/button.tsx` | `components/button/index.tsx` | `variant`/`disabled`/`children` re-declared; extra Sizes / Active / As-child / Loading morph examples; `loading` folds the surface into a circle around the spinner and back (`spring.moderate`, box unchanged), label and spinner crossfade, `aria-busy`, the disabled dim fades; "Loading morph" example over a local `loading-demo.tsx` stage; also serves modo's chrome Button (`shell.Button`); `buttonVariants` and the size aliases moved to `variants.ts` (server-callable) and re-exported; local variants for shadcn-style hosts: `outline` (renders as `tertiary`), `destructive` / `success` (their `-strong` hue as text on a translucent wash that deepens on hover and press, never a solid fill), `link` (underlined foreground text on the control height); size `xs` → `compact`; `buttonVariants` resolves the size aliases and takes `surface` (fill/ring/wash/hover/press/`aria-disabled` as root classes, for an anchor host); `loading` keeps focus (`focusableWhenDisabled`; `asChild` sets `aria-disabled` and blocks clicks) |
240
- | `Calendar` | — (local, on react-day-picker) | `components/inputs/date-time/calendar/index.tsx` (+ `use-month-window.ts`) | DayPicker props pass through (as shadcn's Calendar); styled through `classNames` and custom `Month` / `Day` / `DayButton` (no react-day-picker stylesheet); one scrolling list of months rendered as a 25-month window that moves as you scroll; a pinned weekday row, muted weekends, today's dot; FF's fills (the pick `bg-foreground text-background` like the primary button, a range `bg-active`, its preview `bg-hover`); month and year columns that mark the month being read and jump on click, before or after the days (`columnsSide`), turning into strips when narrow; `extraColumns` / `extraColumnsSide` for the date picker's time |
240
+ | `Calendar` | — (local, on react-day-picker) | `components/inputs/date-time/calendar/index.tsx` (+ `use-month-window.ts`) | DayPicker props pass through (as shadcn's Calendar); styled through `classNames` and custom `Month` / `Day` / `DayButton` (no react-day-picker stylesheet); one scrolling list of months rendered as a 25-month window that moves as you scroll; a pinned weekday row, muted weekends, today's dot; FF's fills (the pick `bg-foreground text-background` like the primary button, a range `bg-active`, its preview `bg-hover`); month and year columns that mark the month being read and jump on click, before or after the days (`columnsSide`), turning into strips when narrow; `extraColumns` / `extraColumnsSide` for the date picker's time; `columnLabels`; a named `CalendarProps` and a `ReactElement` return |
241
241
  | `Card` | `registry/default/card.tsx` | `components/card/index.tsx` | `next/link` → plain anchors (stretched card link, `CardButton`'s `href` form); statics `.Group/.Header/.Title/.Description/.Action/.Content/.Footer/.Media/.Image/.Eyebrow/.Feature/.Button/.Divider`; local `border="outlined"` on a standalone card (the outlined group's hairline frame; inside a group the group decides); local `Card.Divider` (a full-bleed hairline between the card's parts, vertical in an inline card; the Separator primitive) |
242
242
  | `Checkbox` (upstream `CheckboxItem`), `Checkbox.Group` (upstream `CheckboxGroup`) | `registry/base/checkbox-group.tsx` | `components/inputs/checkbox/index.tsx` | renamed; the row stands alone too (own state from `defaultChecked`, `onCheckedChange`, own hover and focus ring, `size`); rows numbered by the group (`lib/row-index.tsx`), so `index` is optional; uncontrolled mode + group-level callback; item `checked`/`onToggle` optional, falling back to group context; arrow-key focus guarded for `noUncheckedIndexedAccess`; shared `useSelectionRuns`, picks melt and split through the goo; `label` optional, any node (named via `aria-labelledby`); without a label and outside a group the checkbox is a bare box, Base UI's Checkbox.Root itself (`id` / `<label htmlFor>`, `aria-label`, `name` / `value` / `required` for forms), keeping its border when checked; `indeterminate` / `checked="indeterminate"` shows the icon set's `minus`, swapped with the self-drawing check in one cell; `disabled` dims a row and takes it out of the group's hover and arrow keys |
243
243
  | `ColorPicker` | `registry/default/color-picker.tsx` | `components/inputs/color-picker/index.tsx` | `@/registry/radix/{slider,tooltip}` → the Base siblings `../slider` / `../../overlays/tooltip`; `noUncheckedIndexedAccess` guards on regex captures, `rgb()`/`hsl()`/`oklch()` parts, `itemRects[i]` and the Slider's array value; static `.Popover`; uncontrolled support was already upstream (verified, kept); `ColorPicker.Popover` (goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier`) and the format menu morph out of their triggers, replacing the motion wrappers and the ignored `actionsRef`; the color types and math (`parseColor`, `buildParsed`, conversions, formatting) in `variants.ts` (server-callable) and re-exported |
244
244
  | `Combobox` | `registry/base/combobox.tsx` | `components/inputs/combobox/index.tsx` | root de-generified (the parser needs a plain function plus a same-file `interface ComboboxProps`): items gained an index signature, `value`/`defaultValue` are `string \| string[]` and `onValueChange` is a union of the two handler shapes; `ComboboxValue<Multiple>` still exported; `creatable` added; `React.*` type refs replaced by named type imports; statics `.Input/.Chips/.Content/.List/.Item/.Empty`; the popup morphs out of the field (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Combobox.Content`), replacing the `scaleY` wrapper and the `actionsRef` deferred unmount; `loading` (the chevron blurs into a `loader-circle` spinner, a status row under the list, the empty message held back) and `loadingLabel`; controlled query (`inputValue` / `defaultInputValue` / `onInputValueChange`); `filter={null}` shows the items as given; `readOnly` (Base UI's, plus the field box's read-only state; no clear or chip ✕); controlled popup (`open` / `defaultOpen` / `onOpenChange`); `footer` on `Combobox.Content` (a node or `({ close, query }) => node`); object items' `label` may be any node (unclipped in rows and chips) with `textValue` to filter, fill the field and name the chip |
245
- | `Command` | — (local) | `components/overlays/command/index.tsx` | Base UI Autocomplete rendered inline in Dialog's panel (`position="top"`, no ✕), morphing from its trigger with goo; grouped/flat command data filtered by label + keywords; one fluid highlight follows Base UI's highlight; ⌘K/Ctrl+K `hotkey` (one palette answers a press); fresh palette per open; statics `.Trigger/.Content/.Palette/.Shortcut`; `CommandList`/`CommandListRoot` exported for Search |
245
+ | `Command` | — (local) | `components/overlays/command/index.tsx` | Base UI Autocomplete rendered inline in Dialog's panel (`position="top"`, no ✕), morphing from its trigger with goo; grouped/flat command data filtered by label + keywords; one fluid highlight follows Base UI's highlight; ⌘K/Ctrl+K `hotkey` (one palette answers a press); fresh palette per open; statics `.Trigger/.Content/.Palette/.Shortcut`; `CommandList`/`CommandListRoot` exported for Search; `onOpenChange(open, details)` with Base UI's change details (`reason`, `event`, `cancel()`) |
246
246
  | `ContextMenu` | — (local; see [Morph sources](#morph-sources)) | `components/overlays/context-menu/index.tsx` | Base UI ContextMenu (right click, long press) with the morph layer growing from the press point (`from="pointer"` default, goo on `spring.goo`); rows are Dropdown's `MenuItem`, `Dropdown.Label`, `Dropdown.Separator` with fluid hover; `ContextMenu.Item` adds `shortcut`; `size` on the root; statics `.Trigger/.Content/.Item/.Label/.Separator` |
247
- | `Dialog` | `registry/base/dialog.tsx` | `components/overlays/dialog/index.tsx` | `DialogProps` and `DialogContentProps` fully re-declared (the slot props now come from `lib/slot.ts`); statics attached as expando properties (`Dialog.Trigger = …`), so `<Dialog.Content>` types without a cast; FF's "With a sidebar" example replaced by "Surfaces inside a dialog" (written before the Sidebar was ported); the panel morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Dialog.Content`), the backdrop fades with the morph's progress, the root holds the open state and scopes `Morph.Part` pairs |
247
+ | `Dialog` | `registry/base/dialog.tsx` | `components/overlays/dialog/index.tsx` | `DialogProps` and `DialogContentProps` fully re-declared (the slot props now come from `lib/slot.ts`); statics attached as expando properties (`Dialog.Trigger = …`), so `<Dialog.Content>` types without a cast; FF's "With a sidebar" example replaced by "Surfaces inside a dialog" (written before the Sidebar was ported); the panel morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Dialog.Content`), the backdrop fades with the morph's progress, the root holds the open state and scopes `Morph.Part` pairs; `onOpenChange(open, details)` hears Base UI's change details and can `cancel()` (one-argument handlers still work), and `createDialogChangeDetails` builds them for the library's own changes; `disablePointerDismissal` on the root; `Dialog.Content` types and forwards `initialFocus` / `finalFocus` and takes `popupClassName` (merged after the popup's width) |
248
248
  | `Dropdown` | `registry/base/dropdown.tsx` | `components/overlays/dropdown/index.tsx` | uncontrolled selection and uncontrolled search filtering (below); `items[next].focus()` guarded; statics `.Menu/.Trigger/.Content/.Item/.Label/.Separator/.Search/.Empty`; FF's "Create from the query" section skipped — adding a row to the list is real consumer state; the popup menu morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Dropdown.Content`), replacing the `scaleY` wrapper and the ignored `actionsRef`; popup rows hand activation to the Menu item's `onClick` so Enter / Space pick; shadcn DropdownMenu parity: children rows with an optional index (`lib/row-order.tsx`), `inset` / `shortcut` rows, a cancelable `onSelect(event)`, `asChild` on the trigger, a keyboard focus ring, `keepMounted`, and the parts in `menu-parts.tsx` (see the headers) |
249
- | `DatePicker`, `DateRangePicker`, `TimePicker`, `DurationPicker`, `MonthPicker` | — (local; props after shadcn's date picker and utegsk/ui's pickers) | `components/inputs/date-time/<id>/index.tsx` (+ the group's `picker-shell.tsx`, `time-columns.tsx`) | a field on the field ladder whose panel morphs out of it with Combobox's options (goo by default; `from` / `effect` / `hideSource` / `tier`, in place with `effect="morph"` + `hideSource`), or a bottom `Sheet` under 640px; one hour column with AM/PM on a 12-hour clock; `columnsSide` / `timeColumnsSide`; the panel header types the value in the locale's formats; `value` / `defaultValue` / `onValueChange` with `null` for empty — a `Date`, react-day-picker's `DateRange`, `"HH:mm"`, a date-fns `Duration`, a month's first day; `locale` is a react-day-picker locale throughout; `picker-shell.tsx` takes an `editor`, `triggerLabel` and `readOnly` |
250
- | `DatePicker` | — (local; the masked input after utegsk/ui's `MaskedDateInput`) | `components/inputs/date-time/date-picker/index.tsx` (+ `masked-date-input.tsx`) | `editable` masked input in the field (Escape restores the last real date), `labels`, `formatDate`, `actions`, `readOnly`, string values (date-only strings read as local days) with an invalid state |
249
+ | `DatePicker`, `DateRangePicker`, `TimePicker`, `DurationPicker`, `MonthPicker` | — (local; props after shadcn's date picker and utegsk/ui's pickers) | `components/inputs/date-time/<id>/index.tsx` (+ the group's `picker-shell.tsx`, `time-columns.tsx`) | a field on the field ladder whose panel morphs out of it with Combobox's options (goo by default; `from` / `effect` / `hideSource` / `tier`, in place with `effect="morph"` + `hideSource`), or a bottom `Sheet` under 640px; one hour column with AM/PM on a 12-hour clock; `columnsSide` / `timeColumnsSide`; the panel header types the value in the locale's formats; `value` / `defaultValue` / `onValueChange` with `null` for empty — a `Date`, react-day-picker's `DateRange`, `"HH:mm"`, a date-fns `Duration`, a month's first day; `locale` is a react-day-picker locale throughout; `picker-shell.tsx` takes an `editor`, `triggerLabel` and `readOnly`; `picker-shell.tsx` also takes `controlRef` / `controlProps` / `formValues` / `required` (visually hidden inputs for native forms) and exports `PickerControlProps` (form + aria props every picker forwards); every picker is a `forwardRef` with `readOnly`, `labels`, string `locale`; `DateRangePicker` adds `editable`, `withTime`, `hourCycle`, `hidden`, `startName` / `endName`, `startInputProps` / `endInputProps` |
250
+ | `DatePicker` | — (local; the masked input after utegsk/ui's `MaskedDateInput`) | `components/inputs/date-time/date-picker/index.tsx` (+ `masked-date-input.tsx`) | `editable` masked input in the field (Escape restores the last real date), `labels`, `formatDate`, `actions`, `readOnly`, string values (date-only strings read as local days) with an invalid state; `hidden`, `required` (no deselect), `unselectable` (`masked-date-input.tsx` takes `clearable`) |
251
251
  | — | `registry/default/menu-item.tsx` | `components/overlays/dropdown/menu-item.tsx` | a row with no `checked` derives it from a self-managed panel and toggles the panel's state; `sourceIndex` carries the authored index so selection survives filter re-indexing; `label` and `index` optional (`children` rows numbered in document order), `inset`, `shortcut`, cancelable `onSelect(event)`; checkbox and radio rows report to the panel for the merged selection background |
252
252
  | — | — (local) | `components/overlays/dropdown/menu-parts.tsx` | `Dropdown.Group`, the `Dropdown.Shortcut` re-export, value-keyed `Dropdown.CheckboxItem`, `Dropdown.RadioGroup` / `Dropdown.RadioItem`, `Dropdown.Sub` / `.SubTrigger` / `.SubContent` (a submenu morphs out of its row), and `useMenuPanel`, the state both popups share (keyboard focus ring, the submenu trigger's pinned highlight, value-checked backgrounds) |
253
253
  | — | `registry/default/dropdown-search.tsx` | `components/overlays/dropdown/dropdown-search.tsx` | `filter` mode: `value`/`onValueChange` optional, the field reads and writes the query held by the panel (`DropdownFilterContext`) |
@@ -267,17 +267,17 @@ only what is specific to it:
267
267
  | `Radio` (upstream `RadioItem`), `Radio.Group` (upstream `RadioGroup`) | `registry/base/radio-group.tsx` | `components/inputs/radio/index.tsx` | renamed; rows numbered by the group (`lib/row-index.tsx`), so `index` is optional; uncontrolled index and value modes; group context carries a `selectIndex` setter every item calls, so items need no `selected`/`onSelect`; arrow-key focus guarded; selected background is a `GooIndicator`; `label` optional, any node; a `Radio` without one is a bare circle, Base UI's Radio.Root (picked by `value`, `id` / `<label htmlFor>`, keeping its border when picked) laid out freely inside a value-mode group, which then draws no rows and leaves the arrow keys to Base UI; `disabled` on a row, or on the group (which dims as a whole); the group's `name` / `required` reach Base UI's RadioGroup for forms |
268
268
  | `Search` | — (local; after beUI's morphing search, see [Morph sources](#morph-sources)) | `components/inputs/search/index.tsx` | pill or icon button springing its own width in place (`spring.slow`), growing over its neighbors (`align`); with `items`, Autocomplete suggestions ooze out of the field via the morph layer (goo on `spring.goo`, `effect`/`tier`) with Command's rows; Escape closes → clears → collapses; blur collapses when empty; `shortcut`; `<search>` landmark + form, `onSubmit` |
269
269
  | `Select` | `registry/base/select.tsx` | `components/inputs/select/index.tsx` | members re-declared one per line (the parser drops members whose type spans lines); upstream's uncontrolled `defaultValue` verified and kept rather than rerouted through `useControllableState`; expando statics `.Trigger/.Content/.Item/.Group/.Label/.Separator`; the popup morphs out of its trigger (morph layer, goo on `spring.goo`; `from` / `effect` / `hideSource` / `tier` on `Select.Content`), replacing the `scaleY` wrapper and the `actionsRef` deferred unmount; `Select.Item`'s `index` is optional: rows take their place in document order (`lib/row-order.tsx`), inside groups, conditionals and maps, and the checked row is found by its own index; `textValue` for typeahead; a node label is shown as is (unclipped) in the rows and, through Base UI's `items`, in the trigger; `Select.Value` part, with `Select.Trigger` children replacing its value slot; `side` / `align` / `sideOffset` on `Select.Content`; `triggerVariants` in `variants.ts` (server-callable) and re-exported; the keyboard ring is the shared `lib/focus-ring.tsx` |
270
- | `Sheet` | — (local; replaces beUI's bottom sheet and drawer, see [Morph sources](#morph-sources)) | `components/overlays/sheet/index.tsx` | Base UI Drawer (swipe to dismiss, snap points, `Drawer.Viewport` for the swipe) with the morph growing from the sheet's own edge; a swipe-dismissed sheet slides on off its edge from the release point (the morph's `exit`); `side`, `snapPoints`, `defaultSnapPoint`; Dialog's parts and `DialogState` (Base UI's Drawer parts are Dialog's); statics as Dialog's plus `.Provider` / `.Indent` |
270
+ | `Sheet` | — (local; replaces beUI's bottom sheet and drawer, see [Morph sources](#morph-sources)) | `components/overlays/sheet/index.tsx` | Base UI Drawer (swipe to dismiss, snap points, `Drawer.Viewport` for the swipe) with the morph growing from the sheet's own edge; a swipe-dismissed sheet slides on off its edge from the release point (the morph's `exit`); `side`, `snapPoints`, `defaultSnapPoint`; Dialog's parts and `DialogState` (Base UI's Drawer parts are Dialog's); statics as Dialog's plus `.Provider` / `.Indent`; `onOpenChange(open, details)` with Base UI's change details (`reason`, `event`, `cancel()`); `disablePointerDismissal` on the root; `Sheet.Content` takes `side` (overrides the root's, the swipe follows), `initialFocus` / `finalFocus` and `popupClassName` (merged after `PLACEMENT`) |
271
271
  | `Sidebar` | `registry/base/sidebar.tsx` (+ `registry/default/sidebar-core.tsx`, `sidebar-menu.tsx`; `app/globals.css` `.scroll-divider`) | `components/navigation/sidebar/index.tsx` (+ `sidebar-core.tsx`, `sidebar-menu.tsx`, `sidebar.css`) | the mobile sheet is the DS's `Sheet` (goo from its edge, swipe to dismiss) instead of a Dialog with a framer slide; no collapsed peek: `openOnHover` on the provider opens the sidebar in flow while the pointer is between its edge and the far side of a trigger beside it, the trigger switching between open and on hover (`panel-*-dashed` glyphs added to the icon context); the active row backgrounds are `GooIndicator`s and the hover highlight the liquid `FluidHoverHighlight`; uncontrolled selection (`value` on the buttons, `value` / `defaultValue` / `onValueChange` on `Sidebar`) and collapsible rows (`Sidebar.MenuItem collapsible`, below); statics `.Provider/.Trigger/.Rail/.Inset/.Input/.Header/.Content/.Footer/.Separator/.Group/.GroupLabel/.GroupAction/.GroupActions/.GroupContent/.Menu/.MenuItem/.MenuButton/.MenuAction/.MenuActions/.MenuBadge/.MenuSkeleton/.MenuSub/.MenuSubItem/.MenuSubButton` through a `SidebarComponent` cast; `fontVariationSettings` → `weight-*`, `text-[Npx]` → type roles, hex focus ring → `ring-focus-ring`, `duration-80\|160\|240` → tiers; `sidebarMenuButtonVariants` and the `SIDEBAR_*` constants in `variants.ts` (server-callable; `variants.ts` files are bunup entries); `inert` through `lib/inert.ts` |
272
- | `Slider` | `registry/base/slider.tsx` | `components/inputs/slider/index.tsx` | `var(--color-accent)` → `var(--accent)` (no `--color-*` aliases here); uncontrolled support on the public wrapper, with both engines still fully controlled; `SliderProps extends Omit<SliderEngineProps, "value" \| "onChange">`; React 18 ref types (`MutableRefObject`); indexed reads of the value/step/pip arrays asserted |
272
+ | `Slider` | `registry/base/slider.tsx` | `components/inputs/slider/index.tsx` | `var(--color-accent)` → `var(--accent)` (no `--color-*` aliases here); uncontrolled support on the public wrapper, with both engines still fully controlled; `SliderProps extends Omit<SliderEngineProps, "value" \| "onChange">`; React 18 ref types (`MutableRefObject`); indexed reads of the value/step/pip arrays asserted; `aria-label` names the thumbs ahead of the visible `label` |
273
273
  | `Switch` | `registry/base/switch.tsx` | `components/inputs/switch/index.tsx` | uncontrolled support; every in-body read goes through the resolved `isChecked` and a `toggle()` that also calls `onToggle`; the thumb is a `GooIndicator` in Base UI's Thumb (stretches as it travels), dragged x held in state; local `description` (muted line, `aria-describedby`) and `trackSide`; local `Switch.Group`: switches as settings rows (track at the end) in one outlined fieldset, after an outlined `Card.Group` (shared hairlines dropped around the fluid hover highlight), rows numbered by the group; `label` optional, any node; without one (outside a group) the switch is the bare track, Base UI's Switch.Root, which toggles itself (click, keyboard, a `<label htmlFor>`) through `onCheckedChange`, with the thumb drag on the track and the toggle that follows a drag swallowed; `name` / `value` / `required` reach Base UI's hidden input in both modes |
274
- | `Table` | `registry/default/table.tsx` | `components/table/index.tsx` (+ `sort-demo.tsx`) | statics `.Header/.Body/.Row/.Head/.Cell`; `TableProps` / `TableRowProps` exported; local additions: Footer, Caption, Empty, sortable Head (`sortDirection`/`defaultSortDirection`/`onSort`), `stacked` + Cell `label`, `containerClassName` |
274
+ | `Table` | `registry/default/table.tsx` | `components/table/index.tsx` (+ `sort-demo.tsx`) | statics `.Header/.Body/.Row/.Head/.Cell`; `TableProps` / `TableRowProps` exported; local additions: Footer, Caption, Empty, sortable Head (`sortDirection`/`defaultSortDirection`/`onSort`), `stacked` + Cell `label` (stacked cells `items-start`), `containerClassName`; unindexed body rows numbered in document order (`lib/row-order`) |
275
275
  | `Tabs` | `registry/base/tabs.tsx` | `components/navigation/tabs/index.tsx` | `defaultSelectedIndex` added as the index-mode twin of the existing `defaultValue`; indicator and hover pill are `GooIndicator`s (the hover pill returns to the active tab as it fades); statics `.List/.Item/.Panel`; `Tabs.Item` node `label` (inert ghost copy), `href` link tabs |
276
276
  | `TabsSubtle` | `registry/base/tabs-subtle.tsx` | `components/navigation/tabs-subtle/index.tsx` | uncontrolled mode; panels may be authored inside the root (below); the tab list's ref write goes through `MutableRefObject`; selected and hover pills are `GooIndicator`s; statics `.Item/.Panel` |
277
277
  | `Textarea` | — (local; Input's multi-line twin) | `components/inputs/textarea/index.tsx` | Base UI `Field.Control` rendered as a `<textarea>`; the field ladder from `lib/field-classes.ts`; padding sized so one row matches the control height; grows from `minRows` to `maxRows` (InputMessage's measure), then scrolls; `value` / `defaultValue` / `onChange`; `readOnly` / `required` (also from a surrounding `Field`) |
278
278
  | `ThinkingIndicator` | `registry/default/thinking-indicator.tsx` | `components/AI/thinking-indicator/index.tsx` | upstream prop JSDoc kept (it already matches the FF API table); the `.shimmer-text` rule it rides lives in `base.css`; local `variant="goo"` (three dots merging and splitting through `GooFilter`, plain label; default stays `shimmer`); reduced motion also follows MotionConfig |
279
- | `Toast` | — (local; after Sileo, see [Morph sources](#morph-sources)) | `components/overlays/toast/index.tsx` (+ `manager.ts`, `global-demo.tsx`) | Base UI Toast (queue, timers, swipe, announcements) with Sileo's pill melting into a body through the shared `GooFilter`; autopilot expand/collapse, six states on the `--status-*` tokens, promise toasts; `Toast` is the provider + viewport, `Toast.Trigger` shows one, `useToast()` the in-tree API (plus `toasts`, `update`, `dismiss`); `manager.ts` holds one module-level Base UI `createToastManager()` exported as the global `toast()` (`.success/.error/.warning/.info/.loading/.promise/.update/.dismiss`, each returning `{ id, update, dismiss }`), queuing calls made before a provider subscribes (dropped on the server); `<Toast manager={toast}>` renders them (`manager` takes any source with a `subscribe`); ReactNode title/description, `icon` as a component or node, `action` as `{ label, onClick }` or a node, `duration` as an alias of `timeout`; a loading toast stays until its type changes, then leaves on the timeout; status glyphs via `useIcons()` instead of direct lucide imports; `global-demo.tsx` is the docs stage |
280
- | `Tooltip` | `registry/base/tooltip.tsx` | `components/overlays/tooltip/index.tsx` | `className`/`children` docs filled in; statics `.Provider` / `.PortalContainer`; the slide + fade replaced by the morph layer (goo from the trigger on `spring.goo`, `from` / `effect` / `tier` props; `followCursor` tooltips fade); a hand-off between grouped tooltips skips the morph (Base UI's `data-instant`); local compound parts `TooltipRoot` (`open` / `defaultOpen` / `onOpenChange`, `delay` or `delayDuration`, `closeDelay`, `followCursor`), `TooltipTrigger` (`render` or Radix-style `asChild`) and `TooltipContent` (`side`, `align`, `sideOffset`, `alignOffset`, `positionerClassName`, morph options, `render` / `asChild` for the label, rest props on the popup), also as statics `.Root .Trigger .Content`; `Tooltip` without `content` is the root, and the flat form gains `open` / `defaultOpen` / `align` / `delay` / `closeDelay`; `TooltipProvider` takes `delay` / `closeDelay` / `timeout` and Radix's `delayDuration` / `skipDelayDuration` (Base UI name wins; a negative skip clamps to 0) |
279
+ | `Toast` | — (local; after Sileo, see [Morph sources](#morph-sources)) | `components/overlays/toast/index.tsx` (+ `manager.ts`, `global-demo.tsx`) | Base UI Toast (queue, timers, swipe, announcements) with Sileo's pill melting into a body through the shared `GooFilter`; autopilot expand/collapse, six states on the `--status-*` tokens, promise toasts; `Toast` is the provider + viewport, `Toast.Trigger` shows one, `useToast()` the in-tree API (plus `toasts`, `update`, `dismiss`); `manager.ts` holds one module-level Base UI `createToastManager()` exported as the global `toast()` (`.success/.error/.warning/.info/.loading/.promise/.update/.dismiss`, each returning `{ id, update, dismiss }`), queuing calls made before a provider subscribes (dropped on the server); `<Toast manager={toast}>` renders them (`manager` takes any source with a `subscribe`); ReactNode title/description, `icon` as a component or node, `action` as `{ label, onClick }` or a node, `duration` as an alias of `timeout`; a loading toast stays until its type changes, then leaves on the timeout; status glyphs via `useIcons()` instead of direct lucide imports; `global-demo.tsx` is the docs stage; `Toast.Action` / `Toast.Close` (Base UI's parts as compact Buttons, `render` / `asChild`) for custom action nodes |
280
+ | `Tooltip` | `registry/base/tooltip.tsx` | `components/overlays/tooltip/index.tsx` | `className`/`children` docs filled in; statics `.Provider` / `.PortalContainer`; the slide + fade replaced by the morph layer (goo from the trigger on `spring.goo`, `from` / `effect` / `tier` props; `followCursor` tooltips fade); a hand-off between grouped tooltips skips the morph (Base UI's `data-instant`); local compound parts `TooltipRoot` (`open` / `defaultOpen` / `onOpenChange`, `delay` or `delayDuration`, `closeDelay`, `followCursor`), `TooltipTrigger` (`render` or Radix-style `asChild`; drops the slot's `nativeButton`) and `TooltipContent` (`side`, `align`, `sideOffset`, `alignOffset`, `collisionPadding`, `positionerClassName`, morph options, `render` / `asChild` for the label, rest props on the popup), also as statics `.Root .Trigger .Content`; `Tooltip` without `content` is the root, and the flat form gains `open` / `defaultOpen` / `align` / `alignOffset` / `collisionPadding` / `delay` / `closeDelay`; `TooltipProvider` takes `delay` / `closeDelay` / `timeout` and Radix's `delayDuration` / `skipDelayDuration` (Base UI name wins; a negative skip clamps to 0) |
281
281
 
282
282
  #### blocks
283
283
 
@@ -16,7 +16,17 @@ import {
16
16
  startOfWeek,
17
17
  startOfYear,
18
18
  } from 'date-fns'
19
- import { type ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
19
+ import {
20
+ Fragment,
21
+ type ReactElement,
22
+ type ReactNode,
23
+ useCallback,
24
+ useEffect,
25
+ useLayoutEffect,
26
+ useMemo,
27
+ useRef,
28
+ useState,
29
+ } from 'react'
20
30
  import { type DayButtonProps, DayPicker, type DayPickerProps, type DayProps, type MonthProps } from 'react-day-picker'
21
31
  import { defaultLocale, fieldLabel } from '../../../../lib/date-locale'
22
32
  import { dividedColumns, ScrollColumn } from '../../../../lib/scroll-column'
@@ -95,6 +105,27 @@ function DayButton({ day: _, modifiers, className, ...props }: DayButtonProps) {
95
105
  )
96
106
  }
97
107
 
108
+ /** The Calendar's own props, beside DayPicker's. */
109
+ interface CalendarOwnProps {
110
+ /** The locale: a react-day-picker / date-fns locale (`import { de } from 'react-day-picker/locale'`). Names months and weekdays, picks the first day of the week. Defaults to `enUS`. */
111
+ locale?: Locale
112
+ /** Pins the calendar to one step of the size ladder (36px days, compact 28px). Defaults to the surrounding SizeProvider. */
113
+ size?: SizeVariant
114
+ /** Which side of the days the month and year columns stand on; the month column keeps next to the days. Defaults to `'end'`. */
115
+ columnsSide?: 'start' | 'end'
116
+ /** Accessible names of the month and year columns (`{ month, year }`). Default to the locale's names for the fields. */
117
+ columnLabels?: { month?: string; year?: string }
118
+ /** More columns, given the layout's orientation — the date picker's hours and minutes. */
119
+ extraColumns?: (orientation: 'vertical' | 'horizontal') => ReactNode
120
+ /** Which side of the days the extra columns stand on, outside the month and year columns when they share it. Defaults to `'end'`. */
121
+ extraColumnsSide?: 'start' | 'end'
122
+ /** Classes for the calendar's box. */
123
+ className?: string
124
+ }
125
+
126
+ /** DayPicker's props (`mode`, `selected`, `onSelect`, matchers…) and the Calendar's own. */
127
+ type CalendarProps = DayPickerProps & CalendarOwnProps
128
+
98
129
  /**
99
130
  * A calendar that scrolls through the months instead of paging, with month
100
131
  * and year columns to jump by.
@@ -122,24 +153,12 @@ function Calendar({
122
153
  locale = defaultLocale,
123
154
  size,
124
155
  columnsSide = 'end',
156
+ columnLabels,
125
157
  extraColumns,
126
158
  extraColumnsSide = 'end',
127
159
  className,
128
160
  ...props
129
- }: DayPickerProps & {
130
- /** The locale: a react-day-picker / date-fns locale (`import { de } from 'react-day-picker/locale'`). Names months and weekdays, picks the first day of the week. Defaults to `enUS`. */
131
- locale?: Locale
132
- /** Pins the calendar to one step of the size ladder (36px days, compact 28px). Defaults to the surrounding SizeProvider. */
133
- size?: SizeVariant
134
- /** Which side of the days the month and year columns stand on; the month column keeps next to the days. Defaults to `'end'`. */
135
- columnsSide?: 'start' | 'end'
136
- /** More columns, given the layout's orientation — the date picker's hours and minutes. */
137
- extraColumns?: (orientation: 'vertical' | 'horizontal') => ReactNode
138
- /** Which side of the days the extra columns stand on, outside the month and year columns when they share it. Defaults to `'end'`. */
139
- extraColumnsSide?: 'start' | 'end'
140
- /** Classes for the calendar's box. */
141
- className?: string
142
- }) {
161
+ }: CalendarProps): ReactElement {
143
162
  const sizeClasses = useSize(size)
144
163
  const picked = pickedDate(props)
145
164
  const today = useMemo(() => new Date(), [])
@@ -229,7 +248,7 @@ function Calendar({
229
248
  const monthColumn = (
230
249
  <ScrollColumn
231
250
  key="month"
232
- label={fieldLabel(locale, 'month')}
251
+ label={columnLabels?.month ?? fieldLabel(locale, 'month')}
233
252
  options={months}
234
253
  value={picked && isSameYear(picked, new Date(yearInView, 0, 1)) ? picked.getMonth() : null}
235
254
  follow={monthInView}
@@ -242,7 +261,7 @@ function Calendar({
242
261
  const yearColumn = (
243
262
  <ScrollColumn
244
263
  key="year"
245
- label={fieldLabel(locale, 'year')}
264
+ label={columnLabels?.year ?? fieldLabel(locale, 'year')}
246
265
  options={years}
247
266
  value={picked?.getFullYear() ?? null}
248
267
  follow={yearInView}
@@ -254,8 +273,10 @@ function Calendar({
254
273
  )
255
274
  const extras = extraColumns?.(orientation)
256
275
  // Outward from the days: the month column, the year column, then the extras.
257
- const before = [extraColumnsSide === 'start' && extras, columnsSide === 'start' && [yearColumn, monthColumn]]
258
- const after = [columnsSide === 'end' && [monthColumn, yearColumn], extraColumnsSide === 'end' && extras]
276
+ // Keyed fragments, so a side's children keep their identity as the extras come and go.
277
+ const extrasPart = extras ? <Fragment key="extras">{extras}</Fragment> : null
278
+ const before = [extraColumnsSide === 'start' && extrasPart, columnsSide === 'start' && [yearColumn, monthColumn]]
279
+ const after = [columnsSide === 'end' && [monthColumn, yearColumn], extraColumnsSide === 'end' && extrasPart]
259
280
  const side = (place: 'start' | 'end') =>
260
281
  wide
261
282
  ? cn(dividedColumns, 'h-full shrink-0 border-border', place === 'start' ? 'border-e' : 'border-s')
@@ -343,8 +364,6 @@ function Calendar({
343
364
  return size ? <SizeProvider size={size}>{content}</SizeProvider> : content
344
365
  }
345
366
 
346
- type CalendarProps = Parameters<typeof Calendar>[0]
347
-
348
367
  export type { CalendarProps }
349
368
  export { Calendar }
350
369
 
@@ -2,6 +2,10 @@ import DatePicker from './index'
2
2
  import { Label } from '../../input'
3
3
  import Button from '../../../button'
4
4
  import Dialog from '../../../overlays/dialog'
5
+ import DateRangePicker from '../date-range-picker'
6
+ import DurationPicker from '../duration-picker'
7
+ import MonthPicker from '../month-picker'
8
+ import TimePicker from '../time-picker'
5
9
  import { ar, de } from 'react-day-picker/locale'
6
10
 
7
11
  # Basic
@@ -164,3 +168,58 @@ The panel lifts above the dialog. Escape in the field first undoes a half-typed
164
168
  </Dialog.Footer>
165
169
  </Dialog.Content>
166
170
  </Dialog>
171
+
172
+ # Slovak, from a language tag
173
+
174
+ `locale` also takes a BCP 47 tag, read through Intl: Slovak month and weekday names, its formats ("9. 10. 2026") and Monday first. `labels` translate the rest, the columns' names included.
175
+
176
+ <div className="w-72 max-w-full">
177
+ <DatePicker
178
+ locale="sk"
179
+ withTime
180
+ actions
181
+ placeholder="Vyberte dátum"
182
+ labels={{ date: 'Dátum', time: 'Čas', close: 'Zavrieť', today: 'Dnes', clear: 'Vymazať', month: 'Mesiac', year: 'Rok', hour: 'Hodina', minute: 'Minúta', calendar: 'Otvoriť kalendár', invalid: 'Neplatný dátum' }}
183
+ defaultValue={new Date(2026, 9, 9, 15, 33)}
184
+ />
185
+ </div>
186
+
187
+ # Hidden days
188
+
189
+ `hidden` leaves days out of the calendar: here the weekends are gaps, and typing one is refused.
190
+
191
+ <div className="w-72 max-w-full">
192
+ <DatePicker placeholder="A working day" hidden={{ dayOfWeek: [0, 6] }} defaultValue={new Date(2026, 9, 9)} />
193
+ </div>
194
+
195
+ # Required and unselectable
196
+
197
+ `required` keeps the picked day when it is pressed again; `unselectable` also keeps it when the text is emptied, and drops Clear.
198
+
199
+ <div className="flex w-72 max-w-full flex-col gap-2">
200
+ <DatePicker required defaultValue={new Date(2026, 9, 9)} />
201
+ <DatePicker unselectable editable actions defaultValue={new Date(2026, 9, 9)} />
202
+ </div>
203
+
204
+ # In a form
205
+
206
+ `name` puts each value in the form's data through a hidden input, in the native inputs' formats, and `required` stops an empty submit and focuses the field. `aria-describedby` and the other `aria-*` props reach the field's control.
207
+
208
+ <form
209
+ className="flex w-80 max-w-full flex-col gap-2"
210
+ onSubmit={event => {
211
+ event.preventDefault()
212
+ const lines = [...new FormData(event.currentTarget)].map(([key, value]) => key + '=' + value)
213
+ event.currentTarget.querySelector('output').textContent = lines.join('\n')
214
+ }}
215
+ >
216
+ <Label htmlFor="form-date">Start</Label>
217
+ <DatePicker id="form-date" name="start" required aria-describedby="form-date-hint" placeholder="Required" />
218
+ <p id="form-date-hint" className="text-caption text-muted-foreground">The first day of the contract.</p>
219
+ <DateRangePicker name="stay" defaultValue={{ from: new Date(2026, 9, 9), to: new Date(2026, 9, 15) }} />
220
+ <TimePicker name="time" defaultValue="09:30" />
221
+ <DurationPicker name="duration" defaultValue={{ hours: 1, minutes: 30 }} />
222
+ <MonthPicker name="month" defaultValue={new Date(2026, 9, 1)} />
223
+ <Button type="submit">Submit</Button>
224
+ <output className="whitespace-pre text-caption text-muted-foreground" />
225
+ </form>