@dashforge/tw 0.9.0-beta → 0.9.1-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.
- package/CHANGELOG.md +29 -0
- package/dist/index.esm.js +629 -67
- package/dist/src/components/Calendar/Calendar.d.ts +14 -0
- package/dist/src/components/Calendar/Calendar.d.ts.map +1 -0
- package/dist/src/components/Calendar/calendar.types.d.ts +72 -0
- package/dist/src/components/Calendar/calendar.types.d.ts.map +1 -0
- package/dist/src/components/Calendar/calendar.variants.d.ts +99 -0
- package/dist/src/components/Calendar/calendar.variants.d.ts.map +1 -0
- package/dist/src/components/DatePicker/DatePicker.d.ts +14 -0
- package/dist/src/components/DatePicker/DatePicker.d.ts.map +1 -0
- package/dist/src/components/DatePicker/datePicker.types.d.ts +88 -0
- package/dist/src/components/DatePicker/datePicker.types.d.ts.map +1 -0
- package/dist/src/components/DatePicker/datePicker.variants.d.ts +97 -0
- package/dist/src/components/DatePicker/datePicker.variants.d.ts.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/Calendar/Calendar.test.tsx +75 -0
- package/src/components/Calendar/Calendar.tsx +264 -0
- package/src/components/Calendar/calendar.types.ts +65 -0
- package/src/components/Calendar/calendar.variants.ts +64 -0
- package/src/components/DatePicker/DatePicker.test.tsx +76 -0
- package/src/components/DatePicker/DatePicker.tsx +305 -0
- package/src/components/DatePicker/datePicker.types.ts +77 -0
- package/src/components/DatePicker/datePicker.variants.ts +50 -0
- package/src/index.ts +21 -1
- package/tsconfig.lib.json +7 -4
package/dist/index.esm.js
CHANGED
|
@@ -11,10 +11,11 @@ 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';
|
|
15
|
+
import * as RadixPopover from '@radix-ui/react-popover';
|
|
14
16
|
import * as RadixDialog from '@radix-ui/react-dialog';
|
|
15
17
|
import * as RadixTabs from '@radix-ui/react-tabs';
|
|
16
18
|
import * as RadixTooltip from '@radix-ui/react-tooltip';
|
|
17
|
-
import * as RadixPopover from '@radix-ui/react-popover';
|
|
18
19
|
import * as RadixAccordion from '@radix-ui/react-accordion';
|
|
19
20
|
|
|
20
21
|
function _extends() {
|
|
@@ -3754,6 +3755,632 @@ const defaultGetOptionDisabled = (option)=>Boolean(option.disabled);
|
|
|
3754
3755
|
});
|
|
3755
3756
|
}
|
|
3756
3757
|
|
|
3758
|
+
/**
|
|
3759
|
+
* Structural slots for `<Calendar>`.
|
|
3760
|
+
*
|
|
3761
|
+
* Theme identity: every class is a bare neutral / brand token-backed
|
|
3762
|
+
* Tailwind class. The `dashforgePreset()` CSS-variable swap auto-inverts
|
|
3763
|
+
* the neutral palette, so there are NO `dark:` variants here (a `dark:` on
|
|
3764
|
+
* a neutral class would double-invert — see `_shared/themeIdentity.test.ts`).
|
|
3765
|
+
*/ const calendarVariants = tv({
|
|
3766
|
+
slots: {
|
|
3767
|
+
root: 'inline-flex w-fit flex-col gap-2 rounded-lg border border-neutral-200 bg-neutral-50 p-3 select-none',
|
|
3768
|
+
header: 'flex items-center justify-between gap-1',
|
|
3769
|
+
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 disabled:pointer-events-none disabled:opacity-40',
|
|
3770
|
+
monthLabel: 'flex-1 text-center text-sm font-semibold text-neutral-900',
|
|
3771
|
+
grid: 'flex flex-col gap-1 focus:outline-none',
|
|
3772
|
+
weekdayRow: 'grid grid-cols-7 gap-1',
|
|
3773
|
+
weekday: 'flex h-8 items-center justify-center text-xs font-semibold text-neutral-500',
|
|
3774
|
+
weekRow: 'grid grid-cols-7 gap-1'
|
|
3775
|
+
},
|
|
3776
|
+
variants: {
|
|
3777
|
+
disabled: {
|
|
3778
|
+
true: {
|
|
3779
|
+
root: 'opacity-60'
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
},
|
|
3783
|
+
defaultVariants: {
|
|
3784
|
+
disabled: false
|
|
3785
|
+
}
|
|
3786
|
+
});
|
|
3787
|
+
/**
|
|
3788
|
+
* Per-day-cell recipe. Each of the 42 grid cells is rendered with its own
|
|
3789
|
+
* state (`siblingMonth` / `today` / `selected` / `disabled`).
|
|
3790
|
+
*/ const calendarDayVariants = tv({
|
|
3791
|
+
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',
|
|
3792
|
+
variants: {
|
|
3793
|
+
siblingMonth: {
|
|
3794
|
+
true: 'text-neutral-400'
|
|
3795
|
+
},
|
|
3796
|
+
today: {
|
|
3797
|
+
true: 'font-bold ring-1 ring-inset ring-primary-400'
|
|
3798
|
+
},
|
|
3799
|
+
selected: {
|
|
3800
|
+
true: 'bg-primary-600 text-white hover:bg-primary-600'
|
|
3801
|
+
},
|
|
3802
|
+
disabled: {
|
|
3803
|
+
true: 'cursor-default text-neutral-300 opacity-60 hover:bg-transparent'
|
|
3804
|
+
}
|
|
3805
|
+
},
|
|
3806
|
+
compoundVariants: [
|
|
3807
|
+
// A selected cell drops the "today" ring — the solid fill is enough.
|
|
3808
|
+
{
|
|
3809
|
+
selected: true,
|
|
3810
|
+
today: true,
|
|
3811
|
+
class: 'ring-0'
|
|
3812
|
+
}
|
|
3813
|
+
],
|
|
3814
|
+
defaultVariants: {
|
|
3815
|
+
siblingMonth: false,
|
|
3816
|
+
today: false,
|
|
3817
|
+
selected: false,
|
|
3818
|
+
disabled: false
|
|
3819
|
+
}
|
|
3820
|
+
});
|
|
3821
|
+
|
|
3822
|
+
// Inline 16×16 stroke chevrons — no icon dependency (tw convention).
|
|
3823
|
+
function ChevronLeftIcon() {
|
|
3824
|
+
return jsx("svg", {
|
|
3825
|
+
width: "1em",
|
|
3826
|
+
height: "1em",
|
|
3827
|
+
viewBox: "0 0 16 16",
|
|
3828
|
+
fill: "none",
|
|
3829
|
+
"aria-hidden": "true",
|
|
3830
|
+
children: jsx("path", {
|
|
3831
|
+
d: "M10 4 6 8l4 4",
|
|
3832
|
+
stroke: "currentColor",
|
|
3833
|
+
strokeWidth: "1.5",
|
|
3834
|
+
strokeLinecap: "round",
|
|
3835
|
+
strokeLinejoin: "round"
|
|
3836
|
+
})
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
function ChevronRightIcon$1() {
|
|
3840
|
+
return jsx("svg", {
|
|
3841
|
+
width: "1em",
|
|
3842
|
+
height: "1em",
|
|
3843
|
+
viewBox: "0 0 16 16",
|
|
3844
|
+
fill: "none",
|
|
3845
|
+
"aria-hidden": "true",
|
|
3846
|
+
children: jsx("path", {
|
|
3847
|
+
d: "m6 4 4 4-4 4",
|
|
3848
|
+
stroke: "currentColor",
|
|
3849
|
+
strokeWidth: "1.5",
|
|
3850
|
+
strokeLinecap: "round",
|
|
3851
|
+
strokeLinejoin: "round"
|
|
3852
|
+
})
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
/**
|
|
3856
|
+
* `<Calendar>` — a standalone, inline month-grid date picker.
|
|
3857
|
+
*
|
|
3858
|
+
* The Tailwind presentation layer over the headless `useCalendar` engine
|
|
3859
|
+
* from `@dashforge/calendar-core` (the same engine the MUI `@dashforge/ui`
|
|
3860
|
+
* calendar consumes — only the skin differs). Implements the WCAG grid
|
|
3861
|
+
* pattern with roving tab-index focus.
|
|
3862
|
+
*
|
|
3863
|
+
* It is a primitive: no form-bridge / RBAC integration — use `<DatePicker>`
|
|
3864
|
+
* for a form-bound date field.
|
|
3865
|
+
*/ function Calendar(props) {
|
|
3866
|
+
var _slotProps_root, _slotProps_header, _slotProps_grid;
|
|
3867
|
+
const { value, defaultValue, onChange, month, year, defaultMonth, defaultYear, onMonthChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, today, disabled = false, autoFocus = false, sx, slotProps, testId, 'aria-label': ariaLabel = 'Calendar' } = props;
|
|
3868
|
+
const isValueControlled = value !== undefined;
|
|
3869
|
+
const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
|
|
3870
|
+
const selectedValue = isValueControlled ? value : internalValue;
|
|
3871
|
+
const handleSelect = useCallback((iso)=>{
|
|
3872
|
+
if (!isValueControlled) {
|
|
3873
|
+
setInternalValue(iso);
|
|
3874
|
+
}
|
|
3875
|
+
onChange == null ? void 0 : onChange(iso);
|
|
3876
|
+
}, [
|
|
3877
|
+
isValueControlled,
|
|
3878
|
+
onChange
|
|
3879
|
+
]);
|
|
3880
|
+
const calendar = useCalendar({
|
|
3881
|
+
selected: selectedValue,
|
|
3882
|
+
month,
|
|
3883
|
+
year,
|
|
3884
|
+
defaultMonth,
|
|
3885
|
+
defaultYear,
|
|
3886
|
+
onMonthChange,
|
|
3887
|
+
minDate,
|
|
3888
|
+
maxDate,
|
|
3889
|
+
disabledDates,
|
|
3890
|
+
isDateDisabled,
|
|
3891
|
+
weekStartDay,
|
|
3892
|
+
locale,
|
|
3893
|
+
today,
|
|
3894
|
+
onSelect: handleSelect
|
|
3895
|
+
});
|
|
3896
|
+
// Roving focus: the active day cell holds DOM focus; it is moved only
|
|
3897
|
+
// after a keyboard interaction (or on mount when `autoFocus`). `focus`
|
|
3898
|
+
// uses `preventScroll` so opening the calendar inside a popup never
|
|
3899
|
+
// scroll-jumps the page before the popup is positioned.
|
|
3900
|
+
const focusedCellRef = useRef(null);
|
|
3901
|
+
const shouldFocusRef = useRef(autoFocus);
|
|
3902
|
+
useEffect(()=>{
|
|
3903
|
+
if (shouldFocusRef.current && focusedCellRef.current) {
|
|
3904
|
+
focusedCellRef.current.focus({
|
|
3905
|
+
preventScroll: true
|
|
3906
|
+
});
|
|
3907
|
+
shouldFocusRef.current = false;
|
|
3908
|
+
}
|
|
3909
|
+
}, [
|
|
3910
|
+
calendar.focusedDate
|
|
3911
|
+
]);
|
|
3912
|
+
const handleKeyDown = useCallback((event)=>{
|
|
3913
|
+
if (disabled) {
|
|
3914
|
+
return;
|
|
3915
|
+
}
|
|
3916
|
+
const action = resolveCalendarKey(event.key);
|
|
3917
|
+
if (action === null) {
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
event.preventDefault();
|
|
3921
|
+
shouldFocusRef.current = true;
|
|
3922
|
+
switch(action){
|
|
3923
|
+
case 'select':
|
|
3924
|
+
calendar.selectDate(calendar.focusedDate);
|
|
3925
|
+
break;
|
|
3926
|
+
case 'monthForward':
|
|
3927
|
+
calendar.goToNextMonth();
|
|
3928
|
+
break;
|
|
3929
|
+
case 'monthBackward':
|
|
3930
|
+
calendar.goToPreviousMonth();
|
|
3931
|
+
break;
|
|
3932
|
+
default:
|
|
3933
|
+
calendar.navigate(action);
|
|
3934
|
+
}
|
|
3935
|
+
}, [
|
|
3936
|
+
disabled,
|
|
3937
|
+
calendar
|
|
3938
|
+
]);
|
|
3939
|
+
const resolvedLocale = locale != null ? locale : 'en-US';
|
|
3940
|
+
const v = calendarVariants({
|
|
3941
|
+
disabled
|
|
3942
|
+
});
|
|
3943
|
+
return jsxs("div", {
|
|
3944
|
+
"data-testid": testId,
|
|
3945
|
+
className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
|
|
3946
|
+
children: [
|
|
3947
|
+
jsxs("div", {
|
|
3948
|
+
className: cn(v.header(), slotProps == null ? void 0 : (_slotProps_header = slotProps.header) == null ? void 0 : _slotProps_header.className),
|
|
3949
|
+
children: [
|
|
3950
|
+
jsx("button", {
|
|
3951
|
+
type: "button",
|
|
3952
|
+
className: v.navButton(),
|
|
3953
|
+
disabled: disabled,
|
|
3954
|
+
"aria-label": "Previous month",
|
|
3955
|
+
onClick: ()=>{
|
|
3956
|
+
calendar.goToPreviousMonth();
|
|
3957
|
+
},
|
|
3958
|
+
children: jsx(ChevronLeftIcon, {})
|
|
3959
|
+
}),
|
|
3960
|
+
jsx("span", {
|
|
3961
|
+
"aria-live": "polite",
|
|
3962
|
+
className: v.monthLabel(),
|
|
3963
|
+
children: calendar.monthLabel
|
|
3964
|
+
}),
|
|
3965
|
+
jsx("button", {
|
|
3966
|
+
type: "button",
|
|
3967
|
+
className: v.navButton(),
|
|
3968
|
+
disabled: disabled,
|
|
3969
|
+
"aria-label": "Next month",
|
|
3970
|
+
onClick: ()=>{
|
|
3971
|
+
calendar.goToNextMonth();
|
|
3972
|
+
},
|
|
3973
|
+
children: jsx(ChevronRightIcon$1, {})
|
|
3974
|
+
})
|
|
3975
|
+
]
|
|
3976
|
+
}),
|
|
3977
|
+
jsxs("div", {
|
|
3978
|
+
role: "grid",
|
|
3979
|
+
"aria-label": ariaLabel,
|
|
3980
|
+
onKeyDown: handleKeyDown,
|
|
3981
|
+
className: cn(v.grid(), slotProps == null ? void 0 : (_slotProps_grid = slotProps.grid) == null ? void 0 : _slotProps_grid.className),
|
|
3982
|
+
children: [
|
|
3983
|
+
jsx("div", {
|
|
3984
|
+
role: "row",
|
|
3985
|
+
className: v.weekdayRow(),
|
|
3986
|
+
children: calendar.weekdayNames.map((dayName, index)=>jsx("span", {
|
|
3987
|
+
role: "columnheader",
|
|
3988
|
+
className: v.weekday(),
|
|
3989
|
+
children: dayName
|
|
3990
|
+
}, `weekday-${String(index)}`))
|
|
3991
|
+
}),
|
|
3992
|
+
calendar.weeks.map((week)=>jsx("div", {
|
|
3993
|
+
role: "row",
|
|
3994
|
+
className: v.weekRow(),
|
|
3995
|
+
children: week.days.map((day)=>{
|
|
3996
|
+
var _slotProps_day;
|
|
3997
|
+
const isActiveCell = day.iso === calendar.focusedDate;
|
|
3998
|
+
return jsx("span", {
|
|
3999
|
+
role: "gridcell",
|
|
4000
|
+
className: "flex",
|
|
4001
|
+
children: jsx("button", {
|
|
4002
|
+
type: "button",
|
|
4003
|
+
ref: isActiveCell ? focusedCellRef : null,
|
|
4004
|
+
tabIndex: isActiveCell ? 0 : -1,
|
|
4005
|
+
disabled: disabled,
|
|
4006
|
+
"aria-label": formatLongDate({
|
|
4007
|
+
year: day.year,
|
|
4008
|
+
month: day.month,
|
|
4009
|
+
day: day.day
|
|
4010
|
+
}, resolvedLocale),
|
|
4011
|
+
"aria-current": day.isToday ? 'date' : undefined,
|
|
4012
|
+
"aria-pressed": day.isSelected,
|
|
4013
|
+
"aria-disabled": day.isDisabled,
|
|
4014
|
+
onClick: ()=>{
|
|
4015
|
+
calendar.selectDate(day.iso);
|
|
4016
|
+
},
|
|
4017
|
+
className: cn(calendarDayVariants({
|
|
4018
|
+
siblingMonth: day.isSiblingMonth,
|
|
4019
|
+
today: day.isToday,
|
|
4020
|
+
selected: day.isSelected,
|
|
4021
|
+
disabled: day.isDisabled
|
|
4022
|
+
}), slotProps == null ? void 0 : (_slotProps_day = slotProps.day) == null ? void 0 : _slotProps_day.className),
|
|
4023
|
+
children: day.day
|
|
4024
|
+
})
|
|
4025
|
+
}, day.iso);
|
|
4026
|
+
})
|
|
4027
|
+
}, week.key))
|
|
4028
|
+
]
|
|
4029
|
+
})
|
|
4030
|
+
]
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
const popoverVariants = tv({
|
|
4035
|
+
slots: {
|
|
4036
|
+
content: [
|
|
4037
|
+
// Neutral border auto-inverts via CSS-var swap.
|
|
4038
|
+
'z-50 rounded-md border border-neutral-200',
|
|
4039
|
+
// bg-white hardcoded (no auto-invert) so `dark:` is required.
|
|
4040
|
+
// Target `dark:bg-neutral-100` resolves to #171717 in dark mode
|
|
4041
|
+
// (one elevation tier above the page surface).
|
|
4042
|
+
'bg-white dark:bg-neutral-100 p-4 shadow-lg',
|
|
4043
|
+
// Text auto-inverts.
|
|
4044
|
+
'text-sm text-neutral-900',
|
|
4045
|
+
'focus:outline-none',
|
|
4046
|
+
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
4047
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
4048
|
+
'motion-reduce:transition-none motion-reduce:duration-0'
|
|
4049
|
+
],
|
|
4050
|
+
// Arrow follows content bg: `fill-white` in light + matching dark target.
|
|
4051
|
+
arrow: 'fill-white dark:fill-neutral-100'
|
|
4052
|
+
}
|
|
4053
|
+
});
|
|
4054
|
+
|
|
4055
|
+
/**
|
|
4056
|
+
* Dashforge TW `<Popover>` — clickable floating content panel.
|
|
4057
|
+
*
|
|
4058
|
+
* Built on `@radix-ui/react-popover`:
|
|
4059
|
+
* - Focus trap inside the popover (Tab cycles within).
|
|
4060
|
+
* - Outside-click + Escape dismiss.
|
|
4061
|
+
* - APG dialog-like semantics (`role="dialog"` on content).
|
|
4062
|
+
* - Portal-rendered.
|
|
4063
|
+
*
|
|
4064
|
+
* Use `<Popover>` for menus / pickers / settings panels with
|
|
4065
|
+
* interactive children. Use `<Tooltip>` for read-only hints.
|
|
4066
|
+
*/ function Popover(props) {
|
|
4067
|
+
var _slotProps_content, _slotProps_arrow;
|
|
4068
|
+
const { children, content, side = 'bottom', align = 'center', showArrow = false, open, onOpenChange, sideOffset = 8, width, slotProps } = props;
|
|
4069
|
+
const v = popoverVariants();
|
|
4070
|
+
return jsxs(RadixPopover.Root, {
|
|
4071
|
+
open: open,
|
|
4072
|
+
onOpenChange: onOpenChange,
|
|
4073
|
+
children: [
|
|
4074
|
+
jsx(RadixPopover.Trigger, {
|
|
4075
|
+
asChild: true,
|
|
4076
|
+
children: children
|
|
4077
|
+
}),
|
|
4078
|
+
jsx(RadixPopover.Portal, {
|
|
4079
|
+
children: jsxs(RadixPopover.Content, {
|
|
4080
|
+
side: side,
|
|
4081
|
+
align: align,
|
|
4082
|
+
sideOffset: sideOffset,
|
|
4083
|
+
style: width != null ? {
|
|
4084
|
+
width
|
|
4085
|
+
} : undefined,
|
|
4086
|
+
className: cn(v.content(), slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
|
|
4087
|
+
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)
|
|
4091
|
+
})
|
|
4092
|
+
]
|
|
4093
|
+
})
|
|
4094
|
+
})
|
|
4095
|
+
]
|
|
4096
|
+
});
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
/**
|
|
4100
|
+
* Slots for `<DatePicker>` — a bridge-integrated date field whose trigger
|
|
4101
|
+
* is a `<button>` styled as an input, opening a `<Calendar>` popover.
|
|
4102
|
+
*
|
|
4103
|
+
* Theme identity: bare neutral / brand token-backed classes only — no
|
|
4104
|
+
* `dark:` variants on the neutral palette (the preset CSS-var swap
|
|
4105
|
+
* auto-inverts). See `_shared/themeIdentity.test.ts`.
|
|
4106
|
+
*/ const datePickerVariants = tv({
|
|
4107
|
+
slots: {
|
|
4108
|
+
root: 'flex flex-col',
|
|
4109
|
+
label: 'block text-sm font-medium text-neutral-900 mb-1',
|
|
4110
|
+
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',
|
|
4112
|
+
value: 'flex-1 truncate text-left',
|
|
4113
|
+
placeholder: 'flex-1 truncate text-left text-neutral-400',
|
|
4114
|
+
icon: 'shrink-0 text-[1rem] text-neutral-500',
|
|
4115
|
+
helperText: 'mt-1 text-sm text-neutral-600',
|
|
4116
|
+
errorText: 'mt-1 text-sm text-danger-600'
|
|
4117
|
+
},
|
|
4118
|
+
variants: {
|
|
4119
|
+
layout: {
|
|
4120
|
+
stacked: {},
|
|
4121
|
+
inline: {
|
|
4122
|
+
root: 'flex-row items-start gap-3',
|
|
4123
|
+
label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
|
|
4124
|
+
}
|
|
4125
|
+
},
|
|
4126
|
+
error: {
|
|
4127
|
+
true: {
|
|
4128
|
+
trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
|
|
4129
|
+
}
|
|
4130
|
+
},
|
|
4131
|
+
fullWidth: {
|
|
4132
|
+
true: {
|
|
4133
|
+
root: 'w-full',
|
|
4134
|
+
trigger: 'w-full min-w-0'
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
},
|
|
4138
|
+
defaultVariants: {
|
|
4139
|
+
layout: 'stacked',
|
|
4140
|
+
error: false,
|
|
4141
|
+
fullWidth: false
|
|
4142
|
+
}
|
|
4143
|
+
});
|
|
4144
|
+
|
|
4145
|
+
// Inline 16×16 stroke calendar glyph — no icon dependency (tw convention).
|
|
4146
|
+
function CalendarIcon() {
|
|
4147
|
+
return jsxs("svg", {
|
|
4148
|
+
width: "1em",
|
|
4149
|
+
height: "1em",
|
|
4150
|
+
viewBox: "0 0 16 16",
|
|
4151
|
+
fill: "none",
|
|
4152
|
+
"aria-hidden": "true",
|
|
4153
|
+
children: [
|
|
4154
|
+
jsx("rect", {
|
|
4155
|
+
x: "2.5",
|
|
4156
|
+
y: "3.5",
|
|
4157
|
+
width: "11",
|
|
4158
|
+
height: "10",
|
|
4159
|
+
rx: "1.5",
|
|
4160
|
+
stroke: "currentColor",
|
|
4161
|
+
strokeWidth: "1.5"
|
|
4162
|
+
}),
|
|
4163
|
+
jsx("path", {
|
|
4164
|
+
d: "M2.5 6.5h11M5.5 2v3M10.5 2v3",
|
|
4165
|
+
stroke: "currentColor",
|
|
4166
|
+
strokeWidth: "1.5",
|
|
4167
|
+
strokeLinecap: "round"
|
|
4168
|
+
})
|
|
4169
|
+
]
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
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 === '') {
|
|
4178
|
+
return '';
|
|
4179
|
+
}
|
|
4180
|
+
const parts = parseISODate(iso);
|
|
4181
|
+
if (parts === null) {
|
|
4182
|
+
return iso;
|
|
4183
|
+
}
|
|
4184
|
+
return new Intl.DateTimeFormat(locale, {
|
|
4185
|
+
dateStyle: 'medium',
|
|
4186
|
+
timeZone: 'UTC'
|
|
4187
|
+
}).format(Date.UTC(parts.year, parts.month - 1, parts.day));
|
|
4188
|
+
}
|
|
4189
|
+
/**
|
|
4190
|
+
* Dashforge TW `<DatePicker>` — a form-bound single-date field.
|
|
4191
|
+
*
|
|
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.
|
|
4196
|
+
*
|
|
4197
|
+
* Storage contract: an ISO `YYYY-MM-DD` string, or `null` — shared with the
|
|
4198
|
+
* MUI `@dashforge/ui` `DatePicker`.
|
|
4199
|
+
*/ function DatePicker(props) {
|
|
4200
|
+
var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
|
|
4201
|
+
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
|
+
const bridge = useContext(DashFormContext);
|
|
4204
|
+
const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
|
|
4205
|
+
const fieldMeta = useDashFieldMeta(name);
|
|
4206
|
+
const accessState = useAccessState(access);
|
|
4207
|
+
const fieldId = useId();
|
|
4208
|
+
const helperId = `${fieldId}-help`;
|
|
4209
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
4210
|
+
const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
|
|
4211
|
+
// StrictMode-safe unregister-on-unmount.
|
|
4212
|
+
const unregisterRef = useRef({
|
|
4213
|
+
bridge,
|
|
4214
|
+
name
|
|
4215
|
+
});
|
|
4216
|
+
unregisterRef.current = {
|
|
4217
|
+
bridge,
|
|
4218
|
+
name
|
|
4219
|
+
};
|
|
4220
|
+
const isMountedRef = useRef(false);
|
|
4221
|
+
useEffect(()=>{
|
|
4222
|
+
isMountedRef.current = true;
|
|
4223
|
+
return ()=>{
|
|
4224
|
+
isMountedRef.current = false;
|
|
4225
|
+
const { bridge: cap, name: capName } = unregisterRef.current;
|
|
4226
|
+
queueMicrotask(()=>{
|
|
4227
|
+
var _cap_unregister;
|
|
4228
|
+
if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
|
|
4229
|
+
});
|
|
4230
|
+
};
|
|
4231
|
+
}, []);
|
|
4232
|
+
// ───── Derived ─────
|
|
4233
|
+
const effectiveDisabled = Boolean(disabled) || accessState.disabled;
|
|
4234
|
+
const effectiveReadOnly = accessState.readonly;
|
|
4235
|
+
const isInteractive = !effectiveDisabled && !effectiveReadOnly;
|
|
4236
|
+
const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
|
|
4237
|
+
let resolvedError = error;
|
|
4238
|
+
let resolvedHelperText = helperText;
|
|
4239
|
+
let registration = null;
|
|
4240
|
+
let resolvedValue;
|
|
4241
|
+
if (isFormMode && bridge) {
|
|
4242
|
+
registration = bridge.register(name, rules);
|
|
4243
|
+
const validation = resolveValidationState(name, bridge, error, helperText);
|
|
4244
|
+
resolvedError = validation.error;
|
|
4245
|
+
resolvedHelperText = validation.helperText;
|
|
4246
|
+
if (explicitValue !== undefined) {
|
|
4247
|
+
resolvedValue = explicitValue;
|
|
4248
|
+
} else {
|
|
4249
|
+
const bv = fieldMeta.value;
|
|
4250
|
+
resolvedValue = bv == null ? null : String(bv);
|
|
4251
|
+
}
|
|
4252
|
+
} else if (explicitValue !== undefined) {
|
|
4253
|
+
resolvedValue = explicitValue;
|
|
4254
|
+
} else {
|
|
4255
|
+
resolvedValue = internalValue;
|
|
4256
|
+
}
|
|
4257
|
+
// RHF threads its own callback ref through here in form mode.
|
|
4258
|
+
const registrationRefFn = registration == null ? void 0 : registration.ref;
|
|
4259
|
+
const triggerRef = useCallback((instance)=>{
|
|
4260
|
+
if (typeof registrationRefFn === 'function') {
|
|
4261
|
+
registrationRefFn(instance);
|
|
4262
|
+
}
|
|
4263
|
+
}, [
|
|
4264
|
+
registrationRefFn
|
|
4265
|
+
]);
|
|
4266
|
+
// ───── Render-time guards (after all hooks) ─────
|
|
4267
|
+
if (!isVisible) return null;
|
|
4268
|
+
if (!accessState.visible) return null;
|
|
4269
|
+
const v = datePickerVariants({
|
|
4270
|
+
layout,
|
|
4271
|
+
error: resolvedError,
|
|
4272
|
+
fullWidth
|
|
4273
|
+
});
|
|
4274
|
+
const resolvedLocale = locale != null ? locale : 'en-US';
|
|
4275
|
+
const displayValue = formatDisplayDate(resolvedValue, resolvedLocale);
|
|
4276
|
+
const commitValue = (next)=>{
|
|
4277
|
+
if (isFormMode && bridge) {
|
|
4278
|
+
var _registration_onChange;
|
|
4279
|
+
bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
|
|
4280
|
+
void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
|
|
4281
|
+
target: {
|
|
4282
|
+
name,
|
|
4283
|
+
value: next != null ? next : ''
|
|
4284
|
+
},
|
|
4285
|
+
type: 'change'
|
|
4286
|
+
}));
|
|
4287
|
+
} else if (explicitValue === undefined) {
|
|
4288
|
+
setInternalValue(next);
|
|
4289
|
+
}
|
|
4290
|
+
onChange == null ? void 0 : onChange(next);
|
|
4291
|
+
};
|
|
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
|
+
const markTouched = ()=>{
|
|
4296
|
+
if (isFormMode && bridge) {
|
|
4297
|
+
var _registration_onBlur;
|
|
4298
|
+
const committed = bridge.getValue(name);
|
|
4299
|
+
void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
|
|
4300
|
+
target: {
|
|
4301
|
+
name,
|
|
4302
|
+
value: committed == null ? '' : String(committed)
|
|
4303
|
+
},
|
|
4304
|
+
type: 'blur'
|
|
4305
|
+
}));
|
|
4306
|
+
}
|
|
4307
|
+
};
|
|
4308
|
+
const handleOpenChange = (next)=>{
|
|
4309
|
+
if (next && !isInteractive) {
|
|
4310
|
+
return;
|
|
4311
|
+
}
|
|
4312
|
+
setIsOpen(next);
|
|
4313
|
+
if (!next) {
|
|
4314
|
+
markTouched();
|
|
4315
|
+
}
|
|
4316
|
+
};
|
|
4317
|
+
const handleCalendarChange = (next)=>{
|
|
4318
|
+
commitValue(next);
|
|
4319
|
+
setIsOpen(false);
|
|
4320
|
+
markTouched();
|
|
4321
|
+
};
|
|
4322
|
+
return jsxs("div", {
|
|
4323
|
+
"data-testid": testId,
|
|
4324
|
+
className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
|
|
4325
|
+
children: [
|
|
4326
|
+
label && jsxs("label", {
|
|
4327
|
+
htmlFor: fieldId,
|
|
4328
|
+
className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
|
|
4329
|
+
children: [
|
|
4330
|
+
label,
|
|
4331
|
+
required && jsx("span", {
|
|
4332
|
+
"aria-hidden": "true",
|
|
4333
|
+
className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
|
|
4334
|
+
children: "*"
|
|
4335
|
+
})
|
|
4336
|
+
]
|
|
4337
|
+
}),
|
|
4338
|
+
jsx(Popover, {
|
|
4339
|
+
open: isOpen,
|
|
4340
|
+
onOpenChange: handleOpenChange,
|
|
4341
|
+
side: "bottom",
|
|
4342
|
+
align: "start",
|
|
4343
|
+
content: jsx(Calendar, {
|
|
4344
|
+
value: resolvedValue,
|
|
4345
|
+
onChange: handleCalendarChange,
|
|
4346
|
+
minDate: minDate,
|
|
4347
|
+
maxDate: maxDate,
|
|
4348
|
+
disabledDates: disabledDates,
|
|
4349
|
+
isDateDisabled: isDateDisabled,
|
|
4350
|
+
weekStartDay: weekStartDay,
|
|
4351
|
+
locale: locale,
|
|
4352
|
+
"aria-label": "Choose date",
|
|
4353
|
+
sx: "border-0 bg-transparent p-0"
|
|
4354
|
+
}),
|
|
4355
|
+
children: jsxs("button", {
|
|
4356
|
+
type: "button",
|
|
4357
|
+
id: fieldId,
|
|
4358
|
+
ref: triggerRef,
|
|
4359
|
+
disabled: effectiveDisabled,
|
|
4360
|
+
"aria-invalid": resolvedError ? true : undefined,
|
|
4361
|
+
"aria-describedby": resolvedHelperText ? helperId : undefined,
|
|
4362
|
+
className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
|
|
4363
|
+
children: [
|
|
4364
|
+
jsx("span", {
|
|
4365
|
+
className: displayValue ? v.value() : v.placeholder(),
|
|
4366
|
+
children: displayValue || placeholder || 'Select a date'
|
|
4367
|
+
}),
|
|
4368
|
+
jsx("span", {
|
|
4369
|
+
className: v.icon(),
|
|
4370
|
+
children: jsx(CalendarIcon, {})
|
|
4371
|
+
})
|
|
4372
|
+
]
|
|
4373
|
+
})
|
|
4374
|
+
}),
|
|
4375
|
+
resolvedHelperText && jsx("p", {
|
|
4376
|
+
id: helperId,
|
|
4377
|
+
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),
|
|
4378
|
+
children: resolvedHelperText
|
|
4379
|
+
})
|
|
4380
|
+
]
|
|
4381
|
+
});
|
|
4382
|
+
}
|
|
4383
|
+
|
|
3757
4384
|
/**
|
|
3758
4385
|
* Tailwind-variants recipe for `<Breadcrumbs>`.
|
|
3759
4386
|
*
|
|
@@ -7176,71 +7803,6 @@ const tooltipVariants = tv({
|
|
|
7176
7803
|
});
|
|
7177
7804
|
}
|
|
7178
7805
|
|
|
7179
|
-
const popoverVariants = tv({
|
|
7180
|
-
slots: {
|
|
7181
|
-
content: [
|
|
7182
|
-
// Neutral border auto-inverts via CSS-var swap.
|
|
7183
|
-
'z-50 rounded-md border border-neutral-200',
|
|
7184
|
-
// bg-white hardcoded (no auto-invert) so `dark:` is required.
|
|
7185
|
-
// Target `dark:bg-neutral-100` resolves to #171717 in dark mode
|
|
7186
|
-
// (one elevation tier above the page surface).
|
|
7187
|
-
'bg-white dark:bg-neutral-100 p-4 shadow-lg',
|
|
7188
|
-
// Text auto-inverts.
|
|
7189
|
-
'text-sm text-neutral-900',
|
|
7190
|
-
'focus:outline-none',
|
|
7191
|
-
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
7192
|
-
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
7193
|
-
'motion-reduce:transition-none motion-reduce:duration-0'
|
|
7194
|
-
],
|
|
7195
|
-
// Arrow follows content bg: `fill-white` in light + matching dark target.
|
|
7196
|
-
arrow: 'fill-white dark:fill-neutral-100'
|
|
7197
|
-
}
|
|
7198
|
-
});
|
|
7199
|
-
|
|
7200
|
-
/**
|
|
7201
|
-
* Dashforge TW `<Popover>` — clickable floating content panel.
|
|
7202
|
-
*
|
|
7203
|
-
* Built on `@radix-ui/react-popover`:
|
|
7204
|
-
* - Focus trap inside the popover (Tab cycles within).
|
|
7205
|
-
* - Outside-click + Escape dismiss.
|
|
7206
|
-
* - APG dialog-like semantics (`role="dialog"` on content).
|
|
7207
|
-
* - Portal-rendered.
|
|
7208
|
-
*
|
|
7209
|
-
* Use `<Popover>` for menus / pickers / settings panels with
|
|
7210
|
-
* interactive children. Use `<Tooltip>` for read-only hints.
|
|
7211
|
-
*/ function Popover(props) {
|
|
7212
|
-
var _slotProps_content, _slotProps_arrow;
|
|
7213
|
-
const { children, content, side = 'bottom', align = 'center', showArrow = false, open, onOpenChange, sideOffset = 8, width, slotProps } = props;
|
|
7214
|
-
const v = popoverVariants();
|
|
7215
|
-
return jsxs(RadixPopover.Root, {
|
|
7216
|
-
open: open,
|
|
7217
|
-
onOpenChange: onOpenChange,
|
|
7218
|
-
children: [
|
|
7219
|
-
jsx(RadixPopover.Trigger, {
|
|
7220
|
-
asChild: true,
|
|
7221
|
-
children: children
|
|
7222
|
-
}),
|
|
7223
|
-
jsx(RadixPopover.Portal, {
|
|
7224
|
-
children: jsxs(RadixPopover.Content, {
|
|
7225
|
-
side: side,
|
|
7226
|
-
align: align,
|
|
7227
|
-
sideOffset: sideOffset,
|
|
7228
|
-
style: width != null ? {
|
|
7229
|
-
width
|
|
7230
|
-
} : undefined,
|
|
7231
|
-
className: cn(v.content(), slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
|
|
7232
|
-
children: [
|
|
7233
|
-
content,
|
|
7234
|
-
showArrow && jsx(RadixPopover.Arrow, {
|
|
7235
|
-
className: cn(v.arrow(), slotProps == null ? void 0 : (_slotProps_arrow = slotProps.arrow) == null ? void 0 : _slotProps_arrow.className)
|
|
7236
|
-
})
|
|
7237
|
-
]
|
|
7238
|
-
})
|
|
7239
|
-
})
|
|
7240
|
-
]
|
|
7241
|
-
});
|
|
7242
|
-
}
|
|
7243
|
-
|
|
7244
7806
|
const accordionVariants = tv({
|
|
7245
7807
|
slots: {
|
|
7246
7808
|
root: 'w-full',
|
|
@@ -11419,4 +11981,4 @@ function SearchIcon(props) {
|
|
|
11419
11981
|
* Package version (synced with `package.json` at publish time).
|
|
11420
11982
|
*/ const VERSION = '0.7.0-beta';
|
|
11421
11983
|
|
|
11422
|
-
export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Checkbox, ConfirmDialogProvider, Container, DataGrid, 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, checkboxVariants, cn, confirmDialogVariants, containerVariants, dataGridVariants, 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 };
|
|
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 };
|