@zvk/ui 0.1.8 → 0.1.9
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 +7 -0
- package/README.md +1 -1
- package/dist/components/alert-dialog/alert-dialog.d.ts +1 -1
- package/dist/components/alert-dialog/alert-dialog.js +18 -10
- package/dist/components/combobox/combobox.js +3 -1
- package/dist/components/command/command-filter.js +1 -1
- package/dist/components/command/command.d.ts +5 -0
- package/dist/components/command/command.js +9 -2
- package/dist/components/command/index.d.ts +1 -1
- package/dist/components/context-menu/context-menu.js +1 -1
- package/dist/components/date-range-picker/date-range-picker.d.ts +27 -0
- package/dist/components/date-range-picker/date-range-picker.js +193 -0
- package/dist/components/date-range-picker/index.d.ts +2 -0
- package/dist/components/date-range-picker/index.js +2 -0
- package/dist/components/dialog/dialog.d.ts +1 -1
- package/dist/components/dialog/dialog.js +19 -14
- package/dist/components/dropdown-menu/dropdown-menu.d.ts +2 -2
- package/dist/components/dropdown-menu/dropdown-menu.js +24 -19
- package/dist/components/file-dropzone/file-dropzone.d.ts +25 -0
- package/dist/components/file-dropzone/file-dropzone.js +171 -0
- package/dist/components/file-dropzone/index.d.ts +2 -0
- package/dist/components/file-dropzone/index.js +2 -0
- package/dist/components/form/form.d.ts +16 -1
- package/dist/components/form/form.js +13 -2
- package/dist/components/form/index.d.ts +1 -1
- package/dist/components/hover-card/hover-card.d.ts +1 -1
- package/dist/components/hover-card/hover-card.js +12 -3
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.js +3 -0
- package/dist/components/kbd/index.d.ts +2 -0
- package/dist/components/kbd/index.js +1 -0
- package/dist/components/kbd/kbd.d.ts +15 -0
- package/dist/components/kbd/kbd.js +10 -0
- package/dist/components/menubar/menubar.js +1 -1
- package/dist/components/popover/popover.d.ts +1 -1
- package/dist/components/popover/popover.js +14 -24
- package/dist/components/select/select.d.ts +1 -1
- package/dist/components/select/select.js +88 -8
- package/dist/components/sheet/sheet.d.ts +1 -1
- package/dist/components/sheet/sheet.js +17 -12
- package/dist/components/table/index.d.ts +1 -1
- package/dist/components/table/table.d.ts +37 -0
- package/dist/components/table/table.js +30 -2
- package/dist/components/toast/index.d.ts +1 -1
- package/dist/components/toast/toast.d.ts +18 -0
- package/dist/components/toast/toast.js +60 -0
- package/dist/components/tooltip/tooltip.d.ts +1 -1
- package/dist/components/tooltip/tooltip.js +12 -3
- package/dist/internal/floating/transform-origin.d.ts +2 -0
- package/dist/internal/floating/transform-origin.js +22 -0
- package/dist/internal/presence/index.d.ts +2 -0
- package/dist/internal/presence/index.js +2 -0
- package/dist/internal/presence/use-presence.d.ts +18 -0
- package/dist/internal/presence/use-presence.js +119 -0
- package/dist/styles.css +1710 -224
- package/dist/tokens/token-types.d.ts +4 -0
- package/dist/tokens/tokens.d.ts +41 -5
- package/dist/tokens/tokens.js +45 -9
- package/package.json +135 -61
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.9](https://github.com/brandon-schabel/zvk/compare/v0.1.8...v0.1.9) (2026-06-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add theme presets and package improvements ([#19](https://github.com/brandon-schabel/zvk/issues/19)) ([867f955](https://github.com/brandon-schabel/zvk/commit/867f9556f7a60144cad4e747f2c032f2d5ede353))
|
|
9
|
+
|
|
3
10
|
## [0.1.8](https://github.com/brandon-schabel/zvk/compare/v0.1.7...v0.1.8) (2026-06-16)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ import "@zvk/ui/styles.css";
|
|
|
20
20
|
|
|
21
21
|
## Release Status
|
|
22
22
|
|
|
23
|
-
GitHub Actions runs `bun run preflight` before release publishing. Release Please opens
|
|
23
|
+
GitHub Actions runs `bun run preflight:ci` before release publishing. Release Please opens
|
|
24
24
|
reviewable version-bump PRs from conventional commits; merging a release PR creates the
|
|
25
25
|
GitHub release and publishes any package with a created release, including `@zvk/ui`, to npm
|
|
26
26
|
through trusted publishing in the protected `npm-publish` environment.
|
|
@@ -31,7 +31,7 @@ export interface AlertDialogCancelProps extends React.ButtonHTMLAttributes<HTMLB
|
|
|
31
31
|
}
|
|
32
32
|
declare function AlertDialogRoot({ children, className, defaultOpen, onOpenChange, open: openProp, ref, ...props }: AlertDialogProps): React.JSX.Element;
|
|
33
33
|
declare function AlertDialogTrigger({ asChild, className, disabled, onClick, ref, type, ...props }: AlertDialogTriggerProps): React.JSX.Element;
|
|
34
|
-
declare function AlertDialogContent({ children, className, ref, ...props }: AlertDialogContentProps): React.JSX.Element | null;
|
|
34
|
+
declare function AlertDialogContent({ children, className, onAnimationEnd, onTransitionEnd, ref, ...props }: AlertDialogContentProps): React.JSX.Element | null;
|
|
35
35
|
declare function AlertDialogTitle({ className, ref, ...props }: AlertDialogTitleProps): React.JSX.Element;
|
|
36
36
|
declare function AlertDialogDescription({ className, ref, ...props }: AlertDialogDescriptionProps): React.JSX.Element;
|
|
37
37
|
declare function AlertDialogFooter({ className, ref, ...props }: AlertDialogFooterProps): React.JSX.Element;
|
|
@@ -6,6 +6,7 @@ import { cn } from "../../utils/cn.js";
|
|
|
6
6
|
import { useControllableState } from "../../hooks/use-controllable-state.js";
|
|
7
7
|
import { DismissableLayer } from "../../internal/dismissable-layer/index.js";
|
|
8
8
|
import { FocusScope } from "../../internal/focus/index.js";
|
|
9
|
+
import { usePresence } from "../../internal/presence/index.js";
|
|
9
10
|
import { Portal } from "../../internal/portal/index.js";
|
|
10
11
|
import { lockScroll, unlockScroll } from "../../internal/scroll-lock/index.js";
|
|
11
12
|
import { Slot } from "../../internal/slot/index.js";
|
|
@@ -29,6 +30,7 @@ function composeRefs(...refs) {
|
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
}
|
|
33
|
+
const alertDialogExitDurationMs = 180;
|
|
32
34
|
function AlertDialogRoot({ children, className, defaultOpen = false, onOpenChange, open: openProp, ref, ...props }) {
|
|
33
35
|
const [open, setOpen] = useControllableState({
|
|
34
36
|
...(openProp !== undefined ? { value: openProp } : {}),
|
|
@@ -47,13 +49,6 @@ function AlertDialogRoot({ children, className, defaultOpen = false, onOpenChang
|
|
|
47
49
|
setDescribedBy(id);
|
|
48
50
|
return () => setDescribedBy((current) => (current === id ? undefined : current));
|
|
49
51
|
}, []);
|
|
50
|
-
React.useLayoutEffect(() => {
|
|
51
|
-
if (!open) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
lockScroll();
|
|
55
|
-
return () => unlockScroll();
|
|
56
|
-
}, [open]);
|
|
57
52
|
return (_jsx(AlertDialogContext.Provider, { value: {
|
|
58
53
|
close: () => setOpen(false),
|
|
59
54
|
contentId: `${instanceId}-content`,
|
|
@@ -80,12 +75,25 @@ function AlertDialogTrigger({ asChild = false, className, disabled, onClick, ref
|
|
|
80
75
|
}
|
|
81
76
|
return (_jsx("button", { ...props, ref: composeRefs(ref, triggerRef), type: type, disabled: disabled, "aria-controls": contentId, "aria-expanded": open ? "true" : "false", className: cn("zvk-ui-alert-dialog__trigger", className), "data-state": open ? "open" : "closed", onClick: composeEventHandlers(onClick, handleClick) }));
|
|
82
77
|
}
|
|
83
|
-
function AlertDialogContent({ children, className, ref, ...props }) {
|
|
78
|
+
function AlertDialogContent({ children, className, onAnimationEnd, onTransitionEnd, ref, ...props }) {
|
|
84
79
|
const { close, contentId, describedBy, labelledBy, open } = useAlertDialogContext("AlertDialog.Content");
|
|
85
|
-
|
|
80
|
+
const presence = usePresence({ open, exitDurationMs: alertDialogExitDurationMs });
|
|
81
|
+
const shouldLockScroll = presence.present && (!presence.inert || presence.motionState === "exiting");
|
|
82
|
+
React.useLayoutEffect(() => {
|
|
83
|
+
if (!shouldLockScroll) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
lockScroll();
|
|
87
|
+
return () => unlockScroll();
|
|
88
|
+
}, [shouldLockScroll]);
|
|
89
|
+
if (!presence.present) {
|
|
86
90
|
return null;
|
|
87
91
|
}
|
|
88
|
-
|
|
92
|
+
const content = (_jsx("div", { ...props, ref: ref, id: contentId, role: "alertdialog", "aria-hidden": presence.inert ? true : undefined, "aria-modal": open ? true : undefined, "aria-describedby": describedBy, "aria-labelledby": labelledBy, className: cn("zvk-ui-alert-dialog__content", className), "data-motion-state": presence.motionState, "data-state": presence.state, inert: presence.inert ? true : undefined, onAnimationEnd: composeEventHandlers(onAnimationEnd, presence.onExitComplete, { checkDefaultPrevented: false }), onTransitionEnd: composeEventHandlers(onTransitionEnd, presence.onExitComplete, { checkDefaultPrevented: false }), children: children }));
|
|
93
|
+
if (!open) {
|
|
94
|
+
return (_jsxs(Portal, { children: [_jsx("div", { "aria-hidden": "true", className: "zvk-ui-alert-dialog__overlay", "data-state": presence.state }), content] }));
|
|
95
|
+
}
|
|
96
|
+
return (_jsxs(Portal, { children: [_jsx("div", { "aria-hidden": "true", className: "zvk-ui-alert-dialog__overlay", "data-state": presence.state }), _jsx(DismissableLayer, { open: open, onDismiss: close, disableEscapeKeyDown: true, disableOutsidePointerDown: true, children: _jsx(FocusScope, { active: open, trapped: true, children: content }) })] }));
|
|
89
97
|
}
|
|
90
98
|
function AlertDialogTitle({ className, ref, ...props }) {
|
|
91
99
|
const { registerTitle, titleId } = useAlertDialogContext("AlertDialog.Title");
|
|
@@ -7,6 +7,7 @@ import { cn } from "../../utils/cn.js";
|
|
|
7
7
|
import { useControllableState } from "../../hooks/use-controllable-state.js";
|
|
8
8
|
import { DismissableLayer } from "../../internal/dismissable-layer/index.js";
|
|
9
9
|
import { useFloatingPosition } from "../../internal/floating/index.js";
|
|
10
|
+
import { placementParts } from "../../internal/floating/placement-aliases.js";
|
|
10
11
|
import { Portal } from "../../internal/portal/index.js";
|
|
11
12
|
function hasRenderableNode(value) {
|
|
12
13
|
return value !== undefined && value !== null && value !== false;
|
|
@@ -76,6 +77,7 @@ export function Combobox({ "aria-describedby": ariaDescribedBy, className, clear
|
|
|
76
77
|
const activeOption = filteredOptions.find((option) => option.value === activeValue && option.disabled !== true) ??
|
|
77
78
|
firstEnabled(filteredOptions);
|
|
78
79
|
const activeOptionIndex = activeOption === undefined ? -1 : filteredOptions.findIndex((option) => option.value === activeOption.value);
|
|
80
|
+
const resolvedPlacement = placementParts(floating.placement);
|
|
79
81
|
React.useEffect(() => {
|
|
80
82
|
if (!open) {
|
|
81
83
|
setQuery(selectedOption?.label ?? "");
|
|
@@ -145,7 +147,7 @@ export function Combobox({ "aria-describedby": ariaDescribedBy, className, clear
|
|
|
145
147
|
setOpen(false);
|
|
146
148
|
}
|
|
147
149
|
}), placeholder: placeholder, required: required, value: open ? query : selectedOption?.label ?? query }));
|
|
148
|
-
return (_jsxs("div", { className: "zvk-ui-combobox", "data-disabled": disabled ? "true" : undefined, "data-invalid": invalid ? "true" : undefined, children: [hasLabel ? _jsx("label", { className: "zvk-ui-combobox__label", htmlFor: inputId, children: label }) : null, _jsxs("div", { className: "zvk-ui-combobox__control", children: [input, clearable && selectedValue !== null ? (_jsx("button", { type: "button", className: "zvk-ui-combobox__clear", "aria-label": "Clear selection", disabled: disabled, onMouseDown: (event) => event.preventDefault(), onClick: clearSelection, children: "Clear" })) : null] }), hasDescription ? _jsx("div", { className: "zvk-ui-combobox__description", id: descriptionId, children: description }) : null, hasError ? _jsx("div", { className: "zvk-ui-combobox__error", id: errorId, children: error }) : null, open ? (_jsx(Portal, { ...(container === undefined ? {} : { container }), children: _jsx(DismissableLayer, { open: open, onDismiss: () => setOpen(false), children: _jsx("div", { id: listboxId, role: "listbox", className: "zvk-ui-combobox__popup", ref: floating.floatingRef, style: floating.floatingStyle, children: filteredOptions.map((option, index) => {
|
|
150
|
+
return (_jsxs("div", { className: "zvk-ui-combobox", "data-disabled": disabled ? "true" : undefined, "data-invalid": invalid ? "true" : undefined, children: [hasLabel ? _jsx("label", { className: "zvk-ui-combobox__label", htmlFor: inputId, children: label }) : null, _jsxs("div", { className: "zvk-ui-combobox__control", children: [input, clearable && selectedValue !== null ? (_jsx("button", { type: "button", className: "zvk-ui-combobox__clear", "aria-label": "Clear selection", disabled: disabled, onMouseDown: (event) => event.preventDefault(), onClick: clearSelection, children: "Clear" })) : null] }), hasDescription ? _jsx("div", { className: "zvk-ui-combobox__description", id: descriptionId, children: description }) : null, hasError ? _jsx("div", { className: "zvk-ui-combobox__error", id: errorId, children: error }) : null, open ? (_jsx(Portal, { ...(container === undefined ? {} : { container }), children: _jsx(DismissableLayer, { open: open, onDismiss: () => setOpen(false), children: _jsx("div", { id: listboxId, role: "listbox", className: "zvk-ui-combobox__popup", "data-align": resolvedPlacement.align, "data-side": resolvedPlacement.side, "data-state": "open", ref: floating.floatingRef, style: floating.floatingStyle, children: filteredOptions.map((option, index) => {
|
|
149
151
|
const isActive = activeOption?.value === option.value;
|
|
150
152
|
const isSelected = selectedValue === option.value;
|
|
151
153
|
return (_jsx("div", { id: optionId(inputId, index), role: "option", "aria-disabled": option.disabled ? "true" : undefined, "aria-selected": isSelected ? "true" : "false", className: "zvk-ui-combobox__option", "data-disabled": option.disabled ? "true" : undefined, "data-highlighted": isActive ? "true" : undefined, "data-selected": isSelected ? "true" : undefined, onMouseDown: (event) => event.preventDefault(), onClick: () => selectOption(option), children: option.label }, option.value));
|
|
@@ -18,6 +18,9 @@ export interface CommandListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
18
18
|
export interface CommandEmptyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
19
19
|
ref?: React.Ref<HTMLDivElement>;
|
|
20
20
|
}
|
|
21
|
+
export interface CommandLoadingProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
22
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
23
|
+
}
|
|
21
24
|
export interface CommandGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
22
25
|
heading?: React.ReactNode;
|
|
23
26
|
ref?: React.Ref<HTMLDivElement>;
|
|
@@ -43,6 +46,7 @@ declare function CommandRoot({ children, className, defaultValue, onItemSelect,
|
|
|
43
46
|
declare function CommandInput({ className, onKeyDown, onValueChange, placeholder, ref, value, ...props }: CommandInputProps): React.JSX.Element;
|
|
44
47
|
declare function CommandList({ className, ref, ...props }: CommandListProps): React.JSX.Element;
|
|
45
48
|
declare function CommandEmpty({ className, ref, ...props }: CommandEmptyProps): React.JSX.Element | null;
|
|
49
|
+
declare function CommandLoading({ className, ref, ...props }: CommandLoadingProps): React.JSX.Element;
|
|
46
50
|
declare function CommandGroup({ children, className, heading, ref, ...props }: CommandGroupProps): React.JSX.Element;
|
|
47
51
|
declare function CommandItem({ children, className, disabled, keywords, onClick, onSelect, ref, value, ...props }: CommandItemProps): React.JSX.Element | null;
|
|
48
52
|
declare function CommandSeparator({ className, ref, ...props }: CommandSeparatorProps): React.JSX.Element;
|
|
@@ -55,6 +59,7 @@ type CommandComponent = typeof CommandRoot & {
|
|
|
55
59
|
Input: typeof CommandInput;
|
|
56
60
|
Item: typeof CommandItem;
|
|
57
61
|
List: typeof CommandList;
|
|
62
|
+
Loading: typeof CommandLoading;
|
|
58
63
|
Separator: typeof CommandSeparator;
|
|
59
64
|
Shortcut: typeof CommandShortcut;
|
|
60
65
|
};
|
|
@@ -54,6 +54,10 @@ function CommandRoot({ children, className, defaultValue = "", onItemSelect, onV
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
const currentIndex = activeId === undefined ? -1 : visible.findIndex((item) => item.id === activeId);
|
|
57
|
+
if (currentIndex === -1) {
|
|
58
|
+
setActiveId(direction === "next" ? visible[0]?.id : visible.at(-1)?.id);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
57
61
|
const nextIndex = direction === "next" ? currentIndex + 1 : currentIndex - 1;
|
|
58
62
|
const wrapped = ((nextIndex % visible.length) + visible.length) % visible.length;
|
|
59
63
|
setActiveId(visible[wrapped]?.id);
|
|
@@ -89,8 +93,7 @@ function CommandRoot({ children, className, defaultValue = "", onItemSelect, onV
|
|
|
89
93
|
setActiveId(visible[0]?.id);
|
|
90
94
|
}
|
|
91
95
|
}, [activeId, query, version, visibleEnabledItems]);
|
|
92
|
-
const empty = collectionRef.current.items().
|
|
93
|
-
!collectionRef.current.items().some((item) => item.data.visible);
|
|
96
|
+
const empty = collectionRef.current.items().every((item) => !item.data.visible);
|
|
94
97
|
return (_jsx(CommandContext.Provider, { value: {
|
|
95
98
|
activeId,
|
|
96
99
|
empty,
|
|
@@ -152,6 +155,9 @@ function CommandEmpty({ className, ref, ...props }) {
|
|
|
152
155
|
}
|
|
153
156
|
return _jsx("div", { ...props, ref: ref, className: cn("zvk-ui-command__empty", className), "cmdk-empty": "" });
|
|
154
157
|
}
|
|
158
|
+
function CommandLoading({ className, ref, ...props }) {
|
|
159
|
+
return (_jsx("div", { ...props, ref: ref, role: "status", "aria-atomic": "true", "aria-live": "polite", className: cn("zvk-ui-command__loading", className), "cmdk-loading": "" }));
|
|
160
|
+
}
|
|
155
161
|
function CommandGroup({ children, className, heading, ref, ...props }) {
|
|
156
162
|
const headingId = React.useId();
|
|
157
163
|
return (_jsxs("div", { ...props, ref: ref, role: "group", "aria-labelledby": heading ? headingId : undefined, className: cn("zvk-ui-command__group", className), "cmdk-group": "", children: [heading ? _jsx("div", { id: headingId, className: "zvk-ui-command__group-heading", "cmdk-group-heading": "", children: heading }) : null, children] }));
|
|
@@ -206,6 +212,7 @@ export const Command = Object.assign(CommandRoot, {
|
|
|
206
212
|
Input: CommandInput,
|
|
207
213
|
Item: CommandItem,
|
|
208
214
|
List: CommandList,
|
|
215
|
+
Loading: CommandLoading,
|
|
209
216
|
Separator: CommandSeparator,
|
|
210
217
|
Shortcut: CommandShortcut
|
|
211
218
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Command, CommandDialog } from "./command.js";
|
|
2
|
-
export type { CommandDialogProps, CommandEmptyProps, CommandGroupProps, CommandInputProps, CommandItemProps, CommandListProps, CommandProps, CommandSeparatorProps, CommandShortcutProps } from "./command.js";
|
|
2
|
+
export type { CommandDialogProps, CommandEmptyProps, CommandGroupProps, CommandInputProps, CommandItemProps, CommandListProps, CommandLoadingProps, CommandProps, CommandSeparatorProps, CommandShortcutProps } from "./command.js";
|
|
@@ -181,7 +181,7 @@ function ContextMenuContent({ align, alignOffset = 0, children, className, colli
|
|
|
181
181
|
if (node !== null) {
|
|
182
182
|
schedulePositionUpdate();
|
|
183
183
|
}
|
|
184
|
-
}, id: contentId, role: "menu", className: cn("zvk-ui-context-menu__content", className), "data-align": placementParts(resolvedPlacement).align, "data-side": placementParts(resolvedPlacement).side, style: { ...style, ...contentStyle }, onKeyDown: composeEventHandlers(onKeyDown, (event) => {
|
|
184
|
+
}, id: contentId, role: "menu", className: cn("zvk-ui-context-menu__content", className), "data-align": placementParts(resolvedPlacement).align, "data-side": placementParts(resolvedPlacement).side, "data-state": "open", style: { ...style, ...contentStyle }, onKeyDown: composeEventHandlers(onKeyDown, (event) => {
|
|
185
185
|
const items = getItems();
|
|
186
186
|
const index = currentIndex(items);
|
|
187
187
|
if (event.key === "ArrowDown") {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface DateRange {
|
|
3
|
+
start: Date | null;
|
|
4
|
+
end: Date | null;
|
|
5
|
+
}
|
|
6
|
+
export interface DateRangePickerProps {
|
|
7
|
+
label?: React.ReactNode;
|
|
8
|
+
description?: React.ReactNode;
|
|
9
|
+
error?: React.ReactNode;
|
|
10
|
+
value?: DateRange;
|
|
11
|
+
defaultValue?: DateRange;
|
|
12
|
+
onValueChange?: (range: DateRange) => void;
|
|
13
|
+
startLabel?: string;
|
|
14
|
+
endLabel?: string;
|
|
15
|
+
startPlaceholder?: string;
|
|
16
|
+
endPlaceholder?: string;
|
|
17
|
+
formatDate?: (date: Date) => string;
|
|
18
|
+
parseDate?: (value: string) => Date | null;
|
|
19
|
+
parseErrorMessage?: (part: DateRangePart, value: string) => string;
|
|
20
|
+
minDate?: Date;
|
|
21
|
+
maxDate?: Date;
|
|
22
|
+
disabledDate?: (date: Date) => boolean;
|
|
23
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
24
|
+
}
|
|
25
|
+
type DateRangePart = "start" | "end";
|
|
26
|
+
export declare function DateRangePicker({ defaultValue, description, disabledDate, endLabel, endPlaceholder, error, formatDate, label, maxDate, minDate, onValueChange, parseDate, parseErrorMessage, ref, startLabel, startPlaceholder, value }: DateRangePickerProps): React.JSX.Element;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Calendar } from "../calendar/calendar.js";
|
|
5
|
+
import { Field } from "../field/field.js";
|
|
6
|
+
import { Input } from "../input/input.js";
|
|
7
|
+
import { Popover } from "../popover/popover.js";
|
|
8
|
+
const emptyRange = { start: null, end: null };
|
|
9
|
+
function hasRenderableNode(value) {
|
|
10
|
+
return value !== undefined && value !== null && value !== false;
|
|
11
|
+
}
|
|
12
|
+
function joinIds(...ids) {
|
|
13
|
+
const value = ids.filter(Boolean).join(" ");
|
|
14
|
+
return value.length > 0 ? value : undefined;
|
|
15
|
+
}
|
|
16
|
+
function defaultFormatDate(date) {
|
|
17
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
18
|
+
day: "numeric",
|
|
19
|
+
month: "short",
|
|
20
|
+
year: "numeric"
|
|
21
|
+
}).format(date);
|
|
22
|
+
}
|
|
23
|
+
function normalizeRange(range) {
|
|
24
|
+
if (!range) {
|
|
25
|
+
return emptyRange;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
start: range.start ?? null,
|
|
29
|
+
end: range.end ?? null
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function startOfDay(date) {
|
|
33
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
34
|
+
}
|
|
35
|
+
function isBeforeDay(left, right) {
|
|
36
|
+
return startOfDay(left).getTime() < startOfDay(right).getTime();
|
|
37
|
+
}
|
|
38
|
+
function isAfterDay(left, right) {
|
|
39
|
+
return startOfDay(left).getTime() > startOfDay(right).getTime();
|
|
40
|
+
}
|
|
41
|
+
function isUnavailableDate(date, { disabledDate, maxDate, minDate }) {
|
|
42
|
+
if (minDate && isBeforeDay(date, minDate)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (maxDate && isAfterDay(date, maxDate)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return disabledDate?.(startOfDay(date)) === true;
|
|
49
|
+
}
|
|
50
|
+
function isValidDate(value) {
|
|
51
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
52
|
+
}
|
|
53
|
+
function getNextCalendarRange(range, date) {
|
|
54
|
+
const selectedDate = startOfDay(date);
|
|
55
|
+
if (!range.start || range.end) {
|
|
56
|
+
return { start: selectedDate, end: null };
|
|
57
|
+
}
|
|
58
|
+
if (isBeforeDay(selectedDate, range.start)) {
|
|
59
|
+
return { start: selectedDate, end: startOfDay(range.start) };
|
|
60
|
+
}
|
|
61
|
+
return { start: startOfDay(range.start), end: selectedDate };
|
|
62
|
+
}
|
|
63
|
+
function getNextInputRange(range, part, date) {
|
|
64
|
+
const selectedDate = startOfDay(date);
|
|
65
|
+
if (part === "start") {
|
|
66
|
+
if (range.end && isAfterDay(selectedDate, range.end)) {
|
|
67
|
+
return { start: startOfDay(range.end), end: selectedDate };
|
|
68
|
+
}
|
|
69
|
+
return { start: selectedDate, end: range.end ? startOfDay(range.end) : null };
|
|
70
|
+
}
|
|
71
|
+
if (range.start && isBeforeDay(selectedDate, range.start)) {
|
|
72
|
+
return { start: selectedDate, end: startOfDay(range.start) };
|
|
73
|
+
}
|
|
74
|
+
return { start: range.start ? startOfDay(range.start) : null, end: selectedDate };
|
|
75
|
+
}
|
|
76
|
+
function clearRangePart(range, part) {
|
|
77
|
+
if (part === "start") {
|
|
78
|
+
return emptyRange;
|
|
79
|
+
}
|
|
80
|
+
return { start: range.start ? startOfDay(range.start) : null, end: null };
|
|
81
|
+
}
|
|
82
|
+
function getDefaultParseErrorMessage(part) {
|
|
83
|
+
return `Enter a valid ${part} date.`;
|
|
84
|
+
}
|
|
85
|
+
function getUnavailableDateMessage(part) {
|
|
86
|
+
return `Choose an available ${part} date.`;
|
|
87
|
+
}
|
|
88
|
+
function getInputValues(range, formatDate) {
|
|
89
|
+
return {
|
|
90
|
+
start: range.start ? formatDate(range.start) : "",
|
|
91
|
+
end: range.end ? formatDate(range.end) : ""
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function DateRangePicker({ defaultValue, description, disabledDate, endLabel = "End date", endPlaceholder = "End date", error, formatDate = defaultFormatDate, label, maxDate, minDate, onValueChange, parseDate, parseErrorMessage, ref, startLabel = "Start date", startPlaceholder = "Start date", value }) {
|
|
95
|
+
const generatedId = React.useId();
|
|
96
|
+
const labelId = hasRenderableNode(label) ? `${generatedId}-label` : undefined;
|
|
97
|
+
const descriptionId = hasRenderableNode(description) ? `${generatedId}-description` : undefined;
|
|
98
|
+
const externalErrorId = hasRenderableNode(error) ? `${generatedId}-error` : undefined;
|
|
99
|
+
const parseErrorId = `${generatedId}-parse-error`;
|
|
100
|
+
const startInputId = `${generatedId}-start`;
|
|
101
|
+
const endInputId = `${generatedId}-end`;
|
|
102
|
+
const hasLabel = hasRenderableNode(label);
|
|
103
|
+
const isControlled = value !== undefined;
|
|
104
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(() => normalizeRange(defaultValue));
|
|
105
|
+
const [openPart, setOpenPart] = React.useState(null);
|
|
106
|
+
const [parseError, setParseError] = React.useState(null);
|
|
107
|
+
const [draftValues, setDraftValues] = React.useState(() => getInputValues(normalizeRange(value ?? defaultValue), formatDate));
|
|
108
|
+
const currentRange = isControlled ? normalizeRange(value) : uncontrolledValue;
|
|
109
|
+
const displayedError = hasRenderableNode(error) ? error : parseError;
|
|
110
|
+
const hasError = hasRenderableNode(displayedError);
|
|
111
|
+
const errorId = hasRenderableNode(error) ? externalErrorId : parseError ? parseErrorId : undefined;
|
|
112
|
+
const describedBy = joinIds(descriptionId, errorId);
|
|
113
|
+
const fieldRefProps = ref !== undefined ? { ref } : {};
|
|
114
|
+
const calendarMonth = currentRange.start ?? currentRange.end ?? minDate ?? undefined;
|
|
115
|
+
const calendarProps = {
|
|
116
|
+
...(calendarMonth !== undefined ? { defaultMonth: calendarMonth } : {}),
|
|
117
|
+
...(disabledDate !== undefined ? { disabledDate } : {}),
|
|
118
|
+
...(maxDate !== undefined ? { maxDate } : {}),
|
|
119
|
+
...(minDate !== undefined ? { minDate } : {})
|
|
120
|
+
};
|
|
121
|
+
React.useEffect(() => {
|
|
122
|
+
setDraftValues(getInputValues(currentRange, formatDate));
|
|
123
|
+
}, [currentRange.start, currentRange.end, formatDate]);
|
|
124
|
+
const commitRange = (nextRange) => {
|
|
125
|
+
const normalizedRange = normalizeRange(nextRange);
|
|
126
|
+
setParseError(null);
|
|
127
|
+
if (!isControlled) {
|
|
128
|
+
setUncontrolledValue(normalizedRange);
|
|
129
|
+
}
|
|
130
|
+
onValueChange?.(normalizedRange);
|
|
131
|
+
};
|
|
132
|
+
const handleOpenChange = (part) => (nextOpen) => {
|
|
133
|
+
setOpenPart((currentPart) => {
|
|
134
|
+
if (nextOpen) {
|
|
135
|
+
return part;
|
|
136
|
+
}
|
|
137
|
+
return currentPart === part ? null : currentPart;
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
const handleCalendarSelect = (date) => {
|
|
141
|
+
if (!date) {
|
|
142
|
+
commitRange(emptyRange);
|
|
143
|
+
setOpenPart(null);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const nextRange = currentRange.start && currentRange.end && openPart === "end"
|
|
147
|
+
? getNextInputRange(currentRange, openPart, date)
|
|
148
|
+
: getNextCalendarRange(currentRange, date);
|
|
149
|
+
commitRange(nextRange);
|
|
150
|
+
if (nextRange.start && nextRange.end) {
|
|
151
|
+
setOpenPart(null);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const setDraftValue = (part, value) => {
|
|
155
|
+
setDraftValues((range) => ({ ...range, [part]: value }));
|
|
156
|
+
};
|
|
157
|
+
const commitInputValue = (part, value) => {
|
|
158
|
+
const trimmedValue = value.trim();
|
|
159
|
+
if (trimmedValue.length === 0) {
|
|
160
|
+
commitRange(clearRangePart(currentRange, part));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!parseDate) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const parsedDate = parseDate?.(trimmedValue) ?? null;
|
|
167
|
+
if (!isValidDate(parsedDate)) {
|
|
168
|
+
setParseError(parseErrorMessage?.(part, trimmedValue) ?? getDefaultParseErrorMessage(part));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const normalizedDate = startOfDay(parsedDate);
|
|
172
|
+
if (isUnavailableDate(normalizedDate, { disabledDate, maxDate, minDate })) {
|
|
173
|
+
setParseError(getUnavailableDateMessage(part));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
commitRange(getNextInputRange(currentRange, part, normalizedDate));
|
|
177
|
+
};
|
|
178
|
+
const renderDateInput = (part) => {
|
|
179
|
+
const inputId = part === "start" ? startInputId : endInputId;
|
|
180
|
+
const inputLabel = part === "start" ? startLabel : endLabel;
|
|
181
|
+
const placeholder = part === "start" ? startPlaceholder : endPlaceholder;
|
|
182
|
+
const calendarLabel = part === "start" ? "Open start date calendar" : "Open end date calendar";
|
|
183
|
+
const calendarDialogLabel = part === "start" ? "Start date calendar" : "End date calendar";
|
|
184
|
+
const inputValue = draftValues[part];
|
|
185
|
+
return (_jsxs("div", { className: "zvk-ui-date-range-picker__field", children: [_jsx("label", { className: "zvk-ui-date-range-picker__field-label", htmlFor: inputId, children: inputLabel }), _jsxs("div", { className: "zvk-ui-date-range-picker__input-row", children: [_jsx(Input, { "aria-describedby": describedBy, className: "zvk-ui-date-range-picker__input", id: inputId, invalid: hasError, placeholder: placeholder, readOnly: !parseDate, value: inputValue, onBlur: (event) => commitInputValue(part, event.currentTarget.value), onChange: (event) => setDraftValue(part, event.currentTarget.value), onKeyDown: (event) => {
|
|
186
|
+
if (event.key === "Enter") {
|
|
187
|
+
event.preventDefault();
|
|
188
|
+
commitInputValue(part, event.currentTarget.value);
|
|
189
|
+
}
|
|
190
|
+
} }), _jsxs(Popover, { open: openPart === part, onOpenChange: handleOpenChange(part), placement: "bottom-start", children: [_jsx(Popover.Trigger, { "aria-label": calendarLabel, className: "zvk-ui-date-range-picker__calendar-trigger", children: "Open" }), _jsx(Popover.Content, { "aria-label": calendarDialogLabel, className: "zvk-ui-date-range-picker__content", matchTriggerWidth: false, sideOffset: 4, children: _jsx(Calendar, { ...calendarProps, selected: currentRange.end ?? currentRange.start, onSelect: handleCalendarSelect }) })] })] })] }));
|
|
191
|
+
};
|
|
192
|
+
return (_jsxs(Field, { className: "zvk-ui-date-range-picker", invalid: hasError, ...fieldRefProps, children: [hasLabel ? _jsx(Field.Label, { id: labelId, children: label }) : null, _jsxs("div", { "aria-describedby": describedBy, "aria-labelledby": labelId, className: "zvk-ui-date-range-picker__group", role: hasLabel ? "group" : undefined, children: [_jsxs("div", { className: "zvk-ui-date-range-picker__controls", children: [renderDateInput("start"), renderDateInput("end")] }), currentRange.start || currentRange.end ? (_jsx("button", { className: "zvk-ui-date-range-picker__clear", type: "button", onClick: () => commitRange(emptyRange), children: "Clear date range" })) : null] }), hasRenderableNode(description) ? _jsx(Field.Description, { id: descriptionId, children: description }) : null, hasError ? _jsx(Field.Error, { id: errorId, children: displayedError }) : null] }));
|
|
193
|
+
}
|
|
@@ -43,7 +43,7 @@ interface DialogPortalProps {
|
|
|
43
43
|
declare function DialogRoot({ children, className, container, defaultOpen, disableEscapeKeyDown, disableOutsidePointerDown, onOpenChange, open: openProp, ref, ...props }: DialogProps): React.JSX.Element;
|
|
44
44
|
declare function DialogPortal({ container, children }: DialogPortalProps): React.JSX.Element;
|
|
45
45
|
declare function DialogOverlay({ className, ref, ...props }: DialogOverlayProps): React.JSX.Element;
|
|
46
|
-
declare function DialogContent({ className, children, forceMount, ref, ...props }: DialogContentProps): React.JSX.Element | null;
|
|
46
|
+
declare function DialogContent({ className, children, forceMount, onAnimationEnd, onTransitionEnd, ref, ...props }: DialogContentProps): React.JSX.Element | null;
|
|
47
47
|
declare function DialogHeader({ className, ref, ...props }: DialogHeaderProps): React.JSX.Element;
|
|
48
48
|
declare function DialogTitle({ className, ref, ...props }: DialogTitleProps): React.JSX.Element;
|
|
49
49
|
declare function DialogDescription({ className, ref, ...props }: DialogDescriptionProps): React.JSX.Element;
|
|
@@ -6,6 +6,7 @@ import { cn } from "../../utils/cn.js";
|
|
|
6
6
|
import { useControllableState } from "../../hooks/use-controllable-state.js";
|
|
7
7
|
import { DismissableLayer } from "../../internal/dismissable-layer/dismissable-layer.js";
|
|
8
8
|
import { FocusScope } from "../../internal/focus/focus-scope.js";
|
|
9
|
+
import { usePresence } from "../../internal/presence/index.js";
|
|
9
10
|
import { lockScroll, unlockScroll } from "../../internal/scroll-lock/scroll-lock.js";
|
|
10
11
|
import { Portal } from "../../internal/portal/index.js";
|
|
11
12
|
import { Slot } from "../../internal/slot/index.js";
|
|
@@ -29,6 +30,7 @@ function composeRefs(...refs) {
|
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
}
|
|
33
|
+
const dialogExitDurationMs = 180;
|
|
32
34
|
function DialogRoot({ children, className, container, defaultOpen = false, disableEscapeKeyDown = false, disableOutsidePointerDown = false, onOpenChange, open: openProp, ref, ...props }) {
|
|
33
35
|
const [open, setOpen] = useControllableState({
|
|
34
36
|
...(openProp !== undefined ? { value: openProp } : {}),
|
|
@@ -51,15 +53,6 @@ function DialogRoot({ children, className, container, defaultOpen = false, disab
|
|
|
51
53
|
setDescribedBy((current) => (current === id ? undefined : current));
|
|
52
54
|
};
|
|
53
55
|
}, []);
|
|
54
|
-
React.useLayoutEffect(() => {
|
|
55
|
-
if (!open) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
lockScroll();
|
|
59
|
-
return () => {
|
|
60
|
-
unlockScroll();
|
|
61
|
-
};
|
|
62
|
-
}, [open]);
|
|
63
56
|
return (_jsx(DialogContext.Provider, { value: {
|
|
64
57
|
close: () => {
|
|
65
58
|
setOpen(false);
|
|
@@ -88,16 +81,28 @@ function DialogPortal({ container, children }) {
|
|
|
88
81
|
function DialogOverlay({ className, ref, ...props }) {
|
|
89
82
|
return _jsx("div", { ...props, ref: ref, className: cn("zvk-ui-dialog__overlay", className) });
|
|
90
83
|
}
|
|
91
|
-
function DialogContent({ className, children, forceMount = false, ref, ...props }) {
|
|
84
|
+
function DialogContent({ className, children, forceMount = false, onAnimationEnd, onTransitionEnd, ref, ...props }) {
|
|
92
85
|
const { close, container, contentId, describedBy, disableEscapeKeyDown, disableOutsidePointerDown, labelledBy, open } = useDialogContext("Dialog.Content");
|
|
93
|
-
|
|
86
|
+
const presence = usePresence({ open, forceMount, exitDurationMs: dialogExitDurationMs });
|
|
87
|
+
const hidden = !open && forceMount && presence.motionState === "idle";
|
|
88
|
+
const shouldLockScroll = presence.present && (!presence.inert || presence.motionState === "exiting");
|
|
89
|
+
React.useLayoutEffect(() => {
|
|
90
|
+
if (!shouldLockScroll) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
lockScroll();
|
|
94
|
+
return () => {
|
|
95
|
+
unlockScroll();
|
|
96
|
+
};
|
|
97
|
+
}, [shouldLockScroll]);
|
|
98
|
+
if (!presence.present) {
|
|
94
99
|
return null;
|
|
95
100
|
}
|
|
96
|
-
const content = (_jsx("div", { ...props, ref: ref, id: contentId, role: "dialog", "aria-modal": true, "aria-labelledby": labelledBy, "aria-describedby": describedBy, className: cn("zvk-ui-dialog__content", className), "data-state":
|
|
101
|
+
const content = (_jsx("div", { ...props, ref: ref, id: contentId, role: "dialog", "aria-hidden": presence.inert ? true : undefined, "aria-modal": open ? true : undefined, "aria-labelledby": labelledBy, "aria-describedby": describedBy, className: cn("zvk-ui-dialog__content", className), "data-motion-state": presence.motionState, "data-state": presence.state, hidden: hidden ? true : undefined, inert: presence.inert ? true : undefined, onAnimationEnd: composeEventHandlers(onAnimationEnd, presence.onExitComplete, { checkDefaultPrevented: false }), onTransitionEnd: composeEventHandlers(onTransitionEnd, presence.onExitComplete, { checkDefaultPrevented: false }), children: children }));
|
|
97
102
|
if (!open) {
|
|
98
|
-
return (_jsxs(DialogPortal, { container: container, children: [_jsx(DialogOverlay, { "aria-hidden": true, hidden: true }), content] }));
|
|
103
|
+
return (_jsxs(DialogPortal, { container: container, children: [_jsx(DialogOverlay, { "aria-hidden": true, "data-state": presence.state, hidden: hidden ? true : undefined }), content] }));
|
|
99
104
|
}
|
|
100
|
-
return (_jsxs(DialogPortal, { container: container, children: [_jsx(DialogOverlay, { "aria-hidden": true }), _jsx(DismissableLayer, { open: open, disableEscapeKeyDown: disableEscapeKeyDown, disableOutsidePointerDown: disableOutsidePointerDown, onDismiss: close, children: _jsx(FocusScope, { trapped: true, active: open, children: content }) })] }));
|
|
105
|
+
return (_jsxs(DialogPortal, { container: container, children: [_jsx(DialogOverlay, { "aria-hidden": true, "data-state": presence.state }), _jsx(DismissableLayer, { open: open, disableEscapeKeyDown: disableEscapeKeyDown, disableOutsidePointerDown: disableOutsidePointerDown, onDismiss: close, children: _jsx(FocusScope, { trapped: true, active: open, children: content }) })] }));
|
|
101
106
|
}
|
|
102
107
|
function DialogHeader({ className, ref, ...props }) {
|
|
103
108
|
return _jsx("div", { ...props, ref: ref, className: cn("zvk-ui-dialog__header", className) });
|
|
@@ -57,8 +57,8 @@ export interface DropdownMenuSubProps extends React.HTMLAttributes<HTMLDivElemen
|
|
|
57
57
|
ref?: React.Ref<HTMLDivElement>;
|
|
58
58
|
}
|
|
59
59
|
declare function DropdownMenuRoot({ children, className, container, defaultOpen, onOpenChange, open: openProp, placement, sideOffset, collisionPadding, matchTriggerWidth, ...props }: DropdownMenuProps): React.JSX.Element;
|
|
60
|
-
declare function DropdownMenuTrigger({ asChild, className, disabled, onClick, ref, type, ...props }: DropdownMenuTriggerProps): React.JSX.Element;
|
|
61
|
-
declare function DropdownMenuContent({ align, alignOffset, children, className, forceMount, side, sideOffset, collisionPadding, matchTriggerWidth, ref, onKeyDown, ...props }: DropdownMenuContentProps): React.JSX.Element | null;
|
|
60
|
+
declare function DropdownMenuTrigger({ asChild, children, className, disabled, onClick, ref, type, ...props }: DropdownMenuTriggerProps): React.JSX.Element;
|
|
61
|
+
declare function DropdownMenuContent({ align, alignOffset, children, className, forceMount, side, sideOffset, collisionPadding, matchTriggerWidth, ref, onAnimationEnd, onKeyDown, ...props }: DropdownMenuContentProps): React.JSX.Element | null;
|
|
62
62
|
declare function DropdownMenuItem({ asChild, children, className, disabled, onClick, onSelect, onKeyDown, ref, ...props }: DropdownMenuItemProps): React.JSX.Element;
|
|
63
63
|
declare function DropdownMenuSeparator({ className, ref, ...props }: DropdownMenuSeparatorProps): React.JSX.Element;
|
|
64
64
|
declare function DropdownMenuLabel({ className, ref, ...props }: DropdownMenuLabelProps): React.JSX.Element;
|