@neasg/design-system 0.4.8 → 0.4.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.
Files changed (115) hide show
  1. package/README.md +4 -4
  2. package/dist/alert.d.ts +21 -2
  3. package/dist/alert.js +10 -3
  4. package/dist/avatar.d.ts +5 -2
  5. package/dist/avatar.js +76 -8
  6. package/dist/back-button.d.ts +2 -0
  7. package/dist/back-button.js +1 -1
  8. package/dist/badge.d.ts +3 -2
  9. package/dist/badge.js +9 -3
  10. package/dist/breadcrumb.js +2 -1
  11. package/dist/button.d.ts +1 -1
  12. package/dist/button.js +12 -9
  13. package/dist/calendar.js +10 -9
  14. package/dist/card.d.ts +29 -1
  15. package/dist/card.js +61 -7
  16. package/dist/checkbox.d.ts +2 -0
  17. package/dist/checkbox.js +2 -2
  18. package/dist/close-button.d.ts +6 -0
  19. package/dist/close-button.js +8 -0
  20. package/dist/collapsible.d.ts +7 -1
  21. package/dist/collapsible.js +2 -2
  22. package/dist/command-search.d.ts +16 -1
  23. package/dist/command-search.js +42 -16
  24. package/dist/command.js +3 -3
  25. package/dist/copy-button.d.ts +3 -1
  26. package/dist/copy-button.js +4 -2
  27. package/dist/count-badge.d.ts +3 -1
  28. package/dist/count-badge.js +10 -5
  29. package/dist/date-input.d.ts +18 -3
  30. package/dist/date-input.js +94 -21
  31. package/dist/dialog-primitive.js +4 -3
  32. package/dist/draggable-tabs.d.ts +7 -0
  33. package/dist/draggable-tabs.js +6 -5
  34. package/dist/drawer.js +2 -2
  35. package/dist/dropdown-button.d.ts +18 -0
  36. package/dist/dropdown-button.js +31 -0
  37. package/dist/edit-button.d.ts +10 -0
  38. package/dist/edit-button.js +7 -0
  39. package/dist/editable-table.d.ts +3 -1
  40. package/dist/editable-table.js +25 -12
  41. package/dist/field.d.ts +2 -2
  42. package/dist/field.js +4 -3
  43. package/dist/file-upload-status-toast.d.ts +28 -0
  44. package/dist/file-upload-status-toast.js +136 -0
  45. package/dist/file-upload.js +8 -7
  46. package/dist/filter-popover.d.ts +37 -0
  47. package/dist/filter-popover.js +73 -0
  48. package/dist/filter-toggle.js +2 -2
  49. package/dist/index.d.ts +45 -14
  50. package/dist/index.js +19 -4
  51. package/dist/input-control.d.ts +3 -2
  52. package/dist/input-control.js +3 -2
  53. package/dist/label-value-grid.d.ts +4 -2
  54. package/dist/label-value-grid.js +7 -6
  55. package/dist/label-value.js +3 -1
  56. package/dist/layout-right-panel-edge.d.ts +6 -0
  57. package/dist/layout-right-panel-edge.js +6 -0
  58. package/dist/layout.d.ts +77 -5
  59. package/dist/layout.js +439 -35
  60. package/dist/lib/date-utils.d.ts +10 -2
  61. package/dist/lib/date-utils.js +91 -11
  62. package/dist/lib/utils.js +8 -1
  63. package/dist/link.js +2 -2
  64. package/dist/message-item.d.ts +62 -0
  65. package/dist/message-item.js +148 -0
  66. package/dist/multi-select.js +31 -5
  67. package/dist/notification.d.ts +82 -0
  68. package/dist/notification.js +121 -0
  69. package/dist/otp-input.js +1 -1
  70. package/dist/page-header.js +1 -1
  71. package/dist/page-section.d.ts +2 -1
  72. package/dist/page-section.js +4 -3
  73. package/dist/pagination.js +2 -2
  74. package/dist/popover-menu.d.ts +23 -1
  75. package/dist/popover-menu.js +75 -5
  76. package/dist/popover-primitive.js +1 -1
  77. package/dist/progress.d.ts +5 -2
  78. package/dist/progress.js +6 -3
  79. package/dist/radio-group.d.ts +21 -0
  80. package/dist/radio-group.js +41 -0
  81. package/dist/rich-text-editor.d.ts +56 -0
  82. package/dist/rich-text-editor.js +394 -0
  83. package/dist/routing-timeline.d.ts +22 -0
  84. package/dist/routing-timeline.js +58 -0
  85. package/dist/scroll-hint.d.ts +8 -0
  86. package/dist/scroll-hint.js +7 -0
  87. package/dist/search-input-shell.js +1 -1
  88. package/dist/search-input.js +2 -2
  89. package/dist/section-nav.js +4 -4
  90. package/dist/select-primitive.js +3 -3
  91. package/dist/select.js +1 -1
  92. package/dist/skeleton.js +1 -1
  93. package/dist/styles.css +235 -57
  94. package/dist/table-column-visibility.d.ts +21 -0
  95. package/dist/table-column-visibility.js +74 -0
  96. package/dist/table-toolbar.js +1 -1
  97. package/dist/table.d.ts +12 -1
  98. package/dist/table.js +67 -33
  99. package/dist/tabs.d.ts +14 -3
  100. package/dist/tabs.js +60 -6
  101. package/dist/tailwind-preset.js +46 -0
  102. package/dist/task-mode-shell.d.ts +24 -0
  103. package/dist/task-mode-shell.js +110 -0
  104. package/dist/textarea.js +2 -1
  105. package/dist/theme-switcher.d.ts +28 -0
  106. package/dist/theme-switcher.js +27 -0
  107. package/dist/theme.d.ts +167 -0
  108. package/dist/theme.js +112 -0
  109. package/dist/toaster.js +5 -4
  110. package/dist/tooltip.js +1 -1
  111. package/dist/typography.d.ts +1 -1
  112. package/dist/typography.js +5 -5
  113. package/dist/workspace-header.d.ts +13 -0
  114. package/dist/workspace-header.js +11 -0
  115. package/package.json +15 -1
package/dist/checkbox.js CHANGED
@@ -8,7 +8,7 @@ const sizeClasses = {
8
8
  md: "size-4",
9
9
  lg: "size-5",
10
10
  };
11
- const Checkbox = React.forwardRef(({ className, indeterminate = false, size = "md", checked, disabled, label, description, id, ...props }, ref) => {
11
+ const Checkbox = React.forwardRef(({ className, indeterminate = false, size = "md", checked, disabled, label, description, labelAlign = "center", id, ...props }, ref) => {
12
12
  const innerRef = React.useRef(null);
13
13
  const generatedId = React.useId();
14
14
  const inputId = id !== null && id !== void 0 ? id : `checkbox-${generatedId}`;
@@ -23,7 +23,7 @@ const Checkbox = React.forwardRef(({ className, indeterminate = false, size = "m
23
23
  : "border-input", disabled && "cursor-not-allowed opacity-50", !label && className), children: [_jsx("input", { ref: innerRef, type: "checkbox", id: inputId, checked: checked, disabled: disabled, className: "absolute inset-0 h-full w-full cursor-pointer appearance-none opacity-0 disabled:cursor-not-allowed", ...props }), indeterminate ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", className: "pointer-events-none absolute inset-0 m-auto h-3 w-3", "aria-hidden": "true", children: _jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) })) : checked ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", className: "pointer-events-none absolute inset-0 m-auto h-3 w-3", "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })) : null] }));
24
24
  if (!label)
25
25
  return box;
26
- return (_jsxs("label", { htmlFor: inputId, className: cn("inline-flex cursor-pointer items-center gap-2 text-sm", disabled && "cursor-not-allowed opacity-50", className), children: [box, _jsxs("div", { className: "select-none", children: [_jsx(Typography, { as: "span", variant: "bodySm", children: label }), description ? (_jsx(Typography, { as: "p", variant: "caption", className: "mt-1", children: description })) : null] })] }));
26
+ return (_jsxs("label", { htmlFor: inputId, className: cn("inline-flex cursor-pointer items-center gap-2 text-sm", labelAlign === "start" && "items-start", disabled && "cursor-not-allowed opacity-50", className), children: [box, _jsxs("div", { className: "select-none", children: [_jsx(Typography, { as: "span", variant: "bodySm", children: label }), description ? (_jsx(Typography, { as: "p", variant: "caption", className: "mt-1", children: description })) : null] })] }));
27
27
  });
28
28
  Checkbox.displayName = "Checkbox";
29
29
  export { Checkbox };
@@ -0,0 +1,6 @@
1
+ import { type ButtonProps } from "./button";
2
+ export interface CloseButtonProps extends Omit<ButtonProps, "children"> {
3
+ iconSize?: number;
4
+ }
5
+ declare function CloseButton({ className, iconSize, variant, size, "aria-label": ariaLabel, ...props }: CloseButtonProps): import("react/jsx-runtime").JSX.Element;
6
+ export { CloseButton };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { XIcon } from "./animated-icons/x";
3
+ import { Button } from "./button";
4
+ import { cn } from "./lib/utils";
5
+ function CloseButton({ className, iconSize = 16, variant = "ghost", size = "compactIcon", "aria-label": ariaLabel = "Close", ...props }) {
6
+ return (_jsx(Button, { type: "button", variant: variant, size: size, "aria-label": ariaLabel, className: cn("text-muted-foreground hover:text-foreground [&_svg]:h-4 [&_svg]:w-4", className), ...props, children: _jsx(XIcon, { size: iconSize }) }));
7
+ }
8
+ export { CloseButton };
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import { type TypographyProps } from "./typography";
2
3
  export interface CollapsibleProps {
3
4
  id?: string;
4
5
  title: React.ReactNode;
@@ -10,10 +11,15 @@ export interface CollapsibleProps {
10
11
  headerAction?: React.ReactNode;
11
12
  /** Element rendered inline after the title (e.g. a tooltip icon). */
12
13
  titleAdornment?: React.ReactNode;
14
+ showChevron?: boolean;
13
15
  className?: string;
14
16
  headerClassName?: string;
17
+ triggerClassName?: string;
18
+ titleClassName?: string;
19
+ titleVariant?: TypographyProps["variant"];
20
+ chevronClassName?: string;
15
21
  contentClassName?: string;
16
22
  }
17
- declare function Collapsible({ id, title, children, defaultOpen, open: openProp, onOpenChange, headerAction, titleAdornment, className, headerClassName, contentClassName, }: CollapsibleProps): import("react/jsx-runtime").JSX.Element;
23
+ declare function Collapsible({ id, title, children, defaultOpen, open: openProp, onOpenChange, headerAction, titleAdornment, showChevron, className, headerClassName, triggerClassName, titleClassName, titleVariant, chevronClassName, contentClassName, }: CollapsibleProps): import("react/jsx-runtime").JSX.Element;
18
24
  declare const CollapsibleSection: typeof Collapsible;
19
25
  export { Collapsible, CollapsibleSection };
@@ -5,7 +5,7 @@ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
5
5
  import { ChevronUp } from "lucide-react";
6
6
  import { cn } from "./lib/utils";
7
7
  import { Typography } from "./typography";
8
- function Collapsible({ id, title, children, defaultOpen = true, open: openProp, onOpenChange, headerAction, titleAdornment, className, headerClassName, contentClassName, }) {
8
+ function Collapsible({ id, title, children, defaultOpen = true, open: openProp, onOpenChange, headerAction, titleAdornment, showChevron = true, className, headerClassName, triggerClassName, titleClassName, titleVariant = "subheading", chevronClassName, contentClassName, }) {
9
9
  const [uncontrolled, setUncontrolled] = React.useState(defaultOpen);
10
10
  const isControlled = openProp !== undefined;
11
11
  const open = isControlled ? openProp : uncontrolled;
@@ -14,7 +14,7 @@ function Collapsible({ id, title, children, defaultOpen = true, open: openProp,
14
14
  setUncontrolled(next);
15
15
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(next);
16
16
  };
17
- return (_jsxs(CollapsiblePrimitive.Root, { id: id, open: open, onOpenChange: setOpen, className: cn("scroll-mt-24 border-b border-border", className), children: [_jsxs("div", { className: cn("flex items-center gap-2 py-3", headerClassName), children: [_jsx(CollapsiblePrimitive.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex flex-1 cursor-pointer items-center gap-2 text-left text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm", children: [_jsx(Typography, { as: "h3", variant: "subheading", children: title }), titleAdornment] }) }), headerAction, _jsx(CollapsiblePrimitive.Trigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": open ? "Collapse section" : "Expand section", className: "group ml-1 cursor-pointer text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm", children: _jsx(ChevronUp, { "aria-hidden": "true", className: "h-4 w-4 transition-transform duration-150 group-data-[state=closed]:rotate-180" }) }) })] }), _jsx(CollapsiblePrimitive.Content, { "data-slot": "collapsible-content", className: "overflow-hidden", children: _jsx("div", { className: cn("pb-6 pt-4", contentClassName), children: children }) })] }));
17
+ return (_jsxs(CollapsiblePrimitive.Root, { id: id, open: open, onOpenChange: setOpen, className: cn("scroll-mt-24 border-b border-border", className), children: [_jsxs("div", { "data-slot": "collapsible-header", className: cn("flex items-center gap-1.5 py-2", headerClassName), children: [_jsx(CollapsiblePrimitive.Trigger, { asChild: true, children: _jsxs("button", { type: "button", "data-slot": "collapsible-trigger", className: cn("flex flex-1 cursor-pointer items-center gap-2 rounded-sm text-left text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", triggerClassName), children: [_jsx(Typography, { as: "h3", variant: titleVariant, className: titleClassName, children: title }), titleAdornment] }) }), headerAction, showChevron ? (_jsx(CollapsiblePrimitive.Trigger, { asChild: true, children: _jsx("button", { type: "button", "data-slot": "collapsible-chevron-trigger", "aria-label": open ? "Collapse section" : "Expand section", className: "group ml-1 cursor-pointer rounded-sm text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: _jsx(ChevronUp, { "aria-hidden": "true", className: cn("h-4 w-4 transition-transform duration-150 group-data-[state=closed]:rotate-180", chevronClassName) }) }) })) : null] }), _jsx(CollapsiblePrimitive.Content, { "data-slot": "collapsible-content", className: "overflow-hidden", children: _jsx("div", { className: cn("pb-6 pt-4", contentClassName), children: children }) })] }));
18
18
  }
19
19
  const CollapsibleSection = Collapsible;
20
20
  export { Collapsible, CollapsibleSection };
@@ -20,6 +20,7 @@ export interface CommandSearchSection {
20
20
  heading: string;
21
21
  items: CommandSearchItem[];
22
22
  }
23
+ export type CommandSearchShowAllResultsLabel = React.ReactNode | ((query: string, hasResults: boolean) => React.ReactNode);
23
24
  export interface CommandSearchProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
24
25
  value: string;
25
26
  onValueChange: (value: string) => void;
@@ -29,6 +30,7 @@ export interface CommandSearchProps extends Omit<React.HTMLAttributes<HTMLDivEle
29
30
  open?: boolean;
30
31
  defaultOpen?: boolean;
31
32
  onOpenChange?: (open: boolean) => void;
33
+ triggerVariant?: "default" | "sidebar";
32
34
  triggerPlaceholder?: string;
33
35
  inputPlaceholder?: string;
34
36
  /** The key that opens the dialog when pressed with Cmd/Ctrl (e.g. "k", "j"). */
@@ -37,8 +39,21 @@ export interface CommandSearchProps extends Omit<React.HTMLAttributes<HTMLDivEle
37
39
  shortcutLabel?: string;
38
40
  allFilter?: CommandSearchFilter;
39
41
  filters?: CommandSearchFilter[];
42
+ /** Shows command-list skeleton rows while results are being fetched. */
43
+ loading?: boolean;
44
+ /** Number of skeleton rows shown while `loading` is true. */
45
+ loadingRows?: number;
46
+ /**
47
+ * Renders a footer action for routing to a full search results page.
48
+ * The action is shown only when the query is not empty and results are not loading.
49
+ */
50
+ onShowAllResults?: (query: string) => void;
51
+ /** Custom label for the full-results footer action. */
52
+ showAllResultsLabel?: CommandSearchShowAllResultsLabel;
53
+ /** Class names applied to the button that opens the command dialog. */
54
+ triggerClassName?: string;
40
55
  dialogClassName?: string;
41
56
  resultsClassName?: string;
42
57
  }
43
- declare function CommandSearch({ value, onValueChange, sections, children, className, isCollapsed, open, defaultOpen, onOpenChange, triggerPlaceholder, inputPlaceholder, shortcutKey, shortcutLabel, allFilter, filters, dialogClassName, resultsClassName, ...props }: CommandSearchProps): import("react/jsx-runtime").JSX.Element;
58
+ declare function CommandSearch({ value, onValueChange, sections, children, className, isCollapsed, open, defaultOpen, onOpenChange, triggerVariant, triggerPlaceholder, inputPlaceholder, shortcutKey, shortcutLabel, allFilter, filters, loading, loadingRows, onShowAllResults, showAllResultsLabel, triggerClassName, dialogClassName, resultsClassName, ...props }: CommandSearchProps): import("react/jsx-runtime").JSX.Element;
44
59
  export { CommandSearch };
@@ -1,13 +1,15 @@
1
1
  "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Button } from "./button";
5
5
  import { Command, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "./command";
6
6
  import { EmptyState } from "./empty-state";
7
7
  import { SearchIcon } from "./animated-icons/search";
8
- import { DialogRoot, DialogSurface } from "./dialog-primitive";
8
+ import { DialogRoot, DialogSurface, DialogTitle } from "./dialog-primitive";
9
9
  import { FilterToggle } from "./filter-toggle";
10
10
  import { cn } from "./lib/utils";
11
+ import { PopoverMenuFooter, PopoverMenuFooterAction } from "./popover-menu";
12
+ import { Skeleton } from "./skeleton";
11
13
  import { Typography } from "./typography";
12
14
  import { SearchHighlight } from "./use-search-highlight";
13
15
  function CommandSearchEmptyIcon({ className }) {
@@ -20,6 +22,9 @@ function getCommandSearchItemValue(item) {
20
22
  .join(" ");
21
23
  return (_b = item.value) !== null && _b !== void 0 ? _b : fallbackValue;
22
24
  }
25
+ function CommandSearchSkeleton({ rows = 4 }) {
26
+ return (_jsx("div", { className: "space-y-[var(--space-1)] p-[var(--space-1)]", children: Array.from({ length: rows }).map((_, index) => (_jsxs("div", { className: "space-y-[var(--space-2)] rounded-sm px-[var(--space-2)] py-[var(--space-2)]", children: [_jsx(Skeleton, { className: "h-4 w-40" }), _jsx(Skeleton, { className: "h-3 w-56 max-w-full" })] }, index))) }));
27
+ }
23
28
  function useControllableOpen({ open, defaultOpen, onOpenChange, }) {
24
29
  const [internalOpen, setInternalOpen] = React.useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
25
30
  const isControlled = open !== undefined;
@@ -32,7 +37,7 @@ function useControllableOpen({ open, defaultOpen, onOpenChange, }) {
32
37
  }, [isControlled, onOpenChange]);
33
38
  return [currentOpen, setOpen];
34
39
  }
35
- function CommandSearch({ value, onValueChange, sections, children, className, isCollapsed = false, open, defaultOpen, onOpenChange, triggerPlaceholder = "Search...", inputPlaceholder = "Search...", shortcutKey = "k", shortcutLabel, allFilter, filters = [], dialogClassName, resultsClassName, ...props }) {
40
+ function CommandSearch({ value, onValueChange, sections, children, className, isCollapsed = false, open, defaultOpen, onOpenChange, triggerVariant = "default", triggerPlaceholder = "Search...", inputPlaceholder = "Search...", shortcutKey = "k", shortcutLabel, allFilter, filters = [], loading = false, loadingRows = 4, onShowAllResults, showAllResultsLabel, triggerClassName, dialogClassName, resultsClassName, ...props }) {
36
41
  const [isOpen, setIsOpen] = useControllableOpen({
37
42
  open,
38
43
  defaultOpen,
@@ -41,6 +46,13 @@ function CommandSearch({ value, onValueChange, sections, children, className, is
41
46
  const inputRef = React.useRef(null);
42
47
  const normalizedValue = value.trim();
43
48
  const visibleSections = React.useMemo(() => (sections !== null && sections !== void 0 ? sections : []).filter((section) => section.items.length > 0), [sections]);
49
+ const hasResults = children !== undefined || visibleSections.length > 0;
50
+ const shouldShowAllResults = Boolean(onShowAllResults) && Boolean(normalizedValue) && !loading;
51
+ const resolvedShowAllResultsLabel = typeof showAllResultsLabel === "function"
52
+ ? showAllResultsLabel(normalizedValue, hasResults)
53
+ : showAllResultsLabel !== null && showAllResultsLabel !== void 0 ? showAllResultsLabel : (hasResults
54
+ ? `Show all results for "${normalizedValue}"`
55
+ : `Search all records for "${normalizedValue}"`);
44
56
  const resolvedLabel = shortcutLabel !== null && shortcutLabel !== void 0 ? shortcutLabel : `⌘${shortcutKey.toUpperCase()}`;
45
57
  React.useEffect(() => {
46
58
  const handleKeyDown = (event) => {
@@ -57,18 +69,32 @@ function CommandSearch({ value, onValueChange, sections, children, className, is
57
69
  window.setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
58
70
  }
59
71
  }, [isOpen]);
60
- return (_jsxs("div", { className: className, ...props, children: [_jsxs(Button, { type: "button", onClick: () => setIsOpen(true), variant: "ghost", className: cn(isCollapsed
61
- ? "h-control w-full justify-center rounded-lg px-3 !font-normal text-muted-foreground hover:bg-white hover:text-foreground"
62
- : "h-control w-full min-w-[14rem] justify-start gap-3 rounded-lg border border-gray-200 bg-white px-3 !font-normal text-muted-foreground hover:bg-white hover:text-foreground"), children: [_jsx(SearchIcon, { className: "shrink-0", size: 16 }), !isCollapsed ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex-1 text-left text-sm", children: triggerPlaceholder }), _jsx("kbd", { className: "hidden h-5 items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] text-muted-foreground sm:inline-flex", children: resolvedLabel })] })) : null] }), _jsx(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, children: _jsx(DialogSurface, { className: cn("max-w-xl gap-0 overflow-hidden p-0 sm:max-w-xl", dialogClassName), hideCloseButton: true, children: _jsxs(Command, { shouldFilter: false, className: "rounded-none border-0", children: [_jsx(CommandInput, { ref: inputRef, value: value, onValueChange: onValueChange, placeholder: inputPlaceholder, className: "text-sm" }), allFilter || filters.length ? (_jsxs("div", { className: "flex items-center gap-1 border-b px-3 py-2", children: [allFilter ? (_jsx(FilterToggle, { selected: allFilter.selected, onClick: allFilter.onSelect, children: allFilter.label })) : null, filters.map((filter) => (_jsx(FilterToggle, { selected: filter.selected, onClick: filter.onSelect, children: filter.label }, filter.value)))] })) : null, _jsx(CommandList, { className: cn("max-h-[400px] p-1", resultsClassName), children: children !== undefined ? children : sections ? (visibleSections.length ? (visibleSections.map((section, index) => (_jsxs(React.Fragment, { children: [index > 0 ? _jsx(CommandSeparator, { alwaysRender: true }) : null, _jsx(CommandGroup, { heading: section.heading, children: section.items.map((item) => (_jsx(CommandItem, { value: getCommandSearchItemValue(item), disabled: item.disabled, onSelect: () => {
63
- var _a;
64
- (_a = item.onSelect) === null || _a === void 0 ? void 0 : _a.call(item);
65
- if (item.closeOnSelect !== false) {
66
- setIsOpen(false);
67
- }
68
- }, children: _jsxs("div", { className: "min-w-0", children: [_jsx(Typography, { as: "p", variant: "label", className: "truncate", children: _jsx(SearchHighlight, { text: item.title, query: value }) }), item.subtitle ? (_jsx(Typography, { as: "p", variant: "caption", className: "truncate", children: _jsx(SearchHighlight, { text: item.subtitle, query: value }) })) : null] }) }, item.id))) })] }, section.key)))) : (_jsx(EmptyState, { icon: CommandSearchEmptyIcon, message: normalizedValue ? "No results found" : "Start typing to search", description: normalizedValue
69
- ? `No results found for "${value}".`
70
- : "Results will appear here as you type." }))) : (_jsx(EmptyState, { icon: CommandSearchEmptyIcon, message: normalizedValue ? "No results found" : "Start typing to search", description: normalizedValue
71
- ? `No results found for "${value}".`
72
- : "Results will appear here as you type." })) })] }) }) })] }));
72
+ const preserveInputFocus = React.useCallback((event) => {
73
+ event.preventDefault();
74
+ }, []);
75
+ const selectFilter = React.useCallback((onSelect) => {
76
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect();
77
+ window.setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
78
+ }, []);
79
+ return (_jsxs("div", { className: className, ...props, children: [_jsxs(Button, { type: "button", onClick: () => setIsOpen(true), variant: "ghost", size: isCollapsed ? "icon" : "default", className: cn(triggerVariant === "sidebar" && isCollapsed
80
+ ? "h-control min-h-control w-control min-w-control max-w-control shrink-0 rounded-control border-none bg-transparent p-0 !font-normal text-muted-foreground hover:bg-card hover:text-foreground focus-visible:bg-card focus-visible:ring-ring/30"
81
+ : triggerVariant === "sidebar"
82
+ ? "h-control w-full min-w-[14rem] justify-start gap-2 rounded-control border border-[hsl(var(--layout-sidebar-border))] bg-card px-2.5 !font-normal text-muted-foreground shadow-none hover:bg-card hover:text-foreground focus-visible:border-ring focus-visible:bg-card focus-visible:ring-ring/30"
83
+ : isCollapsed
84
+ ? "h-control min-h-control w-control min-w-control max-w-control shrink-0 rounded-control p-0 !font-normal text-muted-foreground hover:bg-card hover:text-foreground"
85
+ : "h-control w-full min-w-[14rem] justify-start gap-2 rounded-control border border-border bg-card px-2.5 !font-normal text-muted-foreground hover:bg-card hover:text-foreground", triggerClassName), children: [_jsx(SearchIcon, { className: "shrink-0", size: 16 }), !isCollapsed ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex-1 text-left text-sm", children: triggerPlaceholder }), _jsx("kbd", { className: "hidden h-[20px] items-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[11px] text-muted-foreground sm:inline-flex", children: resolvedLabel })] })) : null] }), _jsx(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, children: _jsxs(DialogSurface, { className: cn("w-[calc(100vw-2rem)] max-w-[52rem] gap-0 overflow-hidden p-0 sm:w-[min(64vw,52rem)]", dialogClassName), "aria-describedby": undefined, hideCloseButton: true, children: [_jsx(DialogTitle, { className: "sr-only", children: inputPlaceholder }), _jsxs(Command, { shouldFilter: false, className: "rounded-none border-0", children: [_jsx(CommandInput, { ref: inputRef, value: value, onValueChange: onValueChange, placeholder: inputPlaceholder, className: "text-sm" }), allFilter || filters.length ? (_jsxs("div", { className: "flex flex-wrap items-center gap-1 border-b px-2.5 py-1.5", children: [allFilter ? (_jsx(FilterToggle, { selected: allFilter.selected, onMouseDown: preserveInputFocus, onClick: () => selectFilter(allFilter.onSelect), children: allFilter.label })) : null, filters.map((filter) => (_jsx(FilterToggle, { selected: filter.selected, onMouseDown: preserveInputFocus, onClick: () => selectFilter(filter.onSelect), children: filter.label }, filter.value)))] })) : null, _jsx(CommandList, { "aria-busy": loading || undefined, className: cn("max-h-[400px] p-1", resultsClassName), children: loading ? (_jsx(CommandSearchSkeleton, { rows: loadingRows })) : children !== undefined ? children : sections ? (visibleSections.length ? (visibleSections.map((section, index) => (_jsxs(React.Fragment, { children: [index > 0 ? _jsx(CommandSeparator, { alwaysRender: true }) : null, _jsx(CommandGroup, { heading: section.heading, children: section.items.map((item) => (_jsx(CommandItem, { value: getCommandSearchItemValue(item), disabled: item.disabled, onSelect: () => {
86
+ var _a;
87
+ (_a = item.onSelect) === null || _a === void 0 ? void 0 : _a.call(item);
88
+ if (item.closeOnSelect !== false) {
89
+ setIsOpen(false);
90
+ }
91
+ }, children: _jsxs("div", { className: "min-w-0", children: [_jsx(Typography, { as: "p", variant: "label", className: "truncate", children: _jsx(SearchHighlight, { text: item.title, query: value }) }), item.subtitle ? (_jsx(Typography, { as: "p", variant: "caption", className: "truncate", children: _jsx(SearchHighlight, { text: item.subtitle, query: value }) })) : null] }) }, item.id))) })] }, section.key)))) : (_jsx(EmptyState, { icon: CommandSearchEmptyIcon, message: normalizedValue ? "No results found" : "Start typing to search", description: normalizedValue
92
+ ? `No results found for "${value}".`
93
+ : "Results will appear here as you type." }))) : (_jsx(EmptyState, { icon: CommandSearchEmptyIcon, message: normalizedValue ? "No results found" : "Start typing to search", description: normalizedValue
94
+ ? `No results found for "${value}".`
95
+ : "Results will appear here as you type." })) }), shouldShowAllResults ? (_jsx(PopoverMenuFooter, { inset: "flush", children: _jsx(PopoverMenuFooterAction, { onClick: () => {
96
+ onShowAllResults === null || onShowAllResults === void 0 ? void 0 : onShowAllResults(normalizedValue);
97
+ setIsOpen(false);
98
+ }, children: _jsx("span", { className: "truncate", children: resolvedShowAllResultsLabel }) }) })) : null] })] }) })] }));
73
99
  }
74
100
  export { CommandSearch };
package/dist/command.js CHANGED
@@ -5,7 +5,7 @@ import { Command as CommandPrimitive } from "cmdk";
5
5
  import { inputControlClassName } from "./input-control";
6
6
  import { cn } from "./lib/utils";
7
7
  import { SearchInputShell } from "./search-input-shell";
8
- const Command = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive, { ref: ref, className: cn("flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", className), ...props })));
8
+ const Command = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive, { ref: ref, className: cn("flex h-full w-full flex-col overflow-hidden rounded-overlay bg-popover text-popover-foreground", className), ...props })));
9
9
  Command.displayName = CommandPrimitive.displayName;
10
10
  const CommandInput = React.forwardRef(({ className, wrapperClassName, iconClassName, iconSize = 16, ...props }, ref) => (_jsx("div", { "cmdk-input-wrapper": "", className: cn("border-b p-4", wrapperClassName), children: _jsx(SearchInputShell, { iconClassName: iconClassName, iconSize: iconSize, children: _jsx(CommandPrimitive.Input, { ref: ref, className: cn(inputControlClassName, "bg-background pl-9 pr-3", className), ...props }) }) })));
11
11
  CommandInput.displayName = CommandPrimitive.Input.displayName;
@@ -13,11 +13,11 @@ const CommandList = React.forwardRef(({ className, ...props }, ref) => (_jsx(Com
13
13
  CommandList.displayName = CommandPrimitive.List.displayName;
14
14
  const CommandEmpty = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.Empty, { ref: ref, className: cn("py-6 text-center text-sm text-muted-foreground", className), ...props })));
15
15
  CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
16
- const CommandGroup = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.Group, { ref: ref, className: cn("overflow-hidden py-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:text-muted-foreground", className), ...props })));
16
+ const CommandGroup = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.Group, { ref: ref, className: cn("overflow-hidden py-[var(--space-1)] text-foreground [&_[cmdk-group-heading]]:px-[var(--space-2)] [&_[cmdk-group-heading]]:py-[var(--space-1)] [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:text-muted-foreground", className), ...props })));
17
17
  CommandGroup.displayName = CommandPrimitive.Group.displayName;
18
18
  const CommandSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.Separator, { ref: ref, className: cn("-mx-1 my-1 h-px bg-border", className), ...props })));
19
19
  CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
20
- const CommandItem = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.Item, { ref: ref, className: cn("relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-muted data-[selected=true]:text-foreground", className), ...props })));
20
+ const CommandItem = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.Item, { ref: ref, className: cn("relative flex cursor-pointer select-none items-center rounded-sm px-[var(--space-2)] py-[var(--space-2)] text-sm outline-none transition-colors data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-muted data-[selected=true]:text-foreground", className), ...props })));
21
21
  CommandItem.displayName = CommandPrimitive.Item.displayName;
22
22
  const CommandShortcut = ({ className, ...props }) => (_jsx("span", { className: cn("ml-auto text-xs tracking-widest text-muted-foreground", className), ...props }));
23
23
  CommandShortcut.displayName = "CommandShortcut";
@@ -14,6 +14,8 @@ export interface CopyButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLBut
14
14
  copyLabel?: string;
15
15
  /** Feedback label after copying. Defaults to "Copied". */
16
16
  copiedLabel?: string;
17
+ /** Tooltip content shown briefly after copying. Defaults to "Copied!". */
18
+ copiedTooltipLabel?: React.ReactNode;
17
19
  }
18
- declare function CopyButton({ value, feedbackDuration, children, onCopy, onError, onClick, copyLabel, copiedLabel, className, type, ...props }: CopyButtonProps): import("react/jsx-runtime").JSX.Element;
20
+ declare function CopyButton({ value, feedbackDuration, children, onCopy, onError, onClick, copyLabel, copiedLabel, copiedTooltipLabel, className, type, ...props }: CopyButtonProps): import("react/jsx-runtime").JSX.Element;
19
21
  export { CopyButton };
@@ -4,7 +4,8 @@ import * as React from "react";
4
4
  import { Copy } from "lucide-react";
5
5
  import { CheckIcon } from "./animated-icons/check";
6
6
  import { cn } from "./lib/utils";
7
- function CopyButton({ value, feedbackDuration = 1500, children, onCopy, onError, onClick, copyLabel = "Copy", copiedLabel = "Copied", className, type = "button", ...props }) {
7
+ import { Tooltip } from "./tooltip";
8
+ function CopyButton({ value, feedbackDuration = 1500, children, onCopy, onError, onClick, copyLabel = "Copy", copiedLabel = "Copied", copiedTooltipLabel = "Copied!", className, type = "button", ...props }) {
8
9
  const [copied, setCopied] = React.useState(false);
9
10
  const timeoutRef = React.useRef(null);
10
11
  React.useEffect(() => () => {
@@ -29,6 +30,7 @@ function CopyButton({ value, feedbackDuration = 1500, children, onCopy, onError,
29
30
  }, [value, feedbackDuration, onCopy, onClick, onError]);
30
31
  const iconOnly = children === undefined;
31
32
  const label = copied ? copiedLabel : copyLabel;
32
- return (_jsxs(_Fragment, { children: [_jsxs("button", { type: type, onClick: handleCopy, "aria-label": iconOnly ? label : undefined, className: cn("inline-flex cursor-pointer items-center gap-1.5 rounded text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50", !iconOnly && "text-sm font-medium", className), ...props, children: [copied ? (_jsx(CheckIcon, { size: 16, className: "text-green-600" })) : (_jsx(Copy, { className: "h-4 w-4", "aria-hidden": "true" })), iconOnly ? null : _jsx("span", { children: copied ? copiedLabel : children })] }), _jsx("span", { className: "sr-only", "aria-live": "polite", children: copied ? copiedLabel : "" })] }));
33
+ const button = (_jsxs("button", { type: type, onClick: handleCopy, "aria-label": iconOnly ? label : undefined, className: cn("inline-flex cursor-pointer items-center gap-1.5 rounded text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50", !iconOnly && "text-sm font-medium", className), ...props, children: [copied ? (_jsx(CheckIcon, { size: 16, className: "text-green-600" })) : (_jsx(Copy, { className: "h-4 w-4", "aria-hidden": "true" })), iconOnly ? null : _jsx("span", { children: copied ? copiedLabel : children })] }));
34
+ return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { content: copiedTooltipLabel, delayDuration: 0, open: copied, children: button }), _jsx("span", { className: "sr-only", "aria-live": "polite", children: copied ? copiedLabel : "" })] }));
33
35
  }
34
36
  export { CopyButton };
@@ -3,8 +3,10 @@ export interface CountBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
3
3
  count: number;
4
4
  variant?: "default" | "danger" | "muted";
5
5
  size?: "sm" | "md";
6
+ /** Caps the displayed value, e.g. count={128} max={9} renders "9+". */
7
+ max?: number;
6
8
  pulse?: boolean;
7
9
  hideWhenZero?: boolean;
8
10
  }
9
- declare function CountBadge({ count, variant, size, pulse, className, hideWhenZero, ...props }: CountBadgeProps): import("react/jsx-runtime").JSX.Element | null;
11
+ declare function CountBadge({ count, variant, size, max, pulse, className, hideWhenZero, ...props }: CountBadgeProps): import("react/jsx-runtime").JSX.Element | null;
10
12
  export { CountBadge };
@@ -1,13 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Badge } from "./badge";
3
3
  import { cn } from "./lib/utils";
4
- function CountBadge({ count, variant = "default", size = "md", pulse = false, className, hideWhenZero = false, ...props }) {
4
+ function CountBadge({ count, variant = "default", size = "md", max, pulse = false, className, hideWhenZero = false, ...props }) {
5
5
  if (count < 0 || (count === 0 && hideWhenZero))
6
6
  return null;
7
7
  const effectiveVariant = count === 0 ? "muted" : variant;
8
+ const resolvedMax = typeof max === "number" && Number.isFinite(max) && max > 0
9
+ ? Math.floor(max)
10
+ : undefined;
11
+ const isCapped = resolvedMax !== undefined && count > resolvedMax;
12
+ const displayCount = isCapped ? `${resolvedMax}+` : count;
8
13
  const sizeClasses = {
9
- sm: "min-w-[18px] h-[18px] text-[10px] px-1",
10
- md: "min-w-[20px] h-5 text-[11px] px-1.5",
14
+ sm: "h-[18px] min-w-[18px] px-1",
15
+ md: "h-[20px] min-w-[20px] px-1.5",
11
16
  };
12
17
  const variantClasses = {
13
18
  default: {
@@ -20,7 +25,7 @@ function CountBadge({ count, variant = "default", size = "md", pulse = false, cl
20
25
  },
21
26
  muted: {
22
27
  badgeVariant: "muted",
23
- className: "border-transparent",
28
+ className: "border-transparent bg-muted-foreground/15 text-foreground/80",
24
29
  },
25
30
  };
26
31
  const pulseColor = {
@@ -29,6 +34,6 @@ function CountBadge({ count, variant = "default", size = "md", pulse = false, cl
29
34
  muted: "bg-muted-foreground/50",
30
35
  };
31
36
  const shouldPulse = pulse && count > 0;
32
- return (_jsxs("span", { className: cn("relative inline-flex", className), ...props, children: [shouldPulse ? (_jsx("span", { className: cn("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75", pulseColor[effectiveVariant]) })) : null, _jsx("span", { className: "relative inline-flex", children: _jsx(Badge, { variant: variantClasses[effectiveVariant].badgeVariant, className: cn("max-w-none rounded-full font-medium leading-none", sizeClasses[size], variantClasses[effectiveVariant].className), children: count }) })] }));
37
+ return (_jsxs("span", { "data-slot": "count-badge", className: cn("relative inline-flex", className), ...props, children: [shouldPulse ? (_jsx("span", { className: cn("absolute inline-flex h-full w-full animate-ping rounded-control opacity-75", pulseColor[effectiveVariant]) })) : null, _jsx("span", { className: "relative inline-flex", children: _jsx(Badge, { variant: variantClasses[effectiveVariant].badgeVariant, className: cn("max-w-none", sizeClasses[size], variantClasses[effectiveVariant].className), children: displayCount }) })] }));
33
38
  }
34
39
  export { CountBadge };
@@ -1,8 +1,11 @@
1
1
  import * as React from "react";
2
- export interface DateInputProps {
2
+ export interface DateRangeInputValue {
3
+ from?: string;
4
+ to?: string;
5
+ }
6
+ interface DateInputSharedProps {
3
7
  label: React.ReactNode;
4
- value: string;
5
- onValueChange: (value: string) => void;
8
+ appearance?: "field" | "inline";
6
9
  description?: React.ReactNode;
7
10
  error?: React.ReactNode;
8
11
  invalid?: boolean;
@@ -18,5 +21,17 @@ export interface DateInputProps {
18
21
  errorClassName?: string;
19
22
  id?: string;
20
23
  }
24
+ export interface DateInputSingleProps extends DateInputSharedProps {
25
+ mode?: "single";
26
+ value: string;
27
+ onValueChange: (value: string) => void;
28
+ }
29
+ export interface DateInputRangeProps extends DateInputSharedProps {
30
+ mode: "range";
31
+ value: DateRangeInputValue;
32
+ onValueChange: (value: DateRangeInputValue) => void;
33
+ numberOfMonths?: number;
34
+ }
35
+ export type DateInputProps = DateInputSingleProps | DateInputRangeProps;
21
36
  declare const DateInput: React.ForwardRefExoticComponent<DateInputProps & React.RefAttributes<HTMLButtonElement>>;
22
37
  export { DateInput };
@@ -2,26 +2,41 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { format, parse } from "date-fns";
5
+ import { CalendarIcon } from "lucide-react";
5
6
  import { Calendar } from "./calendar";
6
7
  import { inputControlClassName } from "./input-control";
7
8
  import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
8
9
  import { PopoverContent, PopoverRoot, PopoverTrigger } from "./popover-primitive";
9
10
  import { cn } from "./lib/utils";
11
+ function createLocalDate(year, monthIndex, day) {
12
+ const date = new Date(year, monthIndex, day);
13
+ if (date.getFullYear() !== year ||
14
+ date.getMonth() !== monthIndex ||
15
+ date.getDate() !== day) {
16
+ return undefined;
17
+ }
18
+ return date;
19
+ }
10
20
  function parseDateString(dateStr) {
11
21
  if (!(dateStr === null || dateStr === void 0 ? void 0 : dateStr.trim()))
12
22
  return undefined;
13
23
  try {
14
- const nativeDate = new Date(dateStr);
15
- if (!isNaN(nativeDate.getTime()))
16
- return nativeDate;
17
- const ddmmyyyyMatch = dateStr.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})/);
24
+ const normalizedDateStr = dateStr.trim();
25
+ const isoDateMatch = normalizedDateStr.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
26
+ if (isoDateMatch) {
27
+ const [, year, month, day] = isoDateMatch;
28
+ return createLocalDate(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10));
29
+ }
30
+ const ddmmyyyyMatch = normalizedDateStr.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})/);
18
31
  if (ddmmyyyyMatch) {
19
32
  const [, day, month, year] = ddmmyyyyMatch;
20
- const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
21
- if (!isNaN(date.getTime()))
22
- return date;
33
+ return createLocalDate(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10));
23
34
  }
24
- const date = parse(dateStr, "yyyy-MM-dd", new Date());
35
+ const nativeDate = new Date(normalizedDateStr);
36
+ if (!isNaN(nativeDate.getTime())) {
37
+ return nativeDate;
38
+ }
39
+ const date = parse(normalizedDateStr, "yyyy-MM-dd", new Date());
25
40
  if (!isNaN(date.getTime()))
26
41
  return date;
27
42
  return undefined;
@@ -33,7 +48,51 @@ function parseDateString(dateStr) {
33
48
  function formatISODate(date) {
34
49
  return format(date, "yyyy-MM-dd");
35
50
  }
36
- const DateInput = React.forwardRef(({ label, value, onValueChange, description, error, invalid = false, hideLabel = false, placeholder = "Pick a date", disabled = false, minDate, maxDate, className, triggerClassName, labelClassName, descriptionClassName, errorClassName, id, }, ref) => {
51
+ function formatDisplayDate(date) {
52
+ return format(date, "d MMM yyyy");
53
+ }
54
+ function getDateTime(date) {
55
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
56
+ }
57
+ function isDateOutsideBounds(date, minDate, maxDate) {
58
+ const dateTime = getDateTime(date);
59
+ if (minDate && dateTime < getDateTime(minDate))
60
+ return true;
61
+ if (maxDate && dateTime > getDateTime(maxDate))
62
+ return true;
63
+ return false;
64
+ }
65
+ function parseDateRangeValue(value) {
66
+ const from = parseDateString(value.from);
67
+ const to = parseDateString(value.to);
68
+ if (!from && !to)
69
+ return undefined;
70
+ return { from, to };
71
+ }
72
+ function formatDateRangeValue(range) {
73
+ if (!(range === null || range === void 0 ? void 0 : range.from) && !(range === null || range === void 0 ? void 0 : range.to)) {
74
+ return {};
75
+ }
76
+ return {
77
+ ...(range.from ? { from: formatISODate(range.from) } : {}),
78
+ ...(range.to ? { to: formatISODate(range.to) } : {}),
79
+ };
80
+ }
81
+ function formatDisplayRange(range) {
82
+ if ((range === null || range === void 0 ? void 0 : range.from) && range.to) {
83
+ return `${formatDisplayDate(range.from)} - ${formatDisplayDate(range.to)}`;
84
+ }
85
+ if (range === null || range === void 0 ? void 0 : range.from) {
86
+ return `${formatDisplayDate(range.from)} -`;
87
+ }
88
+ if (range === null || range === void 0 ? void 0 : range.to) {
89
+ return `- ${formatDisplayDate(range.to)}`;
90
+ }
91
+ return undefined;
92
+ }
93
+ const DateInput = React.forwardRef((props, ref) => {
94
+ var _a, _b, _c;
95
+ const { label, appearance = "field", description, error, invalid = false, hideLabel = false, disabled = false, minDate, maxDate, className, triggerClassName, labelClassName, descriptionClassName, errorClassName, id, } = props;
37
96
  const [open, setOpen] = React.useState(false);
38
97
  const generatedId = React.useId();
39
98
  const inputId = id !== null && id !== void 0 ? id : `date-input-${generatedId}`;
@@ -41,21 +100,35 @@ const DateInput = React.forwardRef(({ label, value, onValueChange, description,
41
100
  const showDescription = Boolean(description) && !error;
42
101
  const descriptionId = showDescription ? `${inputId}-description` : undefined;
43
102
  const isInvalid = invalid || Boolean(error);
44
- const selectedDate = React.useMemo(() => parseDateString(value), [value]);
45
- const displayValue = selectedDate
46
- ? format(selectedDate, "d MMM yyyy")
47
- : undefined;
103
+ const isRange = props.mode === "range";
104
+ const selectedDate = React.useMemo(() => (isRange ? undefined : parseDateString(props.value)), [isRange, props.value]);
105
+ const selectedRange = React.useMemo(() => (isRange ? parseDateRangeValue(props.value) : undefined), [isRange, props.value]);
106
+ const displayValue = isRange
107
+ ? formatDisplayRange(selectedRange)
108
+ : selectedDate
109
+ ? formatDisplayDate(selectedDate)
110
+ : undefined;
111
+ const placeholder = (_a = props.placeholder) !== null && _a !== void 0 ? _a : (isRange ? "Pick range" : "Pick a date");
48
112
  const handleSelect = (date) => {
49
- onValueChange(date ? formatISODate(date) : "");
113
+ if (props.mode === "range")
114
+ return;
115
+ props.onValueChange(date ? formatISODate(date) : "");
50
116
  setOpen(false);
51
117
  };
52
- return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs(PopoverRoot, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("div", { className: "relative w-full", children: [_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground", style: { width: 14, height: 14 }, "aria-hidden": "true", children: [_jsx("rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", ry: "2" }), _jsx("line", { x1: "16", x2: "16", y1: "2", y2: "6" }), _jsx("line", { x1: "8", x2: "8", y1: "2", y2: "6" }), _jsx("line", { x1: "3", x2: "21", y1: "10", y2: "10" })] }), _jsx("button", { ref: ref, type: "button", id: inputId, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn(inputControlClassName, "cursor-pointer pl-9 text-left", !selectedDate && "text-muted-foreground", isInvalid && "border-destructive focus-visible:ring-destructive", triggerClassName), children: displayValue !== null && displayValue !== void 0 ? displayValue : _jsx("span", { children: placeholder }) })] }) }), _jsx(PopoverContent, { className: "w-auto p-0", align: "start", children: _jsx(Calendar, { mode: "single", selected: selectedDate, onSelect: handleSelect, disabled: (date) => {
53
- if (minDate && date < minDate)
54
- return true;
55
- if (maxDate && date > maxDate)
56
- return true;
57
- return false;
58
- }, defaultMonth: selectedDate, autoFocus: true }) })] }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
118
+ const handleRangeSelect = (range) => {
119
+ if (props.mode !== "range")
120
+ return;
121
+ props.onValueChange(formatDateRangeValue(range));
122
+ if ((range === null || range === void 0 ? void 0 : range.from) && range.to) {
123
+ setOpen(false);
124
+ }
125
+ };
126
+ const disabledDays = (date) => isDateOutsideBounds(date, minDate, maxDate);
127
+ const calendarContent = (_jsx(PopoverContent, { className: "w-auto p-0", align: "start", children: props.mode === "range" ? (_jsx(Calendar, { mode: "range", selected: selectedRange, onSelect: handleRangeSelect, disabled: disabledDays, defaultMonth: (_b = selectedRange === null || selectedRange === void 0 ? void 0 : selectedRange.from) !== null && _b !== void 0 ? _b : selectedRange === null || selectedRange === void 0 ? void 0 : selectedRange.to, numberOfMonths: (_c = props.numberOfMonths) !== null && _c !== void 0 ? _c : 2, autoFocus: true })) : (_jsx(Calendar, { mode: "single", selected: selectedDate, onSelect: handleSelect, disabled: disabledDays, defaultMonth: selectedDate, autoFocus: true })) }));
128
+ if (appearance === "inline") {
129
+ return (_jsxs("span", { className: cn("inline-flex", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsxs(PopoverRoot, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { ref: ref, type: "button", id: inputId, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn("inline-flex cursor-pointer items-baseline rounded-sm border-0 bg-transparent p-0 font-medium text-primary underline decoration-primary/40 underline-offset-4 transition-colors hover:decoration-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", !displayValue && "text-muted-foreground decoration-border", isInvalid && "text-destructive decoration-destructive", triggerClassName), children: [_jsxs("span", { className: "sr-only", children: [label, ": "] }), _jsx("span", { children: displayValue !== null && displayValue !== void 0 ? displayValue : placeholder })] }) }), calendarContent] }), showDescription ? (_jsx("span", { id: descriptionId, className: "sr-only", children: description })) : null, error ? (_jsx("span", { id: errorId, role: "alert", className: "sr-only", children: error })) : null] }));
130
+ }
131
+ return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs(PopoverRoot, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { ref: ref, type: "button", id: inputId, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn(inputControlClassName, "relative cursor-pointer pr-9 text-left", !displayValue && "text-muted-foreground", isInvalid && "border-destructive focus-visible:ring-destructive", triggerClassName), children: [_jsx("span", { className: "block truncate", children: displayValue !== null && displayValue !== void 0 ? displayValue : placeholder }), _jsx(CalendarIcon, { "aria-hidden": "true", className: "pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground", strokeWidth: 2 })] }) }), calendarContent] }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
59
132
  });
60
133
  DateInput.displayName = "DateInput";
61
134
  export { DateInput };
@@ -4,20 +4,21 @@ import * as React from "react";
4
4
  import * as DialogPrimitive from "@radix-ui/react-dialog";
5
5
  import { XIcon } from "./animated-icons/x";
6
6
  import { cn } from "./lib/utils";
7
+ import { typographyVariants } from "./typography";
7
8
  const DialogRoot = DialogPrimitive.Root;
8
9
  const DialogTrigger = DialogPrimitive.Trigger;
9
10
  const DialogPortal = DialogPrimitive.Portal;
10
11
  const DialogClose = DialogPrimitive.Close;
11
12
  const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Overlay, { ref: ref, className: cn("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className), ...props })));
12
13
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
13
- const DialogSurface = React.forwardRef(({ className, children, hideCloseButton, ...props }, ref) => (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", className), ...props, children: [children, !hideCloseButton ? (_jsxs(DialogPrimitive.Close, { className: "absolute right-2 top-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground ring-offset-background transition-colors hover:bg-accent hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none", children: [_jsx(XIcon, { size: 16 }), _jsx("span", { className: "sr-only", children: "Close" })] })) : null] })] })));
14
+ const DialogSurface = React.forwardRef(({ className, children, hideCloseButton, ...props }, ref) => (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-3 border bg-background p-4 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-overlay", className), ...props, children: [children, !hideCloseButton ? (_jsxs(DialogPrimitive.Close, { className: "absolute right-2 top-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-control text-muted-foreground ring-offset-background transition-colors hover:bg-accent hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none", children: [_jsx(XIcon, { size: 16 }), _jsx("span", { className: "sr-only", children: "Close" })] })) : null] })] })));
14
15
  DialogSurface.displayName = DialogPrimitive.Content.displayName;
15
16
  const DialogHeader = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props }));
16
17
  DialogHeader.displayName = "DialogHeader";
17
18
  const DialogFooter = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props }));
18
19
  DialogFooter.displayName = "DialogFooter";
19
- const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Title, { ref: ref, className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props })));
20
+ const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Title, { ref: ref, className: cn(typographyVariants({ variant: "subheading" }), className), ...props })));
20
21
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
21
- const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Description, { ref: ref, className: cn("text-sm text-muted-foreground", className), ...props })));
22
+ const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Description, { ref: ref, className: cn(typographyVariants({ variant: "bodySm" }), "text-muted-foreground", className), ...props })));
22
23
  DialogDescription.displayName = DialogPrimitive.Description.displayName;
23
24
  export { DialogRoot, DialogPortal, DialogOverlay, DialogTrigger, DialogClose, DialogSurface, DialogHeader, DialogFooter, DialogTitle, DialogDescription, };
@@ -1,10 +1,17 @@
1
1
  import * as React from "react";
2
+ import { type BadgeProps } from "./badge";
2
3
  import { type TooltipProps } from "./tooltip";
4
+ export interface DraggableTabBadge {
5
+ label: React.ReactNode;
6
+ variant?: BadgeProps["variant"];
7
+ bordered?: BadgeProps["bordered"];
8
+ }
3
9
  export interface DraggableTabItem {
4
10
  id: string;
5
11
  label: React.ReactNode;
6
12
  active?: boolean;
7
13
  disabled?: boolean;
14
+ badge?: DraggableTabBadge;
8
15
  leading?: React.ReactNode;
9
16
  trailing?: React.ReactNode;
10
17
  tooltip?: React.ReactNode;