@dashforge/tw 0.9.1-beta → 0.11.0-beta

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 (47) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/index.esm.js +1330 -405
  3. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts +13 -0
  4. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts.map +1 -0
  5. package/dist/src/components/DateRangePicker/dateRangePicker.types.d.ts +85 -0
  6. package/dist/src/components/DateRangePicker/dateRangePicker.types.d.ts.map +1 -0
  7. package/dist/src/components/DateRangePicker/dateRangePicker.variants.d.ts +183 -0
  8. package/dist/src/components/DateRangePicker/dateRangePicker.variants.d.ts.map +1 -0
  9. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts +7 -43
  10. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts.map +1 -1
  11. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts +50 -55
  12. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts.map +1 -1
  13. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts +36 -94
  14. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts.map +1 -1
  15. package/dist/src/components/Tabs/Tabs.d.ts +8 -4
  16. package/dist/src/components/Tabs/Tabs.d.ts.map +1 -1
  17. package/dist/src/components/Tabs/tabs.types.d.ts +8 -2
  18. package/dist/src/components/Tabs/tabs.types.d.ts.map +1 -1
  19. package/dist/src/components/Tabs/useTabs.d.ts +55 -0
  20. package/dist/src/components/Tabs/useTabs.d.ts.map +1 -0
  21. package/dist/src/components/TimePicker/TimePicker.d.ts +12 -0
  22. package/dist/src/components/TimePicker/TimePicker.d.ts.map +1 -0
  23. package/dist/src/components/TimePicker/timePicker.types.d.ts +80 -0
  24. package/dist/src/components/TimePicker/timePicker.types.d.ts.map +1 -0
  25. package/dist/src/components/TimePicker/timePicker.variants.d.ts +103 -0
  26. package/dist/src/components/TimePicker/timePicker.variants.d.ts.map +1 -0
  27. package/dist/src/index.d.ts +10 -4
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/package.json +3 -4
  30. package/src/components/DateRangePicker/DateRangePicker.test.tsx +92 -0
  31. package/src/components/DateRangePicker/DateRangePicker.tsx +510 -0
  32. package/src/components/DateRangePicker/dateRangePicker.types.ts +74 -0
  33. package/src/components/DateRangePicker/dateRangePicker.variants.ts +101 -0
  34. package/src/components/DateTimePicker/DateTimePicker.test.tsx +60 -186
  35. package/src/components/DateTimePicker/DateTimePicker.tsx +209 -181
  36. package/src/components/DateTimePicker/dateTimePicker.types.ts +50 -54
  37. package/src/components/DateTimePicker/dateTimePicker.variants.ts +22 -51
  38. package/src/components/Tabs/Tabs.test.tsx +85 -14
  39. package/src/components/Tabs/Tabs.tsx +35 -24
  40. package/src/components/Tabs/tabs.types.ts +8 -2
  41. package/src/components/Tabs/useTabs.ts +167 -0
  42. package/src/components/TimePicker/TimePicker.test.tsx +65 -0
  43. package/src/components/TimePicker/TimePicker.tsx +274 -0
  44. package/src/components/TimePicker/timePicker.types.ts +69 -0
  45. package/src/components/TimePicker/timePicker.variants.ts +53 -0
  46. package/src/index.ts +28 -16
  47. package/src/components/DateTimePicker/DateTimePicker.perf.test.tsx +0 -153
package/dist/index.esm.js CHANGED
@@ -11,10 +11,9 @@ import { useDashFieldMeta } from '@dashforge/forms';
11
11
  import * as RadixCheckbox from '@radix-ui/react-checkbox';
12
12
  import * as RadixSwitch from '@radix-ui/react-switch';
13
13
  import * as RadixRadioGroup from '@radix-ui/react-radio-group';
14
- import { useCalendar, resolveCalendarKey, formatLongDate, parseISODate } from '@dashforge/calendar-core';
14
+ import { useCalendar, resolveCalendarKey, formatLongDate, parseISODate, formatTime, generateTimeOptions, timeStringToMinutes, getTodayISODate, useDateRange } from '@dashforge/calendar-core';
15
15
  import * as RadixPopover from '@radix-ui/react-popover';
16
16
  import * as RadixDialog from '@radix-ui/react-dialog';
17
- import * as RadixTabs from '@radix-ui/react-tabs';
18
17
  import * as RadixTooltip from '@radix-ui/react-tooltip';
19
18
  import * as RadixAccordion from '@radix-ui/react-accordion';
20
19
 
@@ -3427,334 +3426,6 @@ const defaultGetOptionDisabled = (option)=>Boolean(option.disabled);
3427
3426
  });
3428
3427
  }
3429
3428
 
3430
- /**
3431
- * Tailwind-variants recipe for `<DateTimePicker>` with 6 slots.
3432
- *
3433
- * Styling mirrors `<TextField>` for layout / sizes / error states so
3434
- * the two field types compose visually in the same form.
3435
- *
3436
- * Native `<input type="date|time|datetime-local">` quirks handled here:
3437
- *
3438
- * - The browser-rendered placeholder mask (e.g., `mm/dd/yyyy`) is
3439
- * invisible on `bg-neutral-50` by default in some themes; we keep
3440
- * the same neutral palette and rely on the input's own contrast
3441
- * rules (no extra `::placeholder` styling — browsers don't honor it
3442
- * on these input types).
3443
- * - The native picker indicator (calendar / clock icon) is
3444
- * browser-supplied; we ensure it stays visible in dark mode via
3445
- * `color-scheme: light dark` on the input (set inline below).
3446
- */ const dateTimePickerVariants = tv({
3447
- slots: {
3448
- root: 'flex',
3449
- label: 'block text-sm font-medium text-neutral-900 mb-1',
3450
- requiredMark: 'text-danger-500 ml-0.5',
3451
- inputWrapper: [
3452
- 'relative flex items-center',
3453
- 'rounded-md border bg-neutral-50',
3454
- 'border-neutral-300',
3455
- 'transition-colors',
3456
- 'focus-within:border-primary-500 focus-within:ring-2 focus-within:ring-primary-500/30'
3457
- ],
3458
- input: [
3459
- 'block w-full bg-transparent outline-none',
3460
- 'text-neutral-900 placeholder:text-neutral-400',
3461
- 'disabled:cursor-not-allowed disabled:opacity-60',
3462
- 'read-only:cursor-default',
3463
- // `color-scheme` lets the native calendar / clock icon respect
3464
- // the dark mode (otherwise it stays light-themed against dark bg)
3465
- '[color-scheme:light_dark]'
3466
- ],
3467
- helperText: 'mt-1 text-sm text-neutral-600',
3468
- errorText: 'mt-1 text-sm text-danger-600'
3469
- },
3470
- variants: {
3471
- size: {
3472
- sm: {
3473
- inputWrapper: 'h-8 px-2',
3474
- input: 'text-sm'
3475
- },
3476
- md: {
3477
- inputWrapper: 'h-10 px-3',
3478
- input: 'text-base'
3479
- },
3480
- lg: {
3481
- inputWrapper: 'h-12 px-4',
3482
- input: 'text-lg'
3483
- }
3484
- },
3485
- layout: {
3486
- stacked: {
3487
- root: 'flex-col'
3488
- },
3489
- inline: {
3490
- root: 'flex-row items-start gap-3',
3491
- label: 'mb-0 pt-2 whitespace-nowrap shrink-0'
3492
- }
3493
- },
3494
- error: {
3495
- true: {
3496
- inputWrapper: 'border-danger-500 focus-within:border-danger-500 focus-within:ring-danger-500/30'
3497
- }
3498
- },
3499
- fullWidth: {
3500
- true: {
3501
- root: 'w-full',
3502
- inputWrapper: 'w-full'
3503
- }
3504
- },
3505
- disabled: {
3506
- true: {
3507
- inputWrapper: 'opacity-60 cursor-not-allowed bg-neutral-200'
3508
- }
3509
- }
3510
- },
3511
- defaultVariants: {
3512
- size: 'md',
3513
- layout: 'stacked',
3514
- error: false,
3515
- fullWidth: false,
3516
- disabled: false
3517
- }
3518
- });
3519
-
3520
- /**
3521
- * Resolve the native HTML input `type` for a given picker mode.
3522
- */ function inputTypeFor(mode) {
3523
- switch(mode){
3524
- case 'time':
3525
- return 'time';
3526
- case 'datetime':
3527
- return 'datetime-local';
3528
- case 'date':
3529
- default:
3530
- return 'date';
3531
- }
3532
- }
3533
- /**
3534
- * Coerce a stored/exposed value (ISO 8601-ish string or `null`/`undefined`)
3535
- * into the format the native input expects.
3536
- *
3537
- * Contract:
3538
- * - `date` → `YYYY-MM-DD`
3539
- * - `time` → `HH:mm` (or `HH:mm:ss` if seconds present)
3540
- * - `datetime` → `YYYY-MM-DDTHH:mm` (no timezone, datetime-local
3541
- * rejects `Z` / offset suffixes)
3542
- *
3543
- * The function is intentionally lenient:
3544
- * - Pass-through when the input already matches the expected shape.
3545
- * - Truncates a richer ISO string (e.g. `2026-05-16T10:23:45.000Z`)
3546
- * down to the mode-appropriate prefix.
3547
- * - Returns `''` for null / undefined / unparseable inputs (native
3548
- * inputs use `''` to represent "no value").
3549
- */ function isoToInputValue(mode, raw) {
3550
- if (raw == null || raw === '') return '';
3551
- // Fast path: already in the expected shape.
3552
- if (mode === 'date' && /^\d{4}-\d{2}-\d{2}$/.test(raw)) return raw;
3553
- if (mode === 'time' && /^\d{2}:\d{2}(:\d{2})?$/.test(raw)) return raw;
3554
- if (mode === 'datetime' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?$/.test(raw)) {
3555
- return raw;
3556
- }
3557
- // Slow path: parse via Date and re-format locally.
3558
- const d = new Date(raw);
3559
- if (isNaN(d.getTime())) return '';
3560
- const pad = (n)=>String(n).padStart(2, '0');
3561
- const yyyy = d.getFullYear();
3562
- const mm = pad(d.getMonth() + 1);
3563
- const dd = pad(d.getDate());
3564
- const hh = pad(d.getHours());
3565
- const mi = pad(d.getMinutes());
3566
- if (mode === 'date') return `${yyyy}-${mm}-${dd}`;
3567
- if (mode === 'time') return `${hh}:${mi}`;
3568
- return `${yyyy}-${mm}-${dd}T${hh}:${mi}`;
3569
- }
3570
- /**
3571
- * Take a native input string and return the canonical "stored" value:
3572
- * the same string for non-empty input, or `null` for empty (so the
3573
- * bridge / consumer never receives the ambiguous empty string).
3574
- */ function inputValueToStored(raw) {
3575
- return raw === '' ? null : raw;
3576
- }
3577
- /**
3578
- * Dashforge TW DateTimePicker — bridge-integrated native date/time input.
3579
- *
3580
- * Three modes (`mode` prop):
3581
- * - `date` — `<input type="date">` → `YYYY-MM-DD`
3582
- * - `time` — `<input type="time">` → `HH:mm` (`HH:mm:ss` with `step={1}`)
3583
- * - `datetime` — `<input type="datetime-local">` → `YYYY-MM-DDTHH:mm`
3584
- *
3585
- * **Why native inputs instead of React Aria `<DatePicker>`?**
3586
- *
3587
- * 1. Zero new deps (`@internationalized/date` would add ~30 KB).
3588
- * 2. Mirrors `@dashforge/ui/DateTimePicker` (MUI side) for API parity.
3589
- * 3. Native inputs are AAA-grade a11y out of the box — screen readers,
3590
- * keyboard nav, OS-provided calendar/clock UI.
3591
- * 4. Predictable state (no internal Aria state machine that fights
3592
- * controlled props — the F5-A lesson learned in `<Autocomplete>`).
3593
- *
3594
- * A richer custom-calendar version (range mode, locale awareness,
3595
- * custom popover styling) can land as F5-B-bis built on Aria
3596
- * `<DatePicker>` / `<Calendar>`.
3597
- *
3598
- * **A11y**:
3599
- * - `aria-invalid` driven by the resolved validation state.
3600
- * - `aria-describedby` links input ↔ helper/error.
3601
- * - Required fields get the native `required` attribute + a visual `*`.
3602
- * - `color-scheme: light dark` keeps the OS calendar icon readable
3603
- * in both color schemes.
3604
- */ function DateTimePicker(props) {
3605
- var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_inputWrapper, _slotProps_input, _slotProps_errorText, _slotProps_helperText;
3606
- const { name, rules, mode = 'date', visibleWhen, layout = 'stacked', size, label, helperText, required, error, disabled, fullWidth, access, sx, slotProps, placeholder, min, max, step, value: explicitValue, defaultValue, onValueChange } = props;
3607
- // ───── Hooks (unconditional) ─────
3608
- const bridge = useContext(DashFormContext);
3609
- const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
3610
- // Per-field reactive snapshot. Same pattern as <TextField> — re-renders
3611
- // this component when its OWN bridge value changes, but not when a
3612
- // sibling field changes (perf contract).
3613
- const fieldMeta = useDashFieldMeta(name);
3614
- const accessState = useAccessState(access);
3615
- const inputId = useId();
3616
- const helperId = `${inputId}-help`;
3617
- // StrictMode-safe unregister-on-unmount.
3618
- const unregisterRef = useRef({
3619
- bridge,
3620
- name
3621
- });
3622
- unregisterRef.current = {
3623
- bridge,
3624
- name
3625
- };
3626
- const isMountedRef = useRef(false);
3627
- useEffect(()=>{
3628
- isMountedRef.current = true;
3629
- return ()=>{
3630
- isMountedRef.current = false;
3631
- const { bridge: cap, name: capName } = unregisterRef.current;
3632
- queueMicrotask(()=>{
3633
- var _cap_unregister;
3634
- if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
3635
- });
3636
- };
3637
- }, []);
3638
- // ───── Derived ─────
3639
- const effectiveDisabled = Boolean(disabled) || accessState.disabled;
3640
- const effectiveReadOnly = accessState.readonly;
3641
- const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
3642
- let resolvedError = error;
3643
- let resolvedHelperText = helperText;
3644
- let registration = null;
3645
- let resolvedValue;
3646
- if (isFormMode && bridge) {
3647
- registration = bridge.register(name, rules);
3648
- const validation = resolveValidationState(name, bridge, error, helperText);
3649
- resolvedError = validation.error;
3650
- resolvedHelperText = validation.helperText;
3651
- if (explicitValue !== undefined) {
3652
- resolvedValue = explicitValue;
3653
- } else {
3654
- const bv = fieldMeta.value;
3655
- resolvedValue = bv == null ? null : String(bv);
3656
- }
3657
- } else if (explicitValue !== undefined) {
3658
- resolvedValue = explicitValue;
3659
- } else {
3660
- // Pure uncontrolled mode: defer to native input's defaultValue.
3661
- resolvedValue = undefined;
3662
- }
3663
- // Registration-supplied callback ref (RHF threads its own ref through
3664
- // here in form mode). Stable callback to keep React 19 happy.
3665
- const registrationRefFn = registration == null ? void 0 : registration.ref;
3666
- const inputRef = useCallback((instance)=>{
3667
- if (typeof registrationRefFn === 'function') {
3668
- registrationRefFn(instance);
3669
- }
3670
- }, [
3671
- registrationRefFn
3672
- ]);
3673
- // ───── Render-time guards (after all hooks) ─────
3674
- if (!isVisible) return null;
3675
- if (!accessState.visible) return null;
3676
- const v = dateTimePickerVariants({
3677
- size,
3678
- layout,
3679
- error: resolvedError,
3680
- fullWidth,
3681
- disabled: effectiveDisabled
3682
- });
3683
- const inputType = inputTypeFor(mode);
3684
- const nativeValue = resolvedValue === undefined ? undefined : isoToInputValue(mode, resolvedValue);
3685
- const nativeDefault = !isFormMode && explicitValue === undefined && defaultValue !== undefined ? isoToInputValue(mode, defaultValue) : undefined;
3686
- const nativeMin = min != null ? isoToInputValue(mode, min) : undefined;
3687
- const nativeMax = max != null ? isoToInputValue(mode, max) : undefined;
3688
- const handleChange = (event)=>{
3689
- const next = inputValueToStored(event.target.value);
3690
- if (isFormMode && bridge) {
3691
- var _registration_onChange;
3692
- bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
3693
- void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
3694
- target: {
3695
- name,
3696
- value: next != null ? next : ''
3697
- },
3698
- type: 'change'
3699
- }));
3700
- }
3701
- onValueChange == null ? void 0 : onValueChange(next);
3702
- };
3703
- const handleBlur = (event)=>{
3704
- if (isFormMode && bridge) {
3705
- var _registration_onBlur;
3706
- registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, event);
3707
- }
3708
- };
3709
- return jsxs("div", {
3710
- className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
3711
- children: [
3712
- label && jsxs("label", {
3713
- htmlFor: inputId,
3714
- className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
3715
- children: [
3716
- label,
3717
- required && jsx("span", {
3718
- "aria-hidden": "true",
3719
- className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
3720
- children: "*"
3721
- })
3722
- ]
3723
- }),
3724
- jsx("div", {
3725
- className: cn(v.inputWrapper(), slotProps == null ? void 0 : (_slotProps_inputWrapper = slotProps.inputWrapper) == null ? void 0 : _slotProps_inputWrapper.className),
3726
- children: jsx("input", {
3727
- id: inputId,
3728
- ref: inputRef,
3729
- name: name,
3730
- type: inputType,
3731
- // Controlled in form / explicit-value mode; uncontrolled with
3732
- // `defaultValue` otherwise. Mirrors the TextField pattern.
3733
- value: nativeValue,
3734
- defaultValue: nativeDefault,
3735
- min: nativeMin,
3736
- max: nativeMax,
3737
- step: step,
3738
- placeholder: placeholder,
3739
- disabled: effectiveDisabled,
3740
- readOnly: effectiveReadOnly,
3741
- required: required,
3742
- "aria-invalid": resolvedError ? true : undefined,
3743
- "aria-describedby": resolvedHelperText ? helperId : undefined,
3744
- onChange: handleChange,
3745
- onBlur: handleBlur,
3746
- className: cn(v.input(), slotProps == null ? void 0 : (_slotProps_input = slotProps.input) == null ? void 0 : _slotProps_input.className)
3747
- })
3748
- }),
3749
- resolvedHelperText && jsx("p", {
3750
- id: helperId,
3751
- className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
3752
- children: resolvedHelperText
3753
- })
3754
- ]
3755
- });
3756
- }
3757
-
3758
3429
  /**
3759
3430
  * Structural slots for `<Calendar>`.
3760
3431
  *
@@ -3820,7 +3491,7 @@ const defaultGetOptionDisabled = (option)=>Boolean(option.disabled);
3820
3491
  });
3821
3492
 
3822
3493
  // Inline 16×16 stroke chevrons — no icon dependency (tw convention).
3823
- function ChevronLeftIcon() {
3494
+ function ChevronLeftIcon$1() {
3824
3495
  return jsx("svg", {
3825
3496
  width: "1em",
3826
3497
  height: "1em",
@@ -3836,7 +3507,7 @@ function ChevronLeftIcon() {
3836
3507
  })
3837
3508
  });
3838
3509
  }
3839
- function ChevronRightIcon$1() {
3510
+ function ChevronRightIcon$2() {
3840
3511
  return jsx("svg", {
3841
3512
  width: "1em",
3842
3513
  height: "1em",
@@ -3955,7 +3626,7 @@ function ChevronRightIcon$1() {
3955
3626
  onClick: ()=>{
3956
3627
  calendar.goToPreviousMonth();
3957
3628
  },
3958
- children: jsx(ChevronLeftIcon, {})
3629
+ children: jsx(ChevronLeftIcon$1, {})
3959
3630
  }),
3960
3631
  jsx("span", {
3961
3632
  "aria-live": "polite",
@@ -3970,7 +3641,7 @@ function ChevronRightIcon$1() {
3970
3641
  onClick: ()=>{
3971
3642
  calendar.goToNextMonth();
3972
3643
  },
3973
- children: jsx(ChevronRightIcon$1, {})
3644
+ children: jsx(ChevronRightIcon$2, {})
3974
3645
  })
3975
3646
  ]
3976
3647
  }),
@@ -4085,33 +3756,948 @@ const popoverVariants = tv({
4085
3756
  } : undefined,
4086
3757
  className: cn(v.content(), slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
4087
3758
  children: [
4088
- content,
4089
- showArrow && jsx(RadixPopover.Arrow, {
4090
- className: cn(v.arrow(), slotProps == null ? void 0 : (_slotProps_arrow = slotProps.arrow) == null ? void 0 : _slotProps_arrow.className)
3759
+ content,
3760
+ showArrow && jsx(RadixPopover.Arrow, {
3761
+ className: cn(v.arrow(), slotProps == null ? void 0 : (_slotProps_arrow = slotProps.arrow) == null ? void 0 : _slotProps_arrow.className)
3762
+ })
3763
+ ]
3764
+ })
3765
+ })
3766
+ ]
3767
+ });
3768
+ }
3769
+
3770
+ /**
3771
+ * Slots for `<DatePicker>` — a bridge-integrated date field whose trigger
3772
+ * is a `<button>` styled as an input, opening a `<Calendar>` popover.
3773
+ *
3774
+ * Theme identity: bare neutral / brand token-backed classes only — no
3775
+ * `dark:` variants on the neutral palette (the preset CSS-var swap
3776
+ * auto-inverts). See `_shared/themeIdentity.test.ts`.
3777
+ */ const datePickerVariants = tv({
3778
+ slots: {
3779
+ root: 'flex flex-col',
3780
+ label: 'block text-sm font-medium text-neutral-900 mb-1',
3781
+ requiredMark: 'text-danger-500 ml-0.5',
3782
+ trigger: 'flex items-center gap-2 h-10 min-w-[12rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
3783
+ value: 'flex-1 truncate text-left',
3784
+ placeholder: 'flex-1 truncate text-left text-neutral-400',
3785
+ icon: 'shrink-0 text-[1rem] text-neutral-500',
3786
+ helperText: 'mt-1 text-sm text-neutral-600',
3787
+ errorText: 'mt-1 text-sm text-danger-600'
3788
+ },
3789
+ variants: {
3790
+ layout: {
3791
+ stacked: {},
3792
+ inline: {
3793
+ root: 'flex-row items-start gap-3',
3794
+ label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
3795
+ }
3796
+ },
3797
+ error: {
3798
+ true: {
3799
+ trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
3800
+ }
3801
+ },
3802
+ fullWidth: {
3803
+ true: {
3804
+ root: 'w-full',
3805
+ trigger: 'w-full min-w-0'
3806
+ }
3807
+ }
3808
+ },
3809
+ defaultVariants: {
3810
+ layout: 'stacked',
3811
+ error: false,
3812
+ fullWidth: false
3813
+ }
3814
+ });
3815
+
3816
+ // Inline 16×16 stroke calendar glyph — no icon dependency (tw convention).
3817
+ function CalendarIcon$2() {
3818
+ return jsxs("svg", {
3819
+ width: "1em",
3820
+ height: "1em",
3821
+ viewBox: "0 0 16 16",
3822
+ fill: "none",
3823
+ "aria-hidden": "true",
3824
+ children: [
3825
+ jsx("rect", {
3826
+ x: "2.5",
3827
+ y: "3.5",
3828
+ width: "11",
3829
+ height: "10",
3830
+ rx: "1.5",
3831
+ stroke: "currentColor",
3832
+ strokeWidth: "1.5"
3833
+ }),
3834
+ jsx("path", {
3835
+ d: "M2.5 6.5h11M5.5 2v3M10.5 2v3",
3836
+ stroke: "currentColor",
3837
+ strokeWidth: "1.5",
3838
+ strokeLinecap: "round"
3839
+ })
3840
+ ]
3841
+ });
3842
+ }
3843
+ /**
3844
+ * Formats a stored ISO date for the trigger display, localized to a medium
3845
+ * date style (e.g. `"May 20, 2026"`). Formatting is done in UTC so the
3846
+ * displayed day always matches the stored `YYYY-MM-DD`.
3847
+ */ function formatDisplayDate(iso, locale) {
3848
+ if (iso == null || iso === '') {
3849
+ return '';
3850
+ }
3851
+ const parts = parseISODate(iso);
3852
+ if (parts === null) {
3853
+ return iso;
3854
+ }
3855
+ return new Intl.DateTimeFormat(locale, {
3856
+ dateStyle: 'medium',
3857
+ timeZone: 'UTC'
3858
+ }).format(Date.UTC(parts.year, parts.month - 1, parts.day));
3859
+ }
3860
+ /**
3861
+ * Dashforge TW `<DatePicker>` — a form-bound single-date field.
3862
+ *
3863
+ * A read-only trigger button paired with a `<Calendar>` popover (Radix
3864
+ * Popover — toggle / Escape / outside-click / focus-trap are Radix-managed).
3865
+ * Integrates with the Dashforge form bridge + RBAC, mirroring the other
3866
+ * `@dashforge/tw` form fields.
3867
+ *
3868
+ * Storage contract: an ISO `YYYY-MM-DD` string, or `null` — shared with the
3869
+ * MUI `@dashforge/ui` `DatePicker`.
3870
+ */ function DatePicker(props) {
3871
+ var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
3872
+ const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, fullWidth, sx, slotProps, testId } = props;
3873
+ // ───── Hooks (unconditional, before any early return) ─────
3874
+ const bridge = useContext(DashFormContext);
3875
+ const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
3876
+ const fieldMeta = useDashFieldMeta(name);
3877
+ const accessState = useAccessState(access);
3878
+ const fieldId = useId();
3879
+ const helperId = `${fieldId}-help`;
3880
+ const [isOpen, setIsOpen] = useState(false);
3881
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
3882
+ // StrictMode-safe unregister-on-unmount.
3883
+ const unregisterRef = useRef({
3884
+ bridge,
3885
+ name
3886
+ });
3887
+ unregisterRef.current = {
3888
+ bridge,
3889
+ name
3890
+ };
3891
+ const isMountedRef = useRef(false);
3892
+ useEffect(()=>{
3893
+ isMountedRef.current = true;
3894
+ return ()=>{
3895
+ isMountedRef.current = false;
3896
+ const { bridge: cap, name: capName } = unregisterRef.current;
3897
+ queueMicrotask(()=>{
3898
+ var _cap_unregister;
3899
+ if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
3900
+ });
3901
+ };
3902
+ }, []);
3903
+ // ───── Derived ─────
3904
+ const effectiveDisabled = Boolean(disabled) || accessState.disabled;
3905
+ const effectiveReadOnly = accessState.readonly;
3906
+ const isInteractive = !effectiveDisabled && !effectiveReadOnly;
3907
+ const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
3908
+ let resolvedError = error;
3909
+ let resolvedHelperText = helperText;
3910
+ let registration = null;
3911
+ let resolvedValue;
3912
+ if (isFormMode && bridge) {
3913
+ registration = bridge.register(name, rules);
3914
+ const validation = resolveValidationState(name, bridge, error, helperText);
3915
+ resolvedError = validation.error;
3916
+ resolvedHelperText = validation.helperText;
3917
+ if (explicitValue !== undefined) {
3918
+ resolvedValue = explicitValue;
3919
+ } else {
3920
+ const bv = fieldMeta.value;
3921
+ resolvedValue = bv == null ? null : String(bv);
3922
+ }
3923
+ } else if (explicitValue !== undefined) {
3924
+ resolvedValue = explicitValue;
3925
+ } else {
3926
+ resolvedValue = internalValue;
3927
+ }
3928
+ // RHF threads its own callback ref through here in form mode.
3929
+ const registrationRefFn = registration == null ? void 0 : registration.ref;
3930
+ const triggerRef = useCallback((instance)=>{
3931
+ if (typeof registrationRefFn === 'function') {
3932
+ registrationRefFn(instance);
3933
+ }
3934
+ }, [
3935
+ registrationRefFn
3936
+ ]);
3937
+ // ───── Render-time guards (after all hooks) ─────
3938
+ if (!isVisible) return null;
3939
+ if (!accessState.visible) return null;
3940
+ const v = datePickerVariants({
3941
+ layout,
3942
+ error: resolvedError,
3943
+ fullWidth
3944
+ });
3945
+ const resolvedLocale = locale != null ? locale : 'en-US';
3946
+ const displayValue = formatDisplayDate(resolvedValue, resolvedLocale);
3947
+ const commitValue = (next)=>{
3948
+ if (isFormMode && bridge) {
3949
+ var _registration_onChange;
3950
+ bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
3951
+ void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
3952
+ target: {
3953
+ name,
3954
+ value: next != null ? next : ''
3955
+ },
3956
+ type: 'change'
3957
+ }));
3958
+ } else if (explicitValue === undefined) {
3959
+ setInternalValue(next);
3960
+ }
3961
+ onChange == null ? void 0 : onChange(next);
3962
+ };
3963
+ // Mark the field touched on popover close. Reads the freshest committed
3964
+ // value straight from the bridge so the touched-blur never reverts a
3965
+ // value just chosen in the calendar.
3966
+ const markTouched = ()=>{
3967
+ if (isFormMode && bridge) {
3968
+ var _registration_onBlur;
3969
+ const committed = bridge.getValue(name);
3970
+ void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
3971
+ target: {
3972
+ name,
3973
+ value: committed == null ? '' : String(committed)
3974
+ },
3975
+ type: 'blur'
3976
+ }));
3977
+ }
3978
+ };
3979
+ const handleOpenChange = (next)=>{
3980
+ if (next && !isInteractive) {
3981
+ return;
3982
+ }
3983
+ setIsOpen(next);
3984
+ if (!next) {
3985
+ markTouched();
3986
+ }
3987
+ };
3988
+ const handleCalendarChange = (next)=>{
3989
+ commitValue(next);
3990
+ setIsOpen(false);
3991
+ markTouched();
3992
+ };
3993
+ return jsxs("div", {
3994
+ "data-testid": testId,
3995
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
3996
+ children: [
3997
+ label && jsxs("label", {
3998
+ htmlFor: fieldId,
3999
+ className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
4000
+ children: [
4001
+ label,
4002
+ required && jsx("span", {
4003
+ "aria-hidden": "true",
4004
+ className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
4005
+ children: "*"
4006
+ })
4007
+ ]
4008
+ }),
4009
+ jsx(Popover, {
4010
+ open: isOpen,
4011
+ onOpenChange: handleOpenChange,
4012
+ side: "bottom",
4013
+ align: "start",
4014
+ content: jsx(Calendar, {
4015
+ value: resolvedValue,
4016
+ onChange: handleCalendarChange,
4017
+ minDate: minDate,
4018
+ maxDate: maxDate,
4019
+ disabledDates: disabledDates,
4020
+ isDateDisabled: isDateDisabled,
4021
+ weekStartDay: weekStartDay,
4022
+ locale: locale,
4023
+ "aria-label": "Choose date",
4024
+ sx: "border-0 bg-transparent p-0"
4025
+ }),
4026
+ children: jsxs("button", {
4027
+ type: "button",
4028
+ id: fieldId,
4029
+ ref: triggerRef,
4030
+ disabled: effectiveDisabled,
4031
+ "aria-invalid": resolvedError ? true : undefined,
4032
+ "aria-describedby": resolvedHelperText ? helperId : undefined,
4033
+ className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
4034
+ children: [
4035
+ jsx("span", {
4036
+ className: displayValue ? v.value() : v.placeholder(),
4037
+ children: displayValue || placeholder || 'Select a date'
4038
+ }),
4039
+ jsx("span", {
4040
+ className: v.icon(),
4041
+ children: jsx(CalendarIcon$2, {})
4042
+ })
4043
+ ]
4044
+ })
4045
+ }),
4046
+ resolvedHelperText && jsx("p", {
4047
+ id: helperId,
4048
+ className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
4049
+ children: resolvedHelperText
4050
+ })
4051
+ ]
4052
+ });
4053
+ }
4054
+
4055
+ /**
4056
+ * Slots for `<TimePicker>` — a bridge-integrated time field whose trigger
4057
+ * is a `<button>` styled as an input, opening a time-list popover.
4058
+ *
4059
+ * Theme identity: bare neutral / brand token-backed classes only — no
4060
+ * `dark:` variants on the neutral palette (the preset CSS-var swap
4061
+ * auto-inverts). See `_shared/themeIdentity.test.ts`.
4062
+ */ const timePickerVariants = tv({
4063
+ slots: {
4064
+ root: 'flex flex-col',
4065
+ label: 'block text-sm font-medium text-neutral-900 mb-1',
4066
+ requiredMark: 'text-danger-500 ml-0.5',
4067
+ trigger: 'flex items-center gap-2 h-10 min-w-[10rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
4068
+ value: 'flex-1 truncate text-left',
4069
+ placeholder: 'flex-1 truncate text-left text-neutral-400',
4070
+ icon: 'shrink-0 text-[1rem] text-neutral-500',
4071
+ list: 'flex max-h-64 min-w-[8rem] flex-col gap-0.5 overflow-y-auto p-1',
4072
+ option: 'flex w-full cursor-pointer items-center rounded px-2.5 py-1.5 text-sm text-neutral-900 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500 aria-selected:bg-primary-600 aria-selected:text-white aria-selected:hover:bg-primary-600 aria-disabled:cursor-default aria-disabled:text-neutral-300 aria-disabled:opacity-60 aria-disabled:hover:bg-transparent',
4073
+ helperText: 'mt-1 text-sm text-neutral-600',
4074
+ errorText: 'mt-1 text-sm text-danger-600'
4075
+ },
4076
+ variants: {
4077
+ layout: {
4078
+ stacked: {},
4079
+ inline: {
4080
+ root: 'flex-row items-start gap-3',
4081
+ label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
4082
+ }
4083
+ },
4084
+ error: {
4085
+ true: {
4086
+ trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
4087
+ }
4088
+ },
4089
+ fullWidth: {
4090
+ true: {
4091
+ root: 'w-full',
4092
+ trigger: 'w-full min-w-0'
4093
+ }
4094
+ }
4095
+ },
4096
+ defaultVariants: {
4097
+ layout: 'stacked',
4098
+ error: false,
4099
+ fullWidth: false
4100
+ }
4101
+ });
4102
+
4103
+ // Inline 16×16 stroke clock glyph — no icon dependency (tw convention).
4104
+ function ClockIcon() {
4105
+ return jsxs("svg", {
4106
+ width: "1em",
4107
+ height: "1em",
4108
+ viewBox: "0 0 16 16",
4109
+ fill: "none",
4110
+ "aria-hidden": "true",
4111
+ children: [
4112
+ jsx("circle", {
4113
+ cx: "8",
4114
+ cy: "8",
4115
+ r: "6",
4116
+ stroke: "currentColor",
4117
+ strokeWidth: "1.5"
4118
+ }),
4119
+ jsx("path", {
4120
+ d: "M8 4.5V8l2.4 1.4",
4121
+ stroke: "currentColor",
4122
+ strokeWidth: "1.5",
4123
+ strokeLinecap: "round",
4124
+ strokeLinejoin: "round"
4125
+ })
4126
+ ]
4127
+ });
4128
+ }
4129
+ /**
4130
+ * Dashforge TW `<TimePicker>` — a form-bound time-of-day field.
4131
+ *
4132
+ * A read-only trigger button paired with a time-list popover (Radix
4133
+ * Popover). Integrates with the Dashforge form bridge + RBAC.
4134
+ *
4135
+ * Storage contract: a canonical 24-hour `"HH:mm"` string, or `null` —
4136
+ * shared with the MUI `@dashforge/ui` `TimePicker`. `hour12` is display-only.
4137
+ */ function TimePicker(props) {
4138
+ var _timeStringToMinutes, _timeStringToMinutes1;
4139
+ var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
4140
+ const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minTime, maxTime, stepMinutes, hour12 = false, fullWidth, sx, slotProps, testId } = props;
4141
+ const bridge = useContext(DashFormContext);
4142
+ const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
4143
+ const fieldMeta = useDashFieldMeta(name);
4144
+ const accessState = useAccessState(access);
4145
+ const fieldId = useId();
4146
+ const helperId = `${fieldId}-help`;
4147
+ const [isOpen, setIsOpen] = useState(false);
4148
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
4149
+ const unregisterRef = useRef({
4150
+ bridge,
4151
+ name
4152
+ });
4153
+ unregisterRef.current = {
4154
+ bridge,
4155
+ name
4156
+ };
4157
+ const isMountedRef = useRef(false);
4158
+ useEffect(()=>{
4159
+ isMountedRef.current = true;
4160
+ return ()=>{
4161
+ isMountedRef.current = false;
4162
+ const { bridge: cap, name: capName } = unregisterRef.current;
4163
+ queueMicrotask(()=>{
4164
+ var _cap_unregister;
4165
+ if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
4166
+ });
4167
+ };
4168
+ }, []);
4169
+ const effectiveDisabled = Boolean(disabled) || accessState.disabled;
4170
+ const isInteractive = !effectiveDisabled && !accessState.readonly;
4171
+ const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
4172
+ let resolvedError = error;
4173
+ let resolvedHelperText = helperText;
4174
+ let registration = null;
4175
+ let resolvedValue;
4176
+ if (isFormMode && bridge) {
4177
+ registration = bridge.register(name, rules);
4178
+ const validation = resolveValidationState(name, bridge, error, helperText);
4179
+ resolvedError = validation.error;
4180
+ resolvedHelperText = validation.helperText;
4181
+ if (explicitValue !== undefined) {
4182
+ resolvedValue = explicitValue;
4183
+ } else {
4184
+ const bv = fieldMeta.value;
4185
+ resolvedValue = bv == null ? null : String(bv);
4186
+ }
4187
+ } else if (explicitValue !== undefined) {
4188
+ resolvedValue = explicitValue;
4189
+ } else {
4190
+ resolvedValue = internalValue;
4191
+ }
4192
+ const registrationRefFn = registration == null ? void 0 : registration.ref;
4193
+ const triggerRef = useCallback((instance)=>{
4194
+ if (typeof registrationRefFn === 'function') {
4195
+ registrationRefFn(instance);
4196
+ }
4197
+ }, [
4198
+ registrationRefFn
4199
+ ]);
4200
+ if (!isVisible) return null;
4201
+ if (!accessState.visible) return null;
4202
+ const v = timePickerVariants({
4203
+ layout,
4204
+ error: resolvedError,
4205
+ fullWidth
4206
+ });
4207
+ const displayValue = resolvedValue != null && resolvedValue !== '' ? formatTime(resolvedValue, {
4208
+ hour12
4209
+ }) : '';
4210
+ const options = generateTimeOptions(_extends({}, minTime !== undefined && {
4211
+ start: minTime
4212
+ }, maxTime !== undefined && {
4213
+ end: maxTime
4214
+ }, stepMinutes !== undefined && {
4215
+ stepMinutes
4216
+ }));
4217
+ const minMinutes = (_timeStringToMinutes = timeStringToMinutes(minTime != null ? minTime : '00:00')) != null ? _timeStringToMinutes : 0;
4218
+ const maxMinutes = (_timeStringToMinutes1 = timeStringToMinutes(maxTime != null ? maxTime : '23:59')) != null ? _timeStringToMinutes1 : 1439;
4219
+ const isTimeDisabled = (time)=>{
4220
+ const minutes = timeStringToMinutes(time);
4221
+ return minutes === null || minutes < minMinutes || minutes > maxMinutes;
4222
+ };
4223
+ const commitValue = (next)=>{
4224
+ if (isFormMode && bridge) {
4225
+ var _registration_onChange;
4226
+ bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
4227
+ void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
4228
+ target: {
4229
+ name,
4230
+ value: next != null ? next : ''
4231
+ },
4232
+ type: 'change'
4233
+ }));
4234
+ } else if (explicitValue === undefined) {
4235
+ setInternalValue(next);
4236
+ }
4237
+ onChange == null ? void 0 : onChange(next);
4238
+ };
4239
+ const markTouched = ()=>{
4240
+ if (isFormMode && bridge) {
4241
+ var _registration_onBlur;
4242
+ const committed = bridge.getValue(name);
4243
+ void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
4244
+ target: {
4245
+ name,
4246
+ value: committed == null ? '' : String(committed)
4247
+ },
4248
+ type: 'blur'
4249
+ }));
4250
+ }
4251
+ };
4252
+ const handleOpenChange = (next)=>{
4253
+ if (next && !isInteractive) {
4254
+ return;
4255
+ }
4256
+ setIsOpen(next);
4257
+ if (!next) {
4258
+ markTouched();
4259
+ }
4260
+ };
4261
+ const handleSelect = (time)=>{
4262
+ if (isTimeDisabled(time)) {
4263
+ return;
4264
+ }
4265
+ commitValue(time);
4266
+ setIsOpen(false);
4267
+ markTouched();
4268
+ };
4269
+ return jsxs("div", {
4270
+ "data-testid": testId,
4271
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
4272
+ children: [
4273
+ label && jsxs("label", {
4274
+ htmlFor: fieldId,
4275
+ className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
4276
+ children: [
4277
+ label,
4278
+ required && jsx("span", {
4279
+ "aria-hidden": "true",
4280
+ className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
4281
+ children: "*"
4282
+ })
4283
+ ]
4284
+ }),
4285
+ jsx(Popover, {
4286
+ open: isOpen,
4287
+ onOpenChange: handleOpenChange,
4288
+ side: "bottom",
4289
+ align: "start",
4290
+ content: jsx("div", {
4291
+ role: "listbox",
4292
+ "aria-label": "Time options",
4293
+ className: v.list(),
4294
+ children: options.map((time)=>jsx("button", {
4295
+ type: "button",
4296
+ role: "option",
4297
+ "aria-selected": time === resolvedValue,
4298
+ "aria-disabled": isTimeDisabled(time),
4299
+ className: v.option(),
4300
+ onClick: ()=>{
4301
+ handleSelect(time);
4302
+ },
4303
+ children: formatTime(time, {
4304
+ hour12
4305
+ })
4306
+ }, time))
4307
+ }),
4308
+ children: jsxs("button", {
4309
+ type: "button",
4310
+ id: fieldId,
4311
+ ref: triggerRef,
4312
+ disabled: effectiveDisabled,
4313
+ "aria-invalid": resolvedError ? true : undefined,
4314
+ "aria-describedby": resolvedHelperText ? helperId : undefined,
4315
+ className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
4316
+ children: [
4317
+ jsx("span", {
4318
+ className: displayValue ? v.value() : v.placeholder(),
4319
+ children: displayValue || placeholder || 'Select a time'
4320
+ }),
4321
+ jsx("span", {
4322
+ className: v.icon(),
4323
+ children: jsx(ClockIcon, {})
4324
+ })
4325
+ ]
4326
+ })
4327
+ }),
4328
+ resolvedHelperText && jsx("p", {
4329
+ id: helperId,
4330
+ className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
4331
+ children: resolvedHelperText
4332
+ })
4333
+ ]
4334
+ });
4335
+ }
4336
+
4337
+ /**
4338
+ * Slots for `<DateTimePicker>` — a bridge-integrated date+time field whose
4339
+ * trigger is a `<button>` styled as an input, opening a popover that
4340
+ * combines a `<Calendar>` with a time list.
4341
+ *
4342
+ * Theme identity: bare neutral / brand token-backed classes only — no
4343
+ * `dark:` variants on the neutral palette (the preset CSS-var swap
4344
+ * auto-inverts). See `_shared/themeIdentity.test.ts`.
4345
+ */ const dateTimePickerVariants = tv({
4346
+ slots: {
4347
+ root: 'flex flex-col',
4348
+ label: 'block text-sm font-medium text-neutral-900 mb-1',
4349
+ requiredMark: 'text-danger-500 ml-0.5',
4350
+ trigger: 'flex items-center gap-2 h-10 min-w-[13rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
4351
+ value: 'flex-1 truncate text-left',
4352
+ placeholder: 'flex-1 truncate text-left text-neutral-400',
4353
+ icon: 'shrink-0 text-[1rem] text-neutral-500',
4354
+ panel: 'flex items-stretch',
4355
+ list: 'flex max-h-[19rem] min-w-[6.5rem] flex-col gap-0.5 overflow-y-auto border-l border-neutral-200 p-1',
4356
+ option: 'flex w-full cursor-pointer items-center rounded px-2.5 py-1.5 text-sm text-neutral-900 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500 aria-selected:bg-primary-600 aria-selected:text-white aria-selected:hover:bg-primary-600',
4357
+ helperText: 'mt-1 text-sm text-neutral-600',
4358
+ errorText: 'mt-1 text-sm text-danger-600'
4359
+ },
4360
+ variants: {
4361
+ layout: {
4362
+ stacked: {},
4363
+ inline: {
4364
+ root: 'flex-row items-start gap-3',
4365
+ label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
4366
+ }
4367
+ },
4368
+ error: {
4369
+ true: {
4370
+ trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
4371
+ }
4372
+ },
4373
+ fullWidth: {
4374
+ true: {
4375
+ root: 'w-full',
4376
+ trigger: 'w-full min-w-0'
4377
+ }
4378
+ }
4379
+ },
4380
+ defaultVariants: {
4381
+ layout: 'stacked',
4382
+ error: false,
4383
+ fullWidth: false
4384
+ }
4385
+ });
4386
+
4387
+ // Inline 16×16 stroke calendar glyph — no icon dependency (tw convention).
4388
+ function CalendarIcon$1() {
4389
+ return jsxs("svg", {
4390
+ width: "1em",
4391
+ height: "1em",
4392
+ viewBox: "0 0 16 16",
4393
+ fill: "none",
4394
+ "aria-hidden": "true",
4395
+ children: [
4396
+ jsx("rect", {
4397
+ x: "2.5",
4398
+ y: "3.5",
4399
+ width: "11",
4400
+ height: "10",
4401
+ rx: "1.5",
4402
+ stroke: "currentColor",
4403
+ strokeWidth: "1.5"
4404
+ }),
4405
+ jsx("path", {
4406
+ d: "M2.5 6.5h11M5.5 2v3M10.5 2v3",
4407
+ stroke: "currentColor",
4408
+ strokeWidth: "1.5",
4409
+ strokeLinecap: "round"
4410
+ })
4411
+ ]
4412
+ });
4413
+ }
4414
+ const DATETIME_PATTERN = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})/;
4415
+ /** Splits a stored `YYYY-MM-DDTHH:mm` value into its date and time parts. */ function splitDateTime(value) {
4416
+ if (value == null || value === '') {
4417
+ return {
4418
+ date: null,
4419
+ time: null
4420
+ };
4421
+ }
4422
+ const match = DATETIME_PATTERN.exec(value);
4423
+ if (match !== null) {
4424
+ var _match_, _match_1, _match_2;
4425
+ return {
4426
+ date: (_match_ = match[1]) != null ? _match_ : null,
4427
+ time: `${(_match_1 = match[2]) != null ? _match_1 : '00'}:${(_match_2 = match[3]) != null ? _match_2 : '00'}`
4428
+ };
4429
+ }
4430
+ return {
4431
+ date: parseISODate(value) !== null ? value : null,
4432
+ time: null
4433
+ };
4434
+ }
4435
+ /** Combines a date and a time into a `YYYY-MM-DDTHH:mm` value (or `null`). */ function joinDateTime(date, time) {
4436
+ if (date === null && time === null) {
4437
+ return null;
4438
+ }
4439
+ return `${date != null ? date : getTodayISODate()}T${time != null ? time : '00:00'}`;
4440
+ }
4441
+ /** Formats a stored datetime for the trigger display. */ function formatDateTime(value, locale, hour12) {
4442
+ const { date, time } = splitDateTime(value);
4443
+ const parts = [];
4444
+ if (date !== null) {
4445
+ const parsed = parseISODate(date);
4446
+ parts.push(parsed === null ? date : new Intl.DateTimeFormat(locale, {
4447
+ dateStyle: 'medium',
4448
+ timeZone: 'UTC'
4449
+ }).format(Date.UTC(parsed.year, parsed.month - 1, parsed.day)));
4450
+ }
4451
+ if (time !== null) {
4452
+ parts.push(formatTime(time, {
4453
+ hour12
4454
+ }));
4455
+ }
4456
+ return parts.join(', ');
4457
+ }
4458
+ /**
4459
+ * Dashforge TW `<DateTimePicker>` — a form-bound date + time field.
4460
+ *
4461
+ * A read-only trigger button paired with a popover combining a `<Calendar>`
4462
+ * and a time list (Radix Popover). Integrates with the Dashforge form
4463
+ * bridge + RBAC.
4464
+ *
4465
+ * Storage contract: a naive ISO datetime `"YYYY-MM-DDTHH:mm"` — no seconds,
4466
+ * no timezone. Shared with the MUI `@dashforge/ui` `DateTimePicker`.
4467
+ */ function DateTimePicker(props) {
4468
+ var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
4469
+ const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, stepMinutes, hour12 = false, fullWidth, sx, slotProps, testId } = props;
4470
+ const bridge = useContext(DashFormContext);
4471
+ const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
4472
+ const fieldMeta = useDashFieldMeta(name);
4473
+ const accessState = useAccessState(access);
4474
+ const fieldId = useId();
4475
+ const helperId = `${fieldId}-help`;
4476
+ const [isOpen, setIsOpen] = useState(false);
4477
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
4478
+ const unregisterRef = useRef({
4479
+ bridge,
4480
+ name
4481
+ });
4482
+ unregisterRef.current = {
4483
+ bridge,
4484
+ name
4485
+ };
4486
+ const isMountedRef = useRef(false);
4487
+ useEffect(()=>{
4488
+ isMountedRef.current = true;
4489
+ return ()=>{
4490
+ isMountedRef.current = false;
4491
+ const { bridge: cap, name: capName } = unregisterRef.current;
4492
+ queueMicrotask(()=>{
4493
+ var _cap_unregister;
4494
+ if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
4495
+ });
4496
+ };
4497
+ }, []);
4498
+ const effectiveDisabled = Boolean(disabled) || accessState.disabled;
4499
+ const isInteractive = !effectiveDisabled && !accessState.readonly;
4500
+ const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
4501
+ let resolvedError = error;
4502
+ let resolvedHelperText = helperText;
4503
+ let registration = null;
4504
+ let resolvedValue;
4505
+ if (isFormMode && bridge) {
4506
+ registration = bridge.register(name, rules);
4507
+ const validation = resolveValidationState(name, bridge, error, helperText);
4508
+ resolvedError = validation.error;
4509
+ resolvedHelperText = validation.helperText;
4510
+ if (explicitValue !== undefined) {
4511
+ resolvedValue = explicitValue;
4512
+ } else {
4513
+ const bv = fieldMeta.value;
4514
+ resolvedValue = bv == null ? null : String(bv);
4515
+ }
4516
+ } else if (explicitValue !== undefined) {
4517
+ resolvedValue = explicitValue;
4518
+ } else {
4519
+ resolvedValue = internalValue;
4520
+ }
4521
+ const registrationRefFn = registration == null ? void 0 : registration.ref;
4522
+ const triggerRef = useCallback((instance)=>{
4523
+ if (typeof registrationRefFn === 'function') {
4524
+ registrationRefFn(instance);
4525
+ }
4526
+ }, [
4527
+ registrationRefFn
4528
+ ]);
4529
+ if (!isVisible) return null;
4530
+ if (!accessState.visible) return null;
4531
+ const v = dateTimePickerVariants({
4532
+ layout,
4533
+ error: resolvedError,
4534
+ fullWidth
4535
+ });
4536
+ const resolvedLocale = locale != null ? locale : 'en-US';
4537
+ const { date, time } = splitDateTime(resolvedValue);
4538
+ const displayValue = formatDateTime(resolvedValue, resolvedLocale, hour12);
4539
+ const options = generateTimeOptions(_extends({}, stepMinutes !== undefined && {
4540
+ stepMinutes
4541
+ }));
4542
+ const commitValue = (next)=>{
4543
+ if (isFormMode && bridge) {
4544
+ var _registration_onChange;
4545
+ bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
4546
+ void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
4547
+ target: {
4548
+ name,
4549
+ value: next != null ? next : ''
4550
+ },
4551
+ type: 'change'
4552
+ }));
4553
+ } else if (explicitValue === undefined) {
4554
+ setInternalValue(next);
4555
+ }
4556
+ onChange == null ? void 0 : onChange(next);
4557
+ };
4558
+ const markTouched = ()=>{
4559
+ if (isFormMode && bridge) {
4560
+ var _registration_onBlur;
4561
+ const committed = bridge.getValue(name);
4562
+ void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
4563
+ target: {
4564
+ name,
4565
+ value: committed == null ? '' : String(committed)
4566
+ },
4567
+ type: 'blur'
4568
+ }));
4569
+ }
4570
+ };
4571
+ const handleOpenChange = (next)=>{
4572
+ if (next && !isInteractive) {
4573
+ return;
4574
+ }
4575
+ setIsOpen(next);
4576
+ if (!next) {
4577
+ markTouched();
4578
+ }
4579
+ };
4580
+ // Picking a date keeps the popover open — the user still needs a time.
4581
+ const handleDateSelect = (nextDate)=>{
4582
+ commitValue(joinDateTime(nextDate, time));
4583
+ };
4584
+ // Picking a time completes the value and closes the popover.
4585
+ const handleTimeSelect = (nextTime)=>{
4586
+ commitValue(joinDateTime(date, nextTime));
4587
+ setIsOpen(false);
4588
+ markTouched();
4589
+ };
4590
+ return jsxs("div", {
4591
+ "data-testid": testId,
4592
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
4593
+ children: [
4594
+ label && jsxs("label", {
4595
+ htmlFor: fieldId,
4596
+ className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
4597
+ children: [
4598
+ label,
4599
+ required && jsx("span", {
4600
+ "aria-hidden": "true",
4601
+ className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
4602
+ children: "*"
4603
+ })
4604
+ ]
4605
+ }),
4606
+ jsx(Popover, {
4607
+ open: isOpen,
4608
+ onOpenChange: handleOpenChange,
4609
+ side: "bottom",
4610
+ align: "start",
4611
+ content: jsxs("div", {
4612
+ className: v.panel(),
4613
+ children: [
4614
+ jsx(Calendar, {
4615
+ value: date,
4616
+ onChange: handleDateSelect,
4617
+ minDate: minDate,
4618
+ maxDate: maxDate,
4619
+ disabledDates: disabledDates,
4620
+ isDateDisabled: isDateDisabled,
4621
+ weekStartDay: weekStartDay,
4622
+ locale: locale,
4623
+ "aria-label": "Choose date",
4624
+ sx: "border-0 bg-transparent p-0"
4625
+ }),
4626
+ jsx("div", {
4627
+ role: "listbox",
4628
+ "aria-label": "Time options",
4629
+ className: v.list(),
4630
+ children: options.map((option)=>jsx("button", {
4631
+ type: "button",
4632
+ role: "option",
4633
+ "aria-selected": option === time,
4634
+ className: v.option(),
4635
+ onClick: ()=>{
4636
+ handleTimeSelect(option);
4637
+ },
4638
+ children: formatTime(option, {
4639
+ hour12
4640
+ })
4641
+ }, option))
4642
+ })
4643
+ ]
4644
+ }),
4645
+ children: jsxs("button", {
4646
+ type: "button",
4647
+ id: fieldId,
4648
+ ref: triggerRef,
4649
+ disabled: effectiveDisabled,
4650
+ "aria-invalid": resolvedError ? true : undefined,
4651
+ "aria-describedby": resolvedHelperText ? helperId : undefined,
4652
+ className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
4653
+ children: [
4654
+ jsx("span", {
4655
+ className: displayValue ? v.value() : v.placeholder(),
4656
+ children: displayValue || placeholder || 'Select date and time'
4657
+ }),
4658
+ jsx("span", {
4659
+ className: v.icon(),
4660
+ children: jsx(CalendarIcon$1, {})
4091
4661
  })
4092
4662
  ]
4093
4663
  })
4664
+ }),
4665
+ resolvedHelperText && jsx("p", {
4666
+ id: helperId,
4667
+ className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
4668
+ children: resolvedHelperText
4094
4669
  })
4095
4670
  ]
4096
4671
  });
4097
4672
  }
4098
4673
 
4099
4674
  /**
4100
- * Slots for `<DatePicker>` — a bridge-integrated date field whose trigger
4101
- * is a `<button>` styled as an input, opening a `<Calendar>` popover.
4675
+ * Slots for `<DateRangePicker>` — a bridge-integrated start/end date field
4676
+ * whose trigger is a `<button>` styled as an input, opening a dual-month
4677
+ * range calendar popover.
4102
4678
  *
4103
4679
  * Theme identity: bare neutral / brand token-backed classes only — no
4104
4680
  * `dark:` variants on the neutral palette (the preset CSS-var swap
4105
4681
  * auto-inverts). See `_shared/themeIdentity.test.ts`.
4106
- */ const datePickerVariants = tv({
4682
+ */ const dateRangePickerVariants = tv({
4107
4683
  slots: {
4108
4684
  root: 'flex flex-col',
4109
4685
  label: 'block text-sm font-medium text-neutral-900 mb-1',
4110
4686
  requiredMark: 'text-danger-500 ml-0.5',
4111
- trigger: 'flex items-center gap-2 h-10 min-w-[12rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
4687
+ trigger: 'flex items-center gap-2 h-10 min-w-[15rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
4112
4688
  value: 'flex-1 truncate text-left',
4113
4689
  placeholder: 'flex-1 truncate text-left text-neutral-400',
4114
4690
  icon: 'shrink-0 text-[1rem] text-neutral-500',
4691
+ panel: 'flex gap-3 p-3 select-none',
4692
+ month: 'flex flex-col gap-2',
4693
+ header: 'flex items-center justify-between gap-1',
4694
+ navButton: 'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-[1rem] text-neutral-600 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500',
4695
+ navSpacer: 'h-7 w-7 shrink-0',
4696
+ monthLabel: 'flex-1 text-center text-sm font-semibold text-neutral-900',
4697
+ grid: 'flex flex-col gap-1',
4698
+ weekdayRow: 'grid grid-cols-7 gap-1',
4699
+ weekday: 'flex h-8 items-center justify-center text-xs font-semibold text-neutral-500',
4700
+ weekRow: 'grid grid-cols-7 gap-1',
4115
4701
  helperText: 'mt-1 text-sm text-neutral-600',
4116
4702
  errorText: 'mt-1 text-sm text-danger-600'
4117
4703
  },
@@ -4141,8 +4727,55 @@ const popoverVariants = tv({
4141
4727
  fullWidth: false
4142
4728
  }
4143
4729
  });
4730
+ /**
4731
+ * Per-day-cell recipe for the range grid. `endpoint` (a committed start/end)
4732
+ * is the solid fill; `inBand` (inside the committed or previewed range) is
4733
+ * the light tint. Declared `inBand` before `endpoint` so an endpoint cell —
4734
+ * which is also in-band — resolves to the solid fill.
4735
+ */ const dateRangeDayVariants = tv({
4736
+ base: 'flex h-9 w-9 items-center justify-center rounded-md text-sm text-neutral-900 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500',
4737
+ variants: {
4738
+ inBand: {
4739
+ true: 'bg-primary-100 hover:bg-primary-100'
4740
+ },
4741
+ endpoint: {
4742
+ true: 'bg-primary-600 text-white hover:bg-primary-600'
4743
+ },
4744
+ siblingMonth: {
4745
+ true: 'text-neutral-400'
4746
+ },
4747
+ today: {
4748
+ true: 'font-bold ring-1 ring-inset ring-primary-400'
4749
+ },
4750
+ disabled: {
4751
+ true: 'cursor-default text-neutral-300 opacity-60 hover:bg-transparent'
4752
+ }
4753
+ },
4754
+ compoundVariants: [
4755
+ {
4756
+ endpoint: true,
4757
+ today: true,
4758
+ class: 'ring-0'
4759
+ },
4760
+ {
4761
+ endpoint: true,
4762
+ siblingMonth: true,
4763
+ class: 'text-white'
4764
+ }
4765
+ ],
4766
+ defaultVariants: {
4767
+ inBand: false,
4768
+ endpoint: false,
4769
+ siblingMonth: false,
4770
+ today: false,
4771
+ disabled: false
4772
+ }
4773
+ });
4144
4774
 
4145
- // Inline 16×16 stroke calendar glyph — no icon dependency (tw convention).
4775
+ const EMPTY_RANGE = {
4776
+ start: null,
4777
+ end: null
4778
+ };
4146
4779
  function CalendarIcon() {
4147
4780
  return jsxs("svg", {
4148
4781
  width: "1em",
@@ -4169,12 +4802,40 @@ function CalendarIcon() {
4169
4802
  ]
4170
4803
  });
4171
4804
  }
4172
- /**
4173
- * Formats a stored ISO date for the trigger display, localized to a medium
4174
- * date style (e.g. `"May 20, 2026"`). Formatting is done in UTC so the
4175
- * displayed day always matches the stored `YYYY-MM-DD`.
4176
- */ function formatDisplayDate(iso, locale) {
4177
- if (iso == null || iso === '') {
4805
+ function ChevronLeftIcon() {
4806
+ return jsx("svg", {
4807
+ width: "1em",
4808
+ height: "1em",
4809
+ viewBox: "0 0 16 16",
4810
+ fill: "none",
4811
+ "aria-hidden": "true",
4812
+ children: jsx("path", {
4813
+ d: "M10 4 6 8l4 4",
4814
+ stroke: "currentColor",
4815
+ strokeWidth: "1.5",
4816
+ strokeLinecap: "round",
4817
+ strokeLinejoin: "round"
4818
+ })
4819
+ });
4820
+ }
4821
+ function ChevronRightIcon$1() {
4822
+ return jsx("svg", {
4823
+ width: "1em",
4824
+ height: "1em",
4825
+ viewBox: "0 0 16 16",
4826
+ fill: "none",
4827
+ "aria-hidden": "true",
4828
+ children: jsx("path", {
4829
+ d: "m6 4 4 4-4 4",
4830
+ stroke: "currentColor",
4831
+ strokeWidth: "1.5",
4832
+ strokeLinecap: "round",
4833
+ strokeLinejoin: "round"
4834
+ })
4835
+ });
4836
+ }
4837
+ /** Formats a stored ISO date for the trigger display (medium style). */ function formatEndpoint(iso, locale) {
4838
+ if (iso === null || iso === '') {
4178
4839
  return '';
4179
4840
  }
4180
4841
  const parts = parseISODate(iso);
@@ -4186,20 +4847,182 @@ function CalendarIcon() {
4186
4847
  timeZone: 'UTC'
4187
4848
  }).format(Date.UTC(parts.year, parts.month - 1, parts.day));
4188
4849
  }
4850
+ function formatRange(range, locale) {
4851
+ const start = formatEndpoint(range.start, locale);
4852
+ const end = formatEndpoint(range.end, locale);
4853
+ if (start === '') {
4854
+ return '';
4855
+ }
4856
+ return end === '' ? start : `${start} – ${end}`;
4857
+ }
4858
+ /** The dual-month range grid rendered inside the popover. */ function RangeCalendar(props) {
4859
+ const { value, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale } = props;
4860
+ const range = useDateRange(_extends({
4861
+ value,
4862
+ onChange,
4863
+ monthCount: 2
4864
+ }, minDate !== undefined && {
4865
+ minDate
4866
+ }, maxDate !== undefined && {
4867
+ maxDate
4868
+ }, disabledDates !== undefined && {
4869
+ disabledDates
4870
+ }, isDateDisabled !== undefined && {
4871
+ isDateDisabled
4872
+ }, weekStartDay !== undefined && {
4873
+ weekStartDay
4874
+ }, locale !== undefined && {
4875
+ locale
4876
+ }));
4877
+ const focusedCellRef = useRef(null);
4878
+ const shouldFocusRef = useRef(false);
4879
+ useEffect(()=>{
4880
+ if (shouldFocusRef.current && focusedCellRef.current) {
4881
+ focusedCellRef.current.focus({
4882
+ preventScroll: true
4883
+ });
4884
+ shouldFocusRef.current = false;
4885
+ }
4886
+ }, [
4887
+ range.focusedDate
4888
+ ]);
4889
+ const handleKeyDown = (event)=>{
4890
+ const action = resolveCalendarKey(event.key);
4891
+ if (action === null) {
4892
+ return;
4893
+ }
4894
+ event.preventDefault();
4895
+ shouldFocusRef.current = true;
4896
+ switch(action){
4897
+ case 'select':
4898
+ range.selectDate(range.focusedDate);
4899
+ break;
4900
+ case 'monthForward':
4901
+ range.goToNextMonth();
4902
+ break;
4903
+ case 'monthBackward':
4904
+ range.goToPreviousMonth();
4905
+ break;
4906
+ default:
4907
+ range.navigate(action);
4908
+ }
4909
+ };
4910
+ const v = dateRangePickerVariants();
4911
+ const resolvedLocale = locale != null ? locale : 'en-US';
4912
+ const lastIndex = range.months.length - 1;
4913
+ return jsx("div", {
4914
+ className: v.panel(),
4915
+ onMouseLeave: ()=>{
4916
+ range.hoverDate(null);
4917
+ },
4918
+ children: range.months.map((monthView, monthIndex)=>jsxs("div", {
4919
+ className: v.month(),
4920
+ children: [
4921
+ jsxs("div", {
4922
+ className: v.header(),
4923
+ children: [
4924
+ monthIndex === 0 ? jsx("button", {
4925
+ type: "button",
4926
+ className: v.navButton(),
4927
+ "aria-label": "Previous month",
4928
+ onClick: ()=>{
4929
+ range.goToPreviousMonth();
4930
+ },
4931
+ children: jsx(ChevronLeftIcon, {})
4932
+ }) : jsx("span", {
4933
+ className: v.navSpacer()
4934
+ }),
4935
+ jsx("span", {
4936
+ "aria-live": "polite",
4937
+ className: v.monthLabel(),
4938
+ children: monthView.monthLabel
4939
+ }),
4940
+ monthIndex === lastIndex ? jsx("button", {
4941
+ type: "button",
4942
+ className: v.navButton(),
4943
+ "aria-label": "Next month",
4944
+ onClick: ()=>{
4945
+ range.goToNextMonth();
4946
+ },
4947
+ children: jsx(ChevronRightIcon$1, {})
4948
+ }) : jsx("span", {
4949
+ className: v.navSpacer()
4950
+ })
4951
+ ]
4952
+ }),
4953
+ jsxs("div", {
4954
+ role: "grid",
4955
+ "aria-label": "Choose date range",
4956
+ onKeyDown: handleKeyDown,
4957
+ className: v.grid(),
4958
+ children: [
4959
+ jsx("div", {
4960
+ role: "row",
4961
+ className: v.weekdayRow(),
4962
+ children: range.weekdayNames.map((dayName, index)=>jsx("span", {
4963
+ role: "columnheader",
4964
+ className: v.weekday(),
4965
+ children: dayName
4966
+ }, `weekday-${String(index)}`))
4967
+ }),
4968
+ monthView.weeks.map((week)=>jsx("div", {
4969
+ role: "row",
4970
+ className: v.weekRow(),
4971
+ children: week.days.map((day)=>{
4972
+ const isActiveCell = day.iso === range.focusedDate;
4973
+ const endpoint = day.isRangeStart || day.isRangeEnd;
4974
+ const inBand = day.isInRange || day.isRangePreview;
4975
+ return jsx("span", {
4976
+ role: "gridcell",
4977
+ className: "flex",
4978
+ children: jsx("button", {
4979
+ type: "button",
4980
+ ref: isActiveCell ? focusedCellRef : null,
4981
+ tabIndex: isActiveCell ? 0 : -1,
4982
+ "aria-label": formatLongDate({
4983
+ year: day.year,
4984
+ month: day.month,
4985
+ day: day.day
4986
+ }, resolvedLocale),
4987
+ "aria-current": day.isToday ? 'date' : undefined,
4988
+ "aria-pressed": endpoint,
4989
+ "aria-disabled": day.isDisabled,
4990
+ onClick: ()=>{
4991
+ range.selectDate(day.iso);
4992
+ },
4993
+ onMouseEnter: ()=>{
4994
+ range.hoverDate(day.iso);
4995
+ },
4996
+ className: dateRangeDayVariants({
4997
+ inBand,
4998
+ endpoint,
4999
+ siblingMonth: day.isSiblingMonth,
5000
+ today: day.isToday,
5001
+ disabled: day.isDisabled
5002
+ }),
5003
+ children: day.day
5004
+ })
5005
+ }, day.iso);
5006
+ })
5007
+ }, week.key))
5008
+ ]
5009
+ })
5010
+ ]
5011
+ }, `${String(monthView.year)}-${String(monthView.month)}`))
5012
+ });
5013
+ }
4189
5014
  /**
4190
- * Dashforge TW `<DatePicker>` — a form-bound single-date field.
5015
+ * Dashforge TW `<DateRangePicker>` — a form-bound start/end date field.
4191
5016
  *
4192
- * A read-only trigger button paired with a `<Calendar>` popover (Radix
4193
- * Popover — toggle / Escape / outside-click / focus-trap are Radix-managed).
4194
- * Integrates with the Dashforge form bridge + RBAC, mirroring the other
4195
- * `@dashforge/tw` form fields.
5017
+ * A read-only trigger button paired with a dual-month range calendar popover
5018
+ * (Radix Popover), built on the headless `useDateRange` engine. Integrates
5019
+ * with the Dashforge form bridge + RBAC.
4196
5020
  *
4197
- * Storage contract: an ISO `YYYY-MM-DD` string, or `null` — shared with the
4198
- * MUI `@dashforge/ui` `DatePicker`.
4199
- */ function DatePicker(props) {
5021
+ * Storage contract: a `{ start, end }` pair of ISO `YYYY-MM-DD` dates —
5022
+ * shared with the MUI `@dashforge/ui` `DateRangePicker`.
5023
+ */ function DateRangePicker(props) {
4200
5024
  var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
4201
5025
  const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, fullWidth, sx, slotProps, testId } = props;
4202
- // ───── Hooks (unconditional, before any early return) ─────
4203
5026
  const bridge = useContext(DashFormContext);
4204
5027
  const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
4205
5028
  const fieldMeta = useDashFieldMeta(name);
@@ -4207,8 +5030,7 @@ function CalendarIcon() {
4207
5030
  const fieldId = useId();
4208
5031
  const helperId = `${fieldId}-help`;
4209
5032
  const [isOpen, setIsOpen] = useState(false);
4210
- const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
4211
- // StrictMode-safe unregister-on-unmount.
5033
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : EMPTY_RANGE);
4212
5034
  const unregisterRef = useRef({
4213
5035
  bridge,
4214
5036
  name
@@ -4229,10 +5051,8 @@ function CalendarIcon() {
4229
5051
  });
4230
5052
  };
4231
5053
  }, []);
4232
- // ───── Derived ─────
4233
5054
  const effectiveDisabled = Boolean(disabled) || accessState.disabled;
4234
- const effectiveReadOnly = accessState.readonly;
4235
- const isInteractive = !effectiveDisabled && !effectiveReadOnly;
5055
+ const isInteractive = !effectiveDisabled && !accessState.readonly;
4236
5056
  const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
4237
5057
  let resolvedError = error;
4238
5058
  let resolvedHelperText = helperText;
@@ -4247,14 +5067,13 @@ function CalendarIcon() {
4247
5067
  resolvedValue = explicitValue;
4248
5068
  } else {
4249
5069
  const bv = fieldMeta.value;
4250
- resolvedValue = bv == null ? null : String(bv);
5070
+ resolvedValue = bv != null ? bv : EMPTY_RANGE;
4251
5071
  }
4252
5072
  } else if (explicitValue !== undefined) {
4253
5073
  resolvedValue = explicitValue;
4254
5074
  } else {
4255
5075
  resolvedValue = internalValue;
4256
5076
  }
4257
- // RHF threads its own callback ref through here in form mode.
4258
5077
  const registrationRefFn = registration == null ? void 0 : registration.ref;
4259
5078
  const triggerRef = useCallback((instance)=>{
4260
5079
  if (typeof registrationRefFn === 'function') {
@@ -4263,16 +5082,15 @@ function CalendarIcon() {
4263
5082
  }, [
4264
5083
  registrationRefFn
4265
5084
  ]);
4266
- // ───── Render-time guards (after all hooks) ─────
4267
5085
  if (!isVisible) return null;
4268
5086
  if (!accessState.visible) return null;
4269
- const v = datePickerVariants({
5087
+ const v = dateRangePickerVariants({
4270
5088
  layout,
4271
5089
  error: resolvedError,
4272
5090
  fullWidth
4273
5091
  });
4274
5092
  const resolvedLocale = locale != null ? locale : 'en-US';
4275
- const displayValue = formatDisplayDate(resolvedValue, resolvedLocale);
5093
+ const displayValue = formatRange(resolvedValue, resolvedLocale);
4276
5094
  const commitValue = (next)=>{
4277
5095
  if (isFormMode && bridge) {
4278
5096
  var _registration_onChange;
@@ -4280,7 +5098,7 @@ function CalendarIcon() {
4280
5098
  void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
4281
5099
  target: {
4282
5100
  name,
4283
- value: next != null ? next : ''
5101
+ value: next
4284
5102
  },
4285
5103
  type: 'change'
4286
5104
  }));
@@ -4289,9 +5107,6 @@ function CalendarIcon() {
4289
5107
  }
4290
5108
  onChange == null ? void 0 : onChange(next);
4291
5109
  };
4292
- // Mark the field touched on popover close. Reads the freshest committed
4293
- // value straight from the bridge so the touched-blur never reverts a
4294
- // value just chosen in the calendar.
4295
5110
  const markTouched = ()=>{
4296
5111
  if (isFormMode && bridge) {
4297
5112
  var _registration_onBlur;
@@ -4299,7 +5114,7 @@ function CalendarIcon() {
4299
5114
  void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
4300
5115
  target: {
4301
5116
  name,
4302
- value: committed == null ? '' : String(committed)
5117
+ value: committed
4303
5118
  },
4304
5119
  type: 'blur'
4305
5120
  }));
@@ -4314,10 +5129,12 @@ function CalendarIcon() {
4314
5129
  markTouched();
4315
5130
  }
4316
5131
  };
4317
- const handleCalendarChange = (next)=>{
5132
+ const handleRangeChange = (next)=>{
4318
5133
  commitValue(next);
4319
- setIsOpen(false);
4320
- markTouched();
5134
+ if (next.start !== null && next.end !== null) {
5135
+ setIsOpen(false);
5136
+ markTouched();
5137
+ }
4321
5138
  };
4322
5139
  return jsxs("div", {
4323
5140
  "data-testid": testId,
@@ -4340,17 +5157,15 @@ function CalendarIcon() {
4340
5157
  onOpenChange: handleOpenChange,
4341
5158
  side: "bottom",
4342
5159
  align: "start",
4343
- content: jsx(Calendar, {
5160
+ content: jsx(RangeCalendar, {
4344
5161
  value: resolvedValue,
4345
- onChange: handleCalendarChange,
5162
+ onChange: handleRangeChange,
4346
5163
  minDate: minDate,
4347
5164
  maxDate: maxDate,
4348
5165
  disabledDates: disabledDates,
4349
5166
  isDateDisabled: isDateDisabled,
4350
5167
  weekStartDay: weekStartDay,
4351
- locale: locale,
4352
- "aria-label": "Choose date",
4353
- sx: "border-0 bg-transparent p-0"
5168
+ locale: locale
4354
5169
  }),
4355
5170
  children: jsxs("button", {
4356
5171
  type: "button",
@@ -4363,7 +5178,7 @@ function CalendarIcon() {
4363
5178
  children: [
4364
5179
  jsx("span", {
4365
5180
  className: displayValue ? v.value() : v.placeholder(),
4366
- children: displayValue || placeholder || 'Select a date'
5181
+ children: displayValue || placeholder || 'Select a date range'
4367
5182
  }),
4368
5183
  jsx("span", {
4369
5184
  className: v.icon(),
@@ -7670,51 +8485,161 @@ const tabsVariants = tv({
7670
8485
  }
7671
8486
  });
7672
8487
 
8488
+ function useTabs(options) {
8489
+ var _ref;
8490
+ var _items_;
8491
+ const { items, value, defaultValue, onValueChange, orientation } = options;
8492
+ const baseId = useId();
8493
+ const firstValue = (_ref = (_items_ = items[0]) == null ? void 0 : _items_.value) != null ? _ref : '';
8494
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : firstValue);
8495
+ const isControlled = value !== undefined;
8496
+ const activeValue = isControlled ? value : internalValue;
8497
+ // Tab buttons, keyed by value — used to move DOM focus on keyboard nav.
8498
+ const tabRefs = useRef(new Map());
8499
+ const select = useCallback((next)=>{
8500
+ if (!isControlled) {
8501
+ setInternalValue(next);
8502
+ }
8503
+ onValueChange == null ? void 0 : onValueChange(next);
8504
+ }, [
8505
+ isControlled,
8506
+ onValueChange
8507
+ ]);
8508
+ const handleKeyDown = useCallback((event)=>{
8509
+ var _tabRefs_current_get;
8510
+ const nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';
8511
+ const prevKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
8512
+ const enabled = items.filter((item)=>item.disabled !== true);
8513
+ if (enabled.length === 0) {
8514
+ return;
8515
+ }
8516
+ const currentIndex = enabled.findIndex((item)=>item.value === activeValue);
8517
+ let targetIndex = null;
8518
+ if (event.key === nextKey) {
8519
+ targetIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % enabled.length;
8520
+ } else if (event.key === prevKey) {
8521
+ targetIndex = currentIndex < 0 ? enabled.length - 1 : (currentIndex - 1 + enabled.length) % enabled.length;
8522
+ } else if (event.key === 'Home') {
8523
+ targetIndex = 0;
8524
+ } else if (event.key === 'End') {
8525
+ targetIndex = enabled.length - 1;
8526
+ }
8527
+ if (targetIndex === null) {
8528
+ return;
8529
+ }
8530
+ event.preventDefault();
8531
+ const target = enabled[targetIndex];
8532
+ if (target === undefined) {
8533
+ return;
8534
+ }
8535
+ select(target.value);
8536
+ (_tabRefs_current_get = tabRefs.current.get(target.value)) == null ? void 0 : _tabRefs_current_get.focus();
8537
+ }, [
8538
+ items,
8539
+ orientation,
8540
+ activeValue,
8541
+ select
8542
+ ]);
8543
+ const getTabListProps = ()=>({
8544
+ role: 'tablist',
8545
+ 'aria-orientation': orientation,
8546
+ onKeyDown: handleKeyDown
8547
+ });
8548
+ const getTabProps = (item)=>{
8549
+ const active = item.value === activeValue;
8550
+ return {
8551
+ role: 'tab',
8552
+ id: `${baseId}-tab-${item.value}`,
8553
+ 'aria-selected': active,
8554
+ 'aria-controls': `${baseId}-panel-${item.value}`,
8555
+ tabIndex: active ? 0 : -1,
8556
+ disabled: item.disabled,
8557
+ 'data-state': active ? 'active' : 'inactive',
8558
+ onClick: ()=>{
8559
+ if (item.disabled !== true) {
8560
+ select(item.value);
8561
+ }
8562
+ },
8563
+ ref: (el)=>{
8564
+ if (el) {
8565
+ tabRefs.current.set(item.value, el);
8566
+ } else {
8567
+ tabRefs.current.delete(item.value);
8568
+ }
8569
+ }
8570
+ };
8571
+ };
8572
+ const getPanelProps = (item)=>{
8573
+ const active = item.value === activeValue;
8574
+ return {
8575
+ role: 'tabpanel',
8576
+ id: `${baseId}-panel-${item.value}`,
8577
+ 'aria-labelledby': `${baseId}-tab-${item.value}`,
8578
+ tabIndex: 0,
8579
+ hidden: !active,
8580
+ 'data-state': active ? 'active' : 'inactive'
8581
+ };
8582
+ };
8583
+ return {
8584
+ activeValue,
8585
+ getTabListProps,
8586
+ getTabProps,
8587
+ getPanelProps
8588
+ };
8589
+ }
8590
+
7673
8591
  /**
7674
8592
  * Dashforge TW `<Tabs>` — declarative tab navigation.
7675
8593
  *
7676
- * Built on `@radix-ui/react-tabs`:
7677
- * - APG tabs pattern (arrow-key navigation, automatic activation,
7678
- * `role="tablist"` + `role="tab"` + `role="tabpanel"`).
7679
- * - Controlled / uncontrolled (`value` / `defaultValue`).
8594
+ * Custom clean-room implementation on the headless `useTabs` engine — no
8595
+ * runtime UI dependency. Implements the WAI-ARIA APG tabs pattern: arrow-key
8596
+ * navigation, automatic activation, roving tabindex, `role="tablist"` +
8597
+ * `role="tab"` + `role="tabpanel"`.
7680
8598
  *
7681
8599
  * Two variant axes:
7682
8600
  * - `variant` — `underline` (default) | `pill`
7683
8601
  * - `orientation` — `horizontal` (default) | `vertical`
8602
+ *
8603
+ * Controlled (`value` + `onValueChange`) or uncontrolled (`defaultValue`).
8604
+ * `keepMounted` keeps inactive panels in the DOM (default: only the active
8605
+ * panel is mounted).
7684
8606
  */ function Tabs(props) {
7685
- var _items_, _slotProps_root, _slotProps_list;
7686
- const { items, value, defaultValue, onValueChange, variant = 'underline', orientation = 'horizontal', sx, slotProps } = props;
8607
+ var _slotProps_root, _slotProps_list;
8608
+ const { items, value, defaultValue, onValueChange, variant = 'underline', orientation = 'horizontal', keepMounted = false, sx, slotProps } = props;
7687
8609
  const v = tabsVariants({
7688
8610
  variant,
7689
8611
  orientation
7690
8612
  });
7691
- const resolvedDefault = defaultValue != null ? defaultValue : (_items_ = items[0]) == null ? void 0 : _items_.value;
7692
- return jsxs(RadixTabs.Root, {
7693
- value: value,
7694
- defaultValue: resolvedDefault,
7695
- onValueChange: onValueChange,
7696
- orientation: orientation,
8613
+ const tabs = useTabs({
8614
+ items,
8615
+ value,
8616
+ defaultValue,
8617
+ onValueChange,
8618
+ orientation
8619
+ });
8620
+ // Either all panels (hidden when inactive) or just the active one.
8621
+ const panels = keepMounted ? items : items.filter((item)=>item.value === tabs.activeValue);
8622
+ return jsxs("div", {
7697
8623
  className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
7698
8624
  children: [
7699
- jsx(RadixTabs.List, {
8625
+ jsx("div", _extends({}, tabs.getTabListProps(), {
7700
8626
  className: cn(v.list(), slotProps == null ? void 0 : (_slotProps_list = slotProps.list) == null ? void 0 : _slotProps_list.className),
7701
8627
  children: items.map((item)=>{
7702
8628
  var _slotProps_trigger;
7703
- return jsx(RadixTabs.Trigger, {
7704
- value: item.value,
7705
- disabled: item.disabled,
8629
+ return jsx("button", _extends({
8630
+ type: "button"
8631
+ }, tabs.getTabProps(item), {
7706
8632
  className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
7707
8633
  children: item.label
7708
- }, item.value);
8634
+ }), item.value);
7709
8635
  })
7710
- }),
7711
- items.map((item)=>{
8636
+ })),
8637
+ panels.map((item)=>{
7712
8638
  var _slotProps_content;
7713
- return jsx(RadixTabs.Content, {
7714
- value: item.value,
8639
+ return jsx("div", _extends({}, tabs.getPanelProps(item), {
7715
8640
  className: cn(v.content(), slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
7716
8641
  children: item.content
7717
- }, item.value);
8642
+ }), item.value);
7718
8643
  })
7719
8644
  ]
7720
8645
  });
@@ -11979,6 +12904,6 @@ function SearchIcon(props) {
11979
12904
  */ // ───── Components ─────
11980
12905
  /**
11981
12906
  * Package version (synced with `package.json` at publish time).
11982
- */ const VERSION = '0.7.0-beta';
12907
+ */ const VERSION = '0.10.0-beta';
11983
12908
 
11984
- export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Calendar, Checkbox, ConfirmDialogProvider, Container, DataGrid, DatePicker, DateTimePicker, Dialog, Divider, Grid, LeftNav, NumberField, OTPField, Pagination, Popover, RadioGroup, RenderButton, RenderChip, RenderText, RenderTwoLine, RowActionsMenu, Skeleton, SnackbarProvider, Stack, Switch, Table, Tabs, TextField, Textarea, Tooltip, TopBar, Typography, VERSION, VisuallyHidden, accordionVariants, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, calendarDayVariants, calendarVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dataGridVariants, datePickerVariants, dateTimePickerVariants, dialogVariants, dividerLineVariants, dividerVariants, getNestedValue, gridVariants, isoToInputValue, leftNavVariants, numberFieldVariants, otpFieldVariants, paginationVariants, popoverVariants, radioGroupVariants, skeletonVariants, snackbarVariants, stackVariants, switchVariants, tableVariants, tabsVariants, textFieldVariants, textareaVariants, tooltipVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };
12909
+ export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Calendar, Checkbox, ConfirmDialogProvider, Container, DataGrid, DatePicker, DateRangePicker, DateTimePicker, Dialog, Divider, Grid, LeftNav, NumberField, OTPField, Pagination, Popover, RadioGroup, RenderButton, RenderChip, RenderText, RenderTwoLine, RowActionsMenu, Skeleton, SnackbarProvider, Stack, Switch, Table, Tabs, TextField, Textarea, TimePicker, Tooltip, TopBar, Typography, VERSION, VisuallyHidden, accordionVariants, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, calendarDayVariants, calendarVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dataGridVariants, datePickerVariants, dateRangeDayVariants, dateRangePickerVariants, dateTimePickerVariants, dialogVariants, dividerLineVariants, dividerVariants, getNestedValue, gridVariants, leftNavVariants, numberFieldVariants, otpFieldVariants, paginationVariants, popoverVariants, radioGroupVariants, skeletonVariants, snackbarVariants, stackVariants, switchVariants, tableVariants, tabsVariants, textFieldVariants, textareaVariants, timePickerVariants, tooltipVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };