@neasg/design-system 0.4.11 → 0.4.13

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 (44) hide show
  1. package/README.md +147 -5
  2. package/dist/avatar-profile-popover.d.ts +34 -0
  3. package/dist/avatar-profile-popover.js +27 -0
  4. package/dist/badge.d.ts +1 -1
  5. package/dist/button.d.ts +1 -1
  6. package/dist/card.d.ts +2 -0
  7. package/dist/card.js +72 -3
  8. package/dist/command-search.d.ts +47 -2
  9. package/dist/command-search.js +173 -24
  10. package/dist/command.d.ts +2 -0
  11. package/dist/command.js +2 -2
  12. package/dist/dialog-primitive.js +1 -1
  13. package/dist/draggable-tabs.d.ts +3 -0
  14. package/dist/draggable-tabs.js +4 -0
  15. package/dist/editable-table.js +2 -2
  16. package/dist/guidance-tip.d.ts +26 -0
  17. package/dist/guidance-tip.js +162 -0
  18. package/dist/index.d.ts +15 -6
  19. package/dist/index.js +6 -2
  20. package/dist/jump-target-highlight.d.ts +13 -0
  21. package/dist/jump-target-highlight.js +95 -0
  22. package/dist/layout.d.ts +27 -5
  23. package/dist/layout.js +128 -34
  24. package/dist/message-item.js +9 -10
  25. package/dist/notification.js +1 -1
  26. package/dist/page-layout.d.ts +22 -0
  27. package/dist/page-layout.js +38 -0
  28. package/dist/page-section.js +1 -1
  29. package/dist/popover-menu.js +2 -2
  30. package/dist/routing-timeline.js +1 -1
  31. package/dist/styles.css +16 -0
  32. package/dist/table-column-visibility.d.ts +2 -1
  33. package/dist/table-column-visibility.js +9 -8
  34. package/dist/table-toolbar.d.ts +2 -1
  35. package/dist/table-toolbar.js +7 -2
  36. package/dist/table.d.ts +31 -2
  37. package/dist/table.js +298 -23
  38. package/dist/tabs.d.ts +2 -1
  39. package/dist/tabs.js +8 -5
  40. package/dist/theme-switcher.d.ts +1 -1
  41. package/dist/theme-switcher.js +4 -6
  42. package/dist/theme.d.ts +336 -77
  43. package/dist/theme.js +269 -55
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -64,9 +64,151 @@ const config: Config = {
64
64
  export default config;
65
65
  ```
66
66
 
67
- Theme tokens are provided through the exported stylesheet. The default root font
68
- size is 12.5px. If you want to change the package base font size, override
69
- `--font-size-root` after importing the stylesheet:
67
+ Theme tokens are provided through the exported stylesheet. Light mode is the
68
+ default; apply the `dark` class to an app root to render the dark palette:
69
+
70
+ ```tsx
71
+ <html className={isDarkMode ? "dark" : undefined}>
72
+ <body>...</body>
73
+ </html>
74
+ ```
75
+
76
+ Systems that need to inspect or mirror the dark palette can import the token
77
+ mapping from the root package:
78
+
79
+ ```ts
80
+ import {
81
+ darkThemeTokenMapping,
82
+ neaDarkNeutralPalette,
83
+ neaDarkNeutralTokenMapping,
84
+ neaPalette,
85
+ neaPaletteDarkTokenMapping,
86
+ semanticColorTokens,
87
+ } from "@neasg/design-system";
88
+ ```
89
+
90
+ `darkThemeTokenMapping` mirrors the `.dark` declarations in the package
91
+ stylesheet. `neaDarkNeutralTokenMapping` contains the raw dark neutral HSL
92
+ values. `neaDarkNeutralPalette` lists the dark neutral token names and is also
93
+ available as `neaPalette.darkNeutral`. `neaPaletteDarkTokenMapping` contains
94
+ the dark counterpart for every raw palette token.
95
+
96
+ For badges, status chips, and other token-driven UI, use `semanticColorTokens`.
97
+ Each semantic color token carries its direct dark mapping:
98
+
99
+ ```ts
100
+ const statusSuccess = semanticColorTokens.find(
101
+ (token) => token.token === "--status-success",
102
+ );
103
+
104
+ statusSuccess?.token; // "--status-success"
105
+ statusSuccess?.foregroundToken; // "--status-success-foreground"
106
+ statusSuccess?.darkValue; // "148 20% 18%"
107
+ statusSuccess?.darkForegroundValue; // "148 28% 62%"
108
+ ```
109
+
110
+ Raw palette entries also carry direct dark mappings:
111
+
112
+ ```ts
113
+ const greenBadge = neaPalette.green.find(
114
+ (token) => token.token === "--nea-green-100",
115
+ );
116
+
117
+ greenBadge?.token; // "--nea-green-100"
118
+ greenBadge?.darkValue; // "145 100% 21%"
119
+ neaPaletteDarkTokenMapping["--nea-green-100"]; // "145 100% 21%"
120
+ ```
121
+
122
+ ```ts
123
+ const neaDarkNeutralTokenMapping = {
124
+ "--nea-dark-neutral-50": "214 18% 92%",
125
+ "--nea-dark-neutral-100": "214 18% 90%",
126
+ "--nea-dark-neutral-200": "215 14% 68%",
127
+ "--nea-dark-neutral-300": "220 10% 16%",
128
+ "--nea-dark-neutral-400": "220 12% 13.5%",
129
+ "--nea-dark-neutral-500": "220 14% 11.5%",
130
+ "--nea-dark-neutral-600": "220 16% 9.5%",
131
+ "--nea-dark-neutral-700": "220 18% 8%",
132
+ "--nea-dark-neutral-800": "220 22% 6.5%",
133
+ } as const;
134
+ ```
135
+
136
+ ```ts
137
+ const darkThemeTokenMapping = {
138
+ "--background": "var(--nea-dark-neutral-300)",
139
+ "--foreground": "var(--nea-dark-neutral-50)",
140
+ "--card": "var(--nea-dark-neutral-300)",
141
+ "--card-foreground": "var(--nea-dark-neutral-50)",
142
+ "--card-border": "220 10% 28%",
143
+ "--popover": "var(--nea-dark-neutral-300)",
144
+ "--popover-foreground": "var(--nea-dark-neutral-50)",
145
+ "--tooltip-background": "var(--nea-dark-neutral-800)",
146
+ "--tooltip-foreground": "var(--nea-dark-neutral-50)",
147
+ "--primary": "148 30% 38%",
148
+ "--primary-foreground": "var(--nea-dark-neutral-50)",
149
+ "--secondary": "var(--nea-dark-neutral-500)",
150
+ "--secondary-foreground": "var(--nea-dark-neutral-100)",
151
+ "--muted": "var(--nea-dark-neutral-500)",
152
+ "--muted-foreground": "var(--nea-dark-neutral-200)",
153
+ "--accent": "var(--nea-dark-neutral-500)",
154
+ "--accent-foreground": "var(--nea-dark-neutral-50)",
155
+ "--destructive": "0 72% 58%",
156
+ "--destructive-foreground": "var(--nea-dark-neutral-50)",
157
+ "--status-success": "148 20% 18%",
158
+ "--status-success-foreground": "148 28% 62%",
159
+ "--status-warning": "38 22% 18%",
160
+ "--status-warning-foreground": "42 42% 64%",
161
+ "--status-info": "214 22% 19%",
162
+ "--status-info-foreground": "214 36% 66%",
163
+ "--status-accent": "263 18% 20%",
164
+ "--status-accent-foreground": "255 34% 68%",
165
+ "--status-destructive": "0 20% 19%",
166
+ "--status-destructive-foreground": "0 42% 66%",
167
+ "--tab-list-background": "var(--table-header-background)",
168
+ "--tab-trigger-foreground": "var(--nea-dark-neutral-200)",
169
+ "--tab-trigger-hover-background": "var(--nea-dark-neutral-400)",
170
+ "--tab-trigger-active-background": "var(--nea-dark-neutral-300)",
171
+ "--tab-trigger-active-foreground": "var(--nea-dark-neutral-50)",
172
+ "--tab-indicator": "var(--primary)",
173
+ "--card-gradient-from": "var(--nea-green-700)",
174
+ "--card-gradient-to": "var(--nea-green-500)",
175
+ "--card-gradient-border": "var(--nea-green-500)",
176
+ "--card-gradient-foreground": "var(--nea-neutral-0)",
177
+ "--layout-chrome-background": "var(--nea-dark-neutral-500)",
178
+ "--layout-shell-background": "var(--layout-chrome-background)",
179
+ "--layout-sidebar-background": "var(--layout-chrome-background)",
180
+ "--layout-sidebar-logo-background": "hsl(var(--nea-neutral-0))",
181
+ "--layout-sidebar-border": "var(--card-border)",
182
+ "--layout-surface-background": "var(--nea-dark-neutral-400)",
183
+ "--layout-workspace-header-background": "var(--nea-dark-neutral-300)",
184
+ "--layout-surface-border": "var(--card-border)",
185
+ "--layout-surface-shadow":
186
+ "0 24px 54px -34px rgb(0 0 0 / 0.64), 0 2px 10px rgb(0 0 0 / 0.26)",
187
+ "--overlay-shadow":
188
+ "0 18px 36px -22px rgb(0 0 0 / 0.58), 0 8px 18px -14px rgb(0 0 0 / 0.34)",
189
+ "--overlay-shadow-strong":
190
+ "0 10px 42px -24px rgb(0 0 0 / 0.68), 0 4px 16px -12px rgb(0 0 0 / 0.42)",
191
+ "--layout-right-panel-background": "var(--layout-surface-background)",
192
+ "--layout-right-panel-border": "220 10% 32%",
193
+ "--layout-right-panel-shadow":
194
+ "-18px 0 36px -28px rgb(0 0 0 / 0.58), -2px 0 12px -8px rgb(0 0 0 / 0.46)",
195
+ "--table-header-background": "var(--nea-dark-neutral-500)",
196
+ "--table-row-striped-background": "var(--nea-dark-neutral-500)",
197
+ "--table-row-warning-background": "var(--nea-dark-neutral-300)",
198
+ "--border": "var(--card-border)",
199
+ "--input": "var(--card-border)",
200
+ "--ring": "148 30% 38%",
201
+ "--component-border": "var(--card-border)",
202
+ "--button-outline-background": "var(--nea-dark-neutral-500)",
203
+ "--button-outline-hover-background": "var(--nea-dark-neutral-400)",
204
+ "--button-outline-border": "var(--component-border)",
205
+ "--tab-list-border": "var(--component-border)",
206
+ "--table-border": "var(--component-border)",
207
+ } as const;
208
+ ```
209
+
210
+ The default root font size is 14px. If you want to change the package base font
211
+ size, override `--font-size-root` after importing the stylesheet:
70
212
 
71
213
  ```css
72
214
  :root {
@@ -88,7 +230,7 @@ const inter = Inter({
88
230
 
89
231
  Use package components before creating app-local UI copies. If a reusable
90
232
  pattern is missing, extract it into the design system instead of duplicating it
91
- locally. This design system is light-theme only.
233
+ locally.
92
234
 
93
235
  ## AI Usage
94
236
 
@@ -135,8 +277,8 @@ Use @neasg/design-system as the source of truth for shared UI.
135
277
  import { Button, Input, Card, Table, Typography } from "@neasg/design-system";
136
278
  - Use the documented props-driven API. Do not build custom compositions from internal primitives.
137
279
  - Use the package theme tokens and Tailwind preset instead of inventing ad hoc colors, spacing, or radius values.
280
+ - Theme switching is driven by the `dark` class. Use the exported `darkThemeTokenMapping`, `semanticColorTokens`, and `neaPaletteDarkTokenMapping` instead of inventing app-local dark-mode colors.
138
281
  - Use Typography for headings, body copy, labels, and captions.
139
- - This design system is light-theme only. Do not generate dark-mode styles, tokens, or examples.
140
282
  - If a needed shared pattern is missing, extract it into @neasg/design-system rather than duplicating it locally.
141
283
  ```
142
284
 
@@ -0,0 +1,34 @@
1
+ import * as React from "react";
2
+ import { type AvatarProfileProps } from "./avatar";
3
+ import { type ButtonProps } from "./button";
4
+ import { type PopoverProps } from "./popover";
5
+ export interface AvatarProfilePopoverProps extends Omit<PopoverProps, "trigger" | "children" | "surface" | "contentClassName"> {
6
+ trigger: React.ReactElement;
7
+ children: React.ReactNode;
8
+ contentClassName?: string;
9
+ }
10
+ declare function AvatarProfilePopover({ trigger, children, contentClassName, align, sideOffset, ...props }: AvatarProfilePopoverProps): import("react/jsx-runtime").JSX.Element;
11
+ export interface AvatarProfilePopoverContentProps extends React.HTMLAttributes<HTMLDivElement> {
12
+ }
13
+ declare function AvatarProfilePopoverContent({ className, ...props }: AvatarProfilePopoverContentProps): import("react/jsx-runtime").JSX.Element;
14
+ export interface AvatarProfilePopoverHeaderProps extends Omit<AvatarProfileProps, "className"> {
15
+ className?: string;
16
+ profileClassName?: string;
17
+ }
18
+ declare function AvatarProfilePopoverHeader({ className, profileClassName, size, avatarClassName, nameClassName, roleClassName, ...props }: AvatarProfilePopoverHeaderProps): import("react/jsx-runtime").JSX.Element;
19
+ export interface AvatarProfilePopoverSectionProps extends React.HTMLAttributes<HTMLElement> {
20
+ label: React.ReactNode;
21
+ description?: React.ReactNode;
22
+ contentClassName?: string;
23
+ labelClassName?: string;
24
+ descriptionClassName?: string;
25
+ }
26
+ declare function AvatarProfilePopoverSection({ className, label, description, children, contentClassName, labelClassName, descriptionClassName, ...props }: AvatarProfilePopoverSectionProps): import("react/jsx-runtime").JSX.Element;
27
+ export interface AvatarProfilePopoverFooterProps extends React.HTMLAttributes<HTMLDivElement> {
28
+ }
29
+ declare function AvatarProfilePopoverFooter({ className, ...props }: AvatarProfilePopoverFooterProps): import("react/jsx-runtime").JSX.Element;
30
+ export interface AvatarProfilePopoverFooterActionProps extends Omit<ButtonProps, "variant" | "size"> {
31
+ tone?: "default" | "destructive";
32
+ }
33
+ declare function AvatarProfilePopoverFooterAction({ className, tone, type, ...props }: AvatarProfilePopoverFooterActionProps): import("react/jsx-runtime").JSX.Element;
34
+ export { AvatarProfilePopover, AvatarProfilePopoverContent, AvatarProfilePopoverHeader, AvatarProfilePopoverSection, AvatarProfilePopoverFooter, AvatarProfilePopoverFooterAction, };
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { AvatarProfile } from "./avatar";
4
+ import { Button } from "./button";
5
+ import { cn } from "./lib/utils";
6
+ import { Popover } from "./popover";
7
+ import { Typography } from "./typography";
8
+ function AvatarProfilePopover({ trigger, children, contentClassName, align = "start", sideOffset = 4, ...props }) {
9
+ return (_jsx(Popover, { ...props, trigger: trigger, surface: "menu", align: align, sideOffset: sideOffset, contentClassName: cn("max-h-[calc(100vh-1rem)] w-80 max-w-[calc(100vw-1rem)] overflow-visible", contentClassName), children: children }));
10
+ }
11
+ function AvatarProfilePopoverContent({ className, ...props }) {
12
+ return (_jsx("div", { "data-slot": "avatar-profile-popover-content", className: cn("min-w-0 bg-popover text-popover-foreground", className), ...props }));
13
+ }
14
+ function AvatarProfilePopoverHeader({ className, profileClassName, size = "sm", avatarClassName, nameClassName, roleClassName, ...props }) {
15
+ return (_jsx("div", { "data-slot": "avatar-profile-popover-header", className: cn("-mx-1 -mt-1 border-b px-3 py-2.5", className), children: _jsx(AvatarProfile, { ...props, size: size, className: profileClassName, avatarClassName: cn("h-9 w-9", avatarClassName), nameClassName: cn("text-sm leading-5", nameClassName), roleClassName: cn("text-xs leading-4", roleClassName) }) }));
16
+ }
17
+ function AvatarProfilePopoverSection({ className, label, description, children, contentClassName, labelClassName, descriptionClassName, ...props }) {
18
+ return (_jsxs("section", { "data-slot": "avatar-profile-popover-section", className: cn("space-y-1.5 px-2 py-2", className), ...props, children: [_jsx(Typography, { as: "h3", variant: "label", className: labelClassName, children: label }), _jsx("div", { className: cn("min-w-0", contentClassName), children: children }), description ? (_jsx(Typography, { as: "p", variant: "bodySm", color: "muted", className: descriptionClassName, children: description })) : null] }));
19
+ }
20
+ function AvatarProfilePopoverFooter({ className, ...props }) {
21
+ return (_jsx("div", { "data-slot": "avatar-profile-popover-footer", className: cn("-mx-1 -mb-1 border-t p-1.5", className), ...props }));
22
+ }
23
+ function AvatarProfilePopoverFooterAction({ className, tone = "default", type = "button", ...props }) {
24
+ return (_jsx(Button, { type: type, variant: "ghost", size: "sm", className: cn("h-auto min-h-8 w-full justify-start px-2 py-1.5 text-left whitespace-normal focus-visible:ring-inset focus-visible:ring-1 focus-visible:ring-offset-0", tone === "destructive" &&
25
+ "text-destructive hover:bg-destructive/10 hover:text-destructive", className), ...props }));
26
+ }
27
+ export { AvatarProfilePopover, AvatarProfilePopoverContent, AvatarProfilePopoverHeader, AvatarProfilePopoverSection, AvatarProfilePopoverFooter, AvatarProfilePopoverFooterAction, };
package/dist/badge.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const badgeVariants: (props?: ({
4
- variant?: "accent" | "muted" | "outline" | "destructive" | "secondary" | "info" | "success" | "warning" | null | undefined;
4
+ variant?: "accent" | "muted" | "destructive" | "outline" | "secondary" | "info" | "success" | "warning" | null | undefined;
5
5
  size?: "default" | "icon" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  /**
package/dist/button.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const buttonVariants: (props?: ({
4
- variant?: "link" | "default" | "outline" | "destructive" | "secondary" | "ghost" | null | undefined;
4
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
5
5
  size?: "default" | "sm" | "compact" | "icon" | "compactIcon" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
package/dist/card.d.ts CHANGED
@@ -11,6 +11,8 @@ export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "t
11
11
  footer?: React.ReactNode;
12
12
  active?: boolean;
13
13
  accent?: "primary";
14
+ /** Optional visual affordance shown in the top-right corner, such as a filter icon. */
15
+ icon?: React.ReactNode;
14
16
  /** Replaces title/stat/subMetric with skeleton placeholders while data is loading. */
15
17
  loading?: boolean;
16
18
  }
package/dist/card.js CHANGED
@@ -22,9 +22,45 @@ const cardSplitGridColumns = {
22
22
  lg: "xl:grid-cols-[minmax(0,1fr)_22rem]",
23
23
  },
24
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) => {
25
+ function mergeRefs(...refs) {
26
+ return (value) => {
27
+ refs.forEach((ref) => {
28
+ if (!ref)
29
+ return;
30
+ if (typeof ref === "function") {
31
+ ref(value);
32
+ return;
33
+ }
34
+ ref.current = value;
35
+ });
36
+ };
37
+ }
38
+ function isAnimatedIconElement(node) {
39
+ var _a, _b;
40
+ if (typeof node.type === "string" || node.type === React.Fragment) {
41
+ return false;
42
+ }
43
+ const component = node.type;
44
+ const componentName = (_b = (_a = component.displayName) !== null && _a !== void 0 ? _a : component.name) !== null && _b !== void 0 ? _b : "";
45
+ return componentName.endsWith("Icon");
46
+ }
47
+ function attachCardIconRef(node, ref) {
48
+ if (!React.isValidElement(node) || !isAnimatedIconElement(node)) {
49
+ return node;
50
+ }
51
+ const refCallback = (value) => {
52
+ ref.current = value;
53
+ };
54
+ return React.cloneElement(node, {
55
+ ref: mergeRefs(node.ref, refCallback),
56
+ });
57
+ }
58
+ const Card = React.forwardRef(({ title, description, variant = "default", size = "default", stat, subMetric, footer, active, accent, icon, loading = false, children, className, onClick, onKeyDown, onMouseEnter, onMouseLeave, onFocus, onBlur, role, tabIndex, "aria-pressed": ariaPressed, ...props }, ref) => {
26
59
  const interactive = Boolean(onClick);
27
60
  const hasStat = stat !== undefined;
61
+ const iconRef = React.useRef(null);
62
+ const showIcon = icon != null;
63
+ const renderedIcon = showIcon ? attachCardIconRef(icon, iconRef) : null;
28
64
  const handleKeyDown = (event) => {
29
65
  onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
30
66
  if (event.defaultPrevented ||
@@ -36,12 +72,45 @@ const Card = React.forwardRef(({ title, description, variant = "default", size =
36
72
  event.preventDefault();
37
73
  onClick(event);
38
74
  };
75
+ const startIconAnimation = () => {
76
+ var _a, _b;
77
+ (_b = (_a = iconRef.current) === null || _a === void 0 ? void 0 : _a.startAnimation) === null || _b === void 0 ? void 0 : _b.call(_a);
78
+ };
79
+ const stopIconAnimation = () => {
80
+ var _a, _b;
81
+ (_b = (_a = iconRef.current) === null || _a === void 0 ? void 0 : _a.stopAnimation) === null || _b === void 0 ? void 0 : _b.call(_a);
82
+ };
83
+ const handleMouseEnter = (event) => {
84
+ if (showIcon) {
85
+ startIconAnimation();
86
+ }
87
+ onMouseEnter === null || onMouseEnter === void 0 ? void 0 : onMouseEnter(event);
88
+ };
89
+ const handleMouseLeave = (event) => {
90
+ if (showIcon) {
91
+ stopIconAnimation();
92
+ }
93
+ onMouseLeave === null || onMouseLeave === void 0 ? void 0 : onMouseLeave(event);
94
+ };
95
+ const handleFocus = (event) => {
96
+ if (showIcon) {
97
+ startIconAnimation();
98
+ }
99
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
100
+ };
101
+ const handleBlur = (event) => {
102
+ if (showIcon) {
103
+ stopIconAnimation();
104
+ }
105
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
106
+ };
39
107
  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 }));
40
108
  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 }));
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" &&
109
+ return (_jsxs("div", { ref: ref, onClick: onClick, onKeyDown: handleKeyDown, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleFocus, onBlur: handleBlur, role: role !== null && role !== void 0 ? role : (interactive ? "button" : undefined), tabIndex: tabIndex !== null && tabIndex !== void 0 ? tabIndex : (interactive ? 0 : undefined), "aria-pressed": ariaPressed !== null && ariaPressed !== void 0 ? ariaPressed : (interactive && active !== undefined ? Boolean(active) : undefined), className: cn("group/card rounded-surface border border-[hsl(var(--card-border))] text-card-foreground", variant === "default" && "bg-card", variant === "elevated" && "bg-card shadow-sm", variant === "gradient" &&
42
110
  "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
111
  "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
112
+ "cursor-pointer transition-[border-color,background-color,box-shadow,transform] duration-150 ease-out hover:border-primary/50 hover:bg-primary/5 hover:shadow-md motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0 motion-safe:active:scale-[0.99] 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("relative flex flex-col gap-2", size === "default" && "p-4", size === "compact" && "p-3", showIcon && size === "default" && "pr-12", showIcon && size === "compact" && "pr-10"), children: [showIcon ? (_jsx("span", { "aria-hidden": "true", "data-slot": "card-icon", className: cn("pointer-events-none absolute right-3 top-3 inline-flex size-7 items-center justify-center rounded-control border border-[hsl(var(--card-border))] bg-background/80 text-muted-foreground shadow-sm transition-[background-color,color,transform] duration-150 ease-out group-hover/card:scale-105 group-hover/card:bg-primary/10 group-hover/card:text-primary group-focus-visible/card:bg-primary/10 group-focus-visible/card:text-primary motion-reduce:transition-colors [&_svg]:size-3.5", size === "compact" && "right-2.5 top-2.5 size-6 [&_svg]:size-3", active && "border-primary/30 bg-primary/10 text-primary", variant === "gradient" &&
113
+ "border-white/25 bg-white/10 text-current group-hover/card:bg-white/20 group-hover/card:text-current group-focus-visible/card:bg-white/20 group-focus-visible/card:text-current"), children: renderedIcon })) : null, loading ? (_jsx(Skeleton, { className: "h-3.5 w-24" })) : (renderedTitle), loading
45
114
  ? description != null && _jsx(Skeleton, { className: "h-5 w-40" })
46
115
  : 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] }));
47
116
  });
@@ -1,26 +1,62 @@
1
1
  import * as React from "react";
2
+ import { type BadgeProps } from "./badge";
2
3
  export interface CommandSearchFilter {
3
4
  value: string;
4
5
  label: string;
6
+ count?: number;
5
7
  selected?: boolean;
6
- onSelect?: () => void;
8
+ onSelect?: (details: CommandSearchFilterSelectDetails) => void;
9
+ }
10
+ export interface CommandSearchFilterSelectDetails {
11
+ value: string;
12
+ selected: boolean;
13
+ allowMultiSelect: boolean;
14
+ }
15
+ export type CommandSearchIcon = React.ComponentType<{
16
+ className?: string;
17
+ }>;
18
+ export type CommandSearchItemTone = "accent" | "destructive" | "info" | "muted" | "success" | "warning";
19
+ export interface CommandSearchItemDetail {
20
+ label: string;
21
+ value: string;
22
+ /** Groups related metadata badges together while preserving the default Badge styling. */
23
+ group?: string;
24
+ variant?: BadgeProps["variant"];
25
+ bordered?: boolean;
26
+ showLabel?: boolean;
7
27
  }
8
28
  export interface CommandSearchItem {
9
29
  id: string;
10
30
  title: string;
11
31
  subtitle?: string;
32
+ description?: string;
33
+ details?: CommandSearchItemDetail[];
34
+ icon?: CommandSearchIcon;
35
+ tone?: CommandSearchItemTone;
36
+ affordance?: React.ReactNode;
37
+ affordanceLabel?: string;
12
38
  value?: string;
13
39
  keywords?: string[];
14
40
  disabled?: boolean;
15
41
  closeOnSelect?: boolean;
16
42
  onSelect?: () => void;
17
43
  }
44
+ export interface CommandSearchSectionAction {
45
+ label: React.ReactNode;
46
+ value?: string;
47
+ affordance?: React.ReactNode;
48
+ affordanceLabel?: string;
49
+ closeOnSelect?: boolean;
50
+ onSelect?: () => void;
51
+ }
18
52
  export interface CommandSearchSection {
19
53
  key: string;
20
54
  heading: string;
21
55
  items: CommandSearchItem[];
56
+ action?: CommandSearchSectionAction;
22
57
  }
23
58
  export type CommandSearchShowAllResultsLabel = React.ReactNode | ((query: string, hasResults: boolean) => React.ReactNode);
59
+ export type CommandSearchShortcutPlatform = "mac" | "windows";
24
60
  export interface CommandSearchProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
25
61
  value: string;
26
62
  onValueChange: (value: string) => void;
@@ -35,14 +71,23 @@ export interface CommandSearchProps extends Omit<React.HTMLAttributes<HTMLDivEle
35
71
  inputPlaceholder?: string;
36
72
  /** The key that opens the dialog when pressed with Cmd/Ctrl (e.g. "k", "j"). */
37
73
  shortcutKey?: string;
74
+ /** Controls the default shortcut label shown on the trigger. */
75
+ shortcutPlatform?: CommandSearchShortcutPlatform;
38
76
  /** Override the label shown on the trigger. Defaults to "⌘{shortcutKey.toUpperCase()}". */
39
77
  shortcutLabel?: string;
40
78
  allFilter?: CommandSearchFilter;
41
79
  filters?: CommandSearchFilter[];
80
+ subFilters?: CommandSearchFilter[];
81
+ /** Whether multiple filters can be selected at once. Selection state remains controlled by the passed filters. */
82
+ allowMultiSelect?: boolean;
42
83
  /** Shows command-list skeleton rows while results are being fetched. */
43
84
  loading?: boolean;
44
85
  /** Number of skeleton rows shown while `loading` is true. */
45
86
  loadingRows?: number;
87
+ /** Minimum height for the results pane to keep the dialog from resizing between states. */
88
+ resultsMinHeight?: React.CSSProperties["minHeight"];
89
+ /** Maximum height for the scrollable results pane. */
90
+ resultsMaxHeight?: React.CSSProperties["maxHeight"];
46
91
  /**
47
92
  * Renders a footer action for routing to a full search results page.
48
93
  * The action is shown only when the query is not empty and results are not loading.
@@ -55,5 +100,5 @@ export interface CommandSearchProps extends Omit<React.HTMLAttributes<HTMLDivEle
55
100
  dialogClassName?: string;
56
101
  resultsClassName?: string;
57
102
  }
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;
103
+ declare function CommandSearch({ value, onValueChange, sections, children, className, isCollapsed, open, defaultOpen, onOpenChange, triggerVariant, triggerPlaceholder, inputPlaceholder, shortcutKey, shortcutPlatform, shortcutLabel, allFilter, filters, subFilters, allowMultiSelect, loading, loadingRows, resultsMinHeight, resultsMaxHeight, onShowAllResults, showAllResultsLabel, triggerClassName, dialogClassName, resultsClassName, ...props }: CommandSearchProps): import("react/jsx-runtime").JSX.Element;
59
104
  export { CommandSearch };