@neasg/design-system 0.4.8 → 0.4.10

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 +14 -14
  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 +9 -3
  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 +7 -3
  42. package/dist/field.js +8 -4
  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 +27 -0
  80. package/dist/radio-group.js +50 -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 +241 -55
  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 +18 -2
package/dist/card.js CHANGED
@@ -1,16 +1,70 @@
1
1
  "use client";
2
- import { jsx as _jsx, 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 { cn } from "./lib/utils";
5
5
  import { Skeleton } from "./skeleton";
6
6
  import { Typography } from "./typography";
7
- const Card = React.forwardRef(({ title, description, variant = "default", size = "default", stat, subMetric, footer, active, loading = false, children, className, onClick, ...props }, ref) => {
8
- const renderedTitle = React.isValidElement(title) ? (title) : (_jsx(Typography, { as: "p", variant: "label", color: variant === "gradient" ? "inherit" : "muted", className: variant === "gradient" ? "opacity-80" : undefined, children: title }));
7
+ const cardGridColumns = {
8
+ 1: "grid-cols-1",
9
+ 2: "sm:grid-cols-2",
10
+ 3: "md:grid-cols-2 lg:grid-cols-3",
11
+ 4: "sm:grid-cols-2 xl:grid-cols-4",
12
+ };
13
+ const cardSplitGridColumns = {
14
+ lg: {
15
+ sm: "lg:grid-cols-[minmax(0,1fr)_18rem]",
16
+ md: "lg:grid-cols-[minmax(0,1fr)_20rem]",
17
+ lg: "lg:grid-cols-[minmax(0,1fr)_22rem]",
18
+ },
19
+ xl: {
20
+ sm: "xl:grid-cols-[minmax(0,1fr)_18rem]",
21
+ md: "xl:grid-cols-[minmax(0,1fr)_20rem]",
22
+ lg: "xl:grid-cols-[minmax(0,1fr)_22rem]",
23
+ },
24
+ };
25
+ const Card = React.forwardRef(({ title, description, variant = "default", size = "default", stat, subMetric, footer, active, accent, loading = false, children, className, onClick, onKeyDown, role, tabIndex, ...props }, ref) => {
26
+ const interactive = Boolean(onClick);
27
+ const hasStat = stat !== undefined;
28
+ const handleKeyDown = (event) => {
29
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
30
+ if (event.defaultPrevented ||
31
+ !interactive ||
32
+ !onClick ||
33
+ (event.key !== "Enter" && event.key !== " ")) {
34
+ return;
35
+ }
36
+ event.preventDefault();
37
+ onClick(event);
38
+ };
39
+ const renderedTitle = React.isValidElement(title) ? (title) : (_jsx(Typography, { as: "p", variant: hasStat ? "caption" : "body", color: variant === "gradient" ? "inherit" : hasStat ? "muted" : "foreground", className: cn(hasStat && "font-medium", !hasStat && "font-semibold", variant === "gradient" && "opacity-80"), children: title }));
9
40
  const renderedDescription = description == null ? null : React.isValidElement(description) ? (description) : (_jsx(Typography, { as: "p", variant: "caption", color: variant === "gradient" ? "inherit" : undefined, className: variant === "gradient" ? "opacity-70" : undefined, children: description }));
10
- return (_jsxs("div", { ref: ref, onClick: onClick, className: cn("rounded-lg border text-card-foreground", variant === "default" && "bg-card", variant === "elevated" && "bg-card shadow-sm", variant === "gradient" &&
11
- "border-primary/20 bg-gradient-to-br from-primary to-primary/80 text-primary-foreground", active && "border-primary bg-primary/[0.04] ring-1 ring-primary/20", onClick && "cursor-pointer transition-colors hover:border-primary", className), ...props, children: [_jsxs("div", { className: cn("flex flex-col gap-2", size === "default" && "p-6", size === "compact" && "p-3"), children: [loading ? (_jsx(Skeleton, { className: "h-3.5 w-24" })) : (renderedTitle), loading
41
+ return (_jsxs("div", { ref: ref, onClick: onClick, onKeyDown: handleKeyDown, role: role !== null && role !== void 0 ? role : (interactive ? "button" : undefined), tabIndex: tabIndex !== null && tabIndex !== void 0 ? tabIndex : (interactive ? 0 : undefined), className: cn("rounded-surface border border-[hsl(var(--card-border))] text-card-foreground", variant === "default" && "bg-card", variant === "elevated" && "bg-card shadow-sm", variant === "gradient" &&
42
+ "border-[hsl(var(--card-gradient-border))]/30 bg-gradient-to-br from-[hsl(var(--card-gradient-from))] to-[hsl(var(--card-gradient-to))] text-[hsl(var(--card-gradient-foreground))]", active && "border-primary bg-primary/[0.04] ring-1 ring-primary/20", accent === "primary" &&
43
+ "border-[hsl(var(--card-border))] shadow-[inset_4px_0_0_0_hsl(var(--primary))]", interactive &&
44
+ "cursor-pointer transition-[border-color,background-color,box-shadow] hover:border-primary/50 hover:bg-primary/5 hover:shadow-md focus-visible:border-primary/50 focus-visible:bg-primary/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", className), ...props, children: [_jsxs("div", { className: cn("flex flex-col gap-2", size === "default" && "p-4", size === "compact" && "p-3"), children: [loading ? (_jsx(Skeleton, { className: "h-3.5 w-24" })) : (renderedTitle), loading
12
45
  ? description != null && _jsx(Skeleton, { className: "h-5 w-40" })
13
- : renderedDescription, loading ? (stat !== undefined && _jsx(Skeleton, { className: "h-8 w-20" })) : stat ? (_jsx(Typography, { as: "p", variant: "title", color: variant === "gradient" ? "inherit" : undefined, children: stat })) : null, loading ? (subMetric !== undefined && _jsx(Skeleton, { className: "h-5 w-28" })) : subMetric ? (_jsx(Typography, { as: "p", variant: "caption", color: variant === "gradient" ? "inherit" : undefined, className: variant === "gradient" ? "opacity-80" : undefined, children: subMetric })) : null] }), children ? (_jsx("div", { className: cn(size === "default" && "p-6 pt-0", size === "compact" && "p-3 pt-0"), children: children })) : null, footer ? (_jsx("div", { className: cn("flex items-center", size === "default" && "p-6 pt-0", size === "compact" && "p-3 pt-0"), children: footer })) : null] }));
46
+ : renderedDescription, loading ? (stat !== undefined && _jsx(Skeleton, { className: "h-8 w-20" })) : stat ? (_jsx(Typography, { as: "p", variant: "title", color: variant === "gradient" ? "inherit" : undefined, children: stat })) : null, loading ? (subMetric !== undefined && _jsx(Skeleton, { className: "h-5 w-28" })) : subMetric ? (_jsx(Typography, { as: "p", variant: "caption", color: variant === "gradient" ? "inherit" : undefined, className: variant === "gradient" ? "opacity-80" : undefined, children: subMetric })) : null] }), children ? (_jsx("div", { className: cn(size === "default" && "p-4 pt-0", size === "compact" && "p-3 pt-0"), children: children })) : null, footer ? (_jsx("div", { className: cn("flex items-center", size === "default" && "p-4 pt-0", size === "compact" && "p-3 pt-0"), children: footer })) : null] }));
14
47
  });
15
48
  Card.displayName = "Card";
16
- export { Card };
49
+ function CardGrid({ columns = 3, align = "stretch", className, ...props }) {
50
+ return (_jsx("div", { className: cn("grid gap-3", cardGridColumns[columns], align === "start" && "auto-rows-max items-start", className), ...props }));
51
+ }
52
+ function CardSplitGrid({ asideWidth = "md", breakpoint = "xl", align = "start", className, ...props }) {
53
+ return (_jsx("div", { className: cn("grid gap-3", cardSplitGridColumns[breakpoint][asideWidth], align === "start" && "auto-rows-max items-start", className), ...props }));
54
+ }
55
+ function CardList({ density = "default", variant = "cards", className, ...props }) {
56
+ return (_jsx("div", { className: cn("grid", variant === "cards" && density === "default" && "gap-2", variant === "cards" && density === "compact" && "gap-1.5", variant === "divided" &&
57
+ "overflow-hidden rounded-surface border border-[hsl(var(--card-border))] bg-background", variant === "divided" &&
58
+ "[&>[data-slot=card-list-item]]:rounded-none [&>[data-slot=card-list-item]]:border-0 [&>[data-slot=card-list-item]+[data-slot=card-list-item]]:border-t [&>[data-slot=card-list-item]+[data-slot=card-list-item]]:border-[hsl(var(--card-border))]", className), ...props }));
59
+ }
60
+ function CardListItem({ label, description, leading, leadingVariant = "badge", trailing, meta, labelLines = 1, descriptionLines = 1, className, onClick, ...props }) {
61
+ const interactive = Boolean(onClick);
62
+ const trailingNode = trailing == null || React.isValidElement(trailing) ? (trailing) : (_jsx(Typography, { as: "span", variant: "bodySm", className: "block truncate font-medium", children: trailing }));
63
+ const content = (_jsxs(_Fragment, { children: [leading !== undefined ? (leadingVariant === "badge" ? (_jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-surface bg-muted text-sm font-semibold text-foreground", children: leading })) : (_jsx("div", { className: "shrink-0 text-muted-foreground", children: leading }))) : null, _jsxs("div", { className: "min-w-0 flex-1 overflow-hidden", children: [_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-2", children: [_jsx(Typography, { as: "p", variant: "bodySm", className: cn("min-w-0 font-medium text-foreground", labelLines === 1 && "truncate", labelLines === 2 && "line-clamp-2"), children: label }), meta !== undefined ? (_jsx(Typography, { as: "span", variant: "caption", className: "min-w-0 max-w-[50%] shrink truncate text-right text-muted-foreground", children: meta })) : null] }), description !== undefined ? (_jsx(Typography, { as: "p", variant: "caption", className: cn("mt-0.5 text-muted-foreground", descriptionLines === 1 && "truncate", descriptionLines === 2 && "line-clamp-2"), children: description })) : null] }), trailingNode !== undefined ? (_jsx("div", { className: "min-w-0 max-w-[45%] shrink-0 overflow-hidden truncate text-right [&>*]:max-w-full", children: trailingNode })) : null] }));
64
+ const itemClassName = cn("flex w-full min-w-0 items-center gap-3 rounded-surface border border-[hsl(var(--card-border))] bg-background py-2 pl-3 pr-4 text-left", interactive && "cursor-pointer transition-colors hover:border-primary", className);
65
+ if (interactive) {
66
+ return (_jsx("button", { type: "button", "data-slot": "card-list-item", className: itemClassName, onClick: onClick, ...props, children: content }));
67
+ }
68
+ return (_jsx("div", { "data-slot": "card-list-item", className: itemClassName, ...props, children: content }));
69
+ }
70
+ export { Card, CardGrid, CardSplitGrid, CardList, CardListItem };
@@ -8,6 +8,8 @@ export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputE
8
8
  label?: React.ReactNode;
9
9
  /** Description shown below the label in smaller muted text. */
10
10
  description?: React.ReactNode;
11
+ /** Aligns the checkbox control against the label content. */
12
+ labelAlign?: "center" | "start";
11
13
  }
12
14
  declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
13
15
  export { Checkbox };
package/dist/checkbox.js CHANGED
@@ -8,10 +8,16 @@ 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 startAlignedOffsetClasses = {
12
+ sm: "mt-[3px]",
13
+ md: "mt-[3px]",
14
+ lg: "mt-0",
15
+ };
16
+ const Checkbox = React.forwardRef(({ className, indeterminate = false, size = "md", checked, disabled, label, description, labelAlign = "center", id, ...props }, ref) => {
12
17
  const innerRef = React.useRef(null);
13
18
  const generatedId = React.useId();
14
19
  const inputId = id !== null && id !== void 0 ? id : `checkbox-${generatedId}`;
20
+ const alignToStart = labelAlign === "start" || Boolean(description);
15
21
  React.useImperativeHandle(ref, () => innerRef.current);
16
22
  React.useEffect(() => {
17
23
  if (innerRef.current) {
@@ -20,10 +26,10 @@ const Checkbox = React.forwardRef(({ className, indeterminate = false, size = "m
20
26
  }, [indeterminate]);
21
27
  const box = (_jsxs("span", { className: cn("relative inline-block shrink-0 rounded-sm border bg-background align-middle text-primary-foreground transition-colors", sizeClasses[size], (checked || indeterminate) && !disabled
22
28
  ? "border-primary bg-primary"
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] }));
29
+ : "border-input", disabled && "cursor-not-allowed opacity-50", label && alignToStart && startAlignedOffsetClasses[size], !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
30
  if (!label)
25
31
  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] })] }));
32
+ return (_jsxs("label", { htmlFor: inputId, className: cn("inline-flex cursor-pointer gap-2 text-sm", alignToStart ? "items-start" : "items-center", disabled && "cursor-not-allowed opacity-50", className), children: [box, _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5 select-none", children: [_jsx(Typography, { as: "span", variant: "bodySm", children: label }), description ? (_jsx(Typography, { as: "p", variant: "caption", color: "muted", children: description })) : null] })] }));
27
33
  });
28
34
  Checkbox.displayName = "Checkbox";
29
35
  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 };