@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
@@ -1,10 +1,14 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { ArrowRight, ArrowUpRight, SlidersHorizontal } from "lucide-react";
5
+ import { Badge } from "./badge";
4
6
  import { Button } from "./button";
5
7
  import { Command, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "./command";
6
8
  import { EmptyState } from "./empty-state";
7
9
  import { SearchIcon } from "./animated-icons/search";
10
+ import { XIcon } from "./animated-icons/x";
11
+ import { CountBadge } from "./count-badge";
8
12
  import { DialogRoot, DialogSurface, DialogTitle } from "./dialog-primitive";
9
13
  import { FilterToggle } from "./filter-toggle";
10
14
  import { cn } from "./lib/utils";
@@ -15,15 +19,128 @@ import { SearchHighlight } from "./use-search-highlight";
15
19
  function CommandSearchEmptyIcon({ className }) {
16
20
  return _jsx(SearchIcon, { className: className, size: 20 });
17
21
  }
22
+ const commandSearchItemToneClasses = {
23
+ accent: "border-status-accent-foreground/20 bg-status-accent text-status-accent-foreground",
24
+ destructive: "border-status-destructive-foreground/20 bg-status-destructive text-status-destructive-foreground",
25
+ info: "border-status-info-foreground/20 bg-status-info text-status-info-foreground",
26
+ muted: "border-border bg-muted text-muted-foreground",
27
+ success: "border-status-success-foreground/20 bg-status-success text-status-success-foreground",
28
+ warning: "border-status-warning-foreground/20 bg-status-warning text-status-warning-foreground",
29
+ };
18
30
  function getCommandSearchItemValue(item) {
19
- var _a, _b;
20
- const fallbackValue = [item.title, item.subtitle, ...((_a = item.keywords) !== null && _a !== void 0 ? _a : [])]
31
+ var _a, _b, _c, _d;
32
+ const detailValues = (_b = (_a = item.details) === null || _a === void 0 ? void 0 : _a.flatMap((detail) => [detail.label, detail.value])) !== null && _b !== void 0 ? _b : [];
33
+ const fallbackValue = [
34
+ item.title,
35
+ item.subtitle,
36
+ item.description,
37
+ ...detailValues,
38
+ ...((_c = item.keywords) !== null && _c !== void 0 ? _c : []),
39
+ ]
21
40
  .filter(Boolean)
22
41
  .join(" ");
23
- return (_b = item.value) !== null && _b !== void 0 ? _b : fallbackValue;
42
+ return (_d = item.value) !== null && _d !== void 0 ? _d : fallbackValue;
43
+ }
44
+ function getCommandSearchSectionActionValue(section) {
45
+ if (!section.action) {
46
+ return section.heading;
47
+ }
48
+ if (section.action.value) {
49
+ return section.action.value;
50
+ }
51
+ const label = typeof section.action.label === "string"
52
+ ? section.action.label
53
+ : "Show all results";
54
+ return `${section.heading} ${label}`;
55
+ }
56
+ function handleCommandSearchItemKeyDown(event, onSelect) {
57
+ if (event.key !== "Enter" && event.key !== " ") {
58
+ return;
59
+ }
60
+ event.preventDefault();
61
+ event.stopPropagation();
62
+ onSelect();
63
+ }
64
+ function isCommandSearchTitleDetail(detail) {
65
+ return detail.showLabel === false && Boolean(detail.variant);
66
+ }
67
+ function getNormalizedCommandSearchDetailLabel(detail) {
68
+ return detail.label.toLowerCase().replace(/\.$/, "");
69
+ }
70
+ function isCommandSearchCompanyDetail(detail) {
71
+ const label = getNormalizedCommandSearchDetailLabel(detail);
72
+ return (label === "company name" ||
73
+ label === "uen" ||
74
+ label === "postal code");
75
+ }
76
+ const commandSearchDetailGroupPriority = {
77
+ identity: 0,
78
+ summary: 1,
79
+ };
80
+ function getCommandSearchDetailGroup(detail) {
81
+ if (detail.group)
82
+ return detail.group;
83
+ const label = getNormalizedCommandSearchDetailLabel(detail);
84
+ if (label === "company name" ||
85
+ label === "case number" ||
86
+ label === "licence no" ||
87
+ label === "inspection no" ||
88
+ label === "uen" ||
89
+ label === "postal code") {
90
+ return "identity";
91
+ }
92
+ if (label === "active licences" || label === "in progress cases") {
93
+ return "summary";
94
+ }
95
+ return "metadata";
96
+ }
97
+ function getGroupedCommandSearchDetails(details) {
98
+ const groups = new Map();
99
+ details.forEach((detail, index) => {
100
+ const key = getCommandSearchDetailGroup(detail);
101
+ const group = groups.get(key);
102
+ if (group) {
103
+ group.details.push(detail);
104
+ return;
105
+ }
106
+ groups.set(key, { key, details: [detail], order: index });
107
+ });
108
+ return Array.from(groups.values()).sort((a, b) => {
109
+ var _a, _b;
110
+ const priorityA = (_a = commandSearchDetailGroupPriority[a.key]) !== null && _a !== void 0 ? _a : 2;
111
+ const priorityB = (_b = commandSearchDetailGroupPriority[b.key]) !== null && _b !== void 0 ? _b : 2;
112
+ return priorityA - priorityB || a.order - b.order;
113
+ });
114
+ }
115
+ function CommandSearchDetailBadge({ detail, query, className, }) {
116
+ var _a;
117
+ return (_jsxs(Badge, { variant: (_a = detail.variant) !== null && _a !== void 0 ? _a : "muted", bordered: detail.bordered, "data-slot": "command-search-detail", className: cn("max-w-full justify-start text-left", className), title: detail.showLabel === false
118
+ ? detail.value
119
+ : `${detail.label}: ${detail.value}`, children: [detail.showLabel === false ? null : (_jsx("span", { className: "shrink-0", children: detail.label })), _jsx("span", { className: cn("min-w-0 truncate", !detail.variant && "text-foreground"), children: _jsx(SearchHighlight, { text: detail.value, query: query }) })] }));
120
+ }
121
+ function CommandSearchResultItem({ item, query, }) {
122
+ var _a, _b, _c, _d;
123
+ const Icon = item.icon;
124
+ const tone = (_a = item.tone) !== null && _a !== void 0 ? _a : "muted";
125
+ const details = (_b = item.details) !== null && _b !== void 0 ? _b : [];
126
+ const titleDetails = details.filter(isCommandSearchTitleDetail);
127
+ const metadataDetails = details.filter((detail) => !isCommandSearchTitleDetail(detail));
128
+ const companyDetails = metadataDetails.filter(isCommandSearchCompanyDetail);
129
+ const recordDetails = metadataDetails.filter((detail) => !isCommandSearchCompanyDetail(detail));
130
+ const recordDetailGroups = getGroupedCommandSearchDetails(recordDetails);
131
+ const hasDetails = metadataDetails.length > 0;
132
+ const hasRecordDetails = recordDetails.length > 0;
133
+ const hasCompanyDetails = companyDetails.length > 0;
134
+ return (_jsxs(_Fragment, { children: [Icon ? (_jsx("span", { className: cn("mt-[calc(var(--space-1)/2)] flex size-[var(--space-8)] shrink-0 items-center justify-center rounded-control border", commandSearchItemToneClasses[tone]), "aria-hidden": "true", children: _jsx(Icon, { className: "h-3.5 w-3.5" }) })) : null, _jsxs("span", { className: "min-w-0 flex-1 space-y-[var(--space-2)]", children: [_jsxs("span", { "data-slot": "command-search-title-line", className: "flex min-w-0 items-center gap-[var(--space-2)]", children: [_jsx(Typography, { as: "span", variant: "label", className: "min-w-0 truncate", children: _jsx(SearchHighlight, { text: item.title, query: query }) }), titleDetails.map((detail) => (_jsx(CommandSearchDetailBadge, { detail: detail, query: query, className: "shrink-0" }, `${detail.label}-${detail.value}`)))] }), !hasDetails && item.subtitle ? (_jsx(Typography, { as: "span", variant: "caption", className: "block truncate text-muted-foreground", children: _jsx(SearchHighlight, { text: item.subtitle, query: query }) })) : null, !hasDetails && item.description ? (_jsx(Typography, { as: "span", variant: "caption", className: "block line-clamp-2 text-muted-foreground", children: _jsx(SearchHighlight, { text: item.description, query: query }) })) : null, hasDetails ? (_jsxs("span", { "data-slot": "command-search-details-lines", className: "flex min-w-0 flex-col gap-[calc(var(--space-1)+0.125rem)] overflow-hidden", children: [hasRecordDetails ? (_jsx("span", { "data-slot": "command-search-record-details-line", className: "flex min-w-0 items-center gap-[var(--space-1)] overflow-hidden", children: recordDetailGroups.map((group) => (_jsx("span", { "data-slot": "command-search-detail-group", "data-group": group.key, className: "flex min-w-0 shrink items-center gap-[var(--space-1)]", children: group.details.map((detail) => (_jsx(CommandSearchDetailBadge, { detail: detail, query: query, className: "min-w-0 shrink" }, `${detail.label}-${detail.value}`))) }, group.key))) })) : null, hasCompanyDetails ? (_jsx("span", { "data-slot": "command-search-company-details-line", "data-group": "company", className: "flex min-w-0 items-center gap-[var(--space-1)] overflow-hidden", children: companyDetails.map((detail) => (_jsx(CommandSearchDetailBadge, { detail: detail, query: query, className: "min-w-0 shrink" }, `${detail.label}-${detail.value}`))) })) : null] })) : null] }), _jsxs("span", { "data-slot": "command-search-result-affordance", className: "ml-auto flex size-[var(--space-8)] shrink-0 self-center items-center justify-center rounded-control text-muted-foreground opacity-0 transition-opacity group-data-[selected=true]:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100", children: [(_c = item.affordance) !== null && _c !== void 0 ? _c : (_jsx(ArrowUpRight, { className: "size-[var(--space-4)]", "aria-hidden": "true" })), _jsx("span", { className: "sr-only", children: (_d = item.affordanceLabel) !== null && _d !== void 0 ? _d : "Open result" })] })] }));
24
135
  }
25
136
  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))) }));
137
+ return (_jsx("div", { "data-slot": "command-search-skeleton", className: "space-y-[var(--space-1)] p-[var(--space-1)]", children: Array.from({ length: rows }).map((_, index) => (_jsxs("div", { "data-slot": "command-search-skeleton-row", className: "flex min-h-[var(--space-8)] items-start gap-[var(--space-2)] rounded-sm px-[var(--space-2)] py-[var(--space-3)]", children: [_jsx(Skeleton, { "data-slot": "command-search-skeleton-icon", className: "mt-[calc(var(--space-1)/2)] size-[var(--space-8)] shrink-0 rounded-control" }), _jsxs("div", { className: "min-w-0 flex-1 space-y-[var(--space-2)]", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-[var(--space-2)]", children: [_jsx(Skeleton, { className: "h-[var(--space-4)] w-36 max-w-[55%]" }), _jsx(Skeleton, { className: "h-[var(--space-5)] w-16 shrink-0 rounded-md" })] }), _jsx("div", { className: "flex min-w-0 items-center gap-[var(--space-1)] overflow-hidden", children: _jsx(Skeleton, { className: "h-[var(--space-5)] w-28 shrink rounded-md" }) }), _jsxs("div", { className: "flex min-w-0 items-center gap-[var(--space-1)] overflow-hidden", children: [_jsx(Skeleton, { className: "h-[var(--space-5)] w-40 max-w-[45%] shrink rounded-md" }), _jsx(Skeleton, { className: "h-[var(--space-5)] w-24 shrink rounded-md" }), _jsx(Skeleton, { className: "h-[var(--space-5)] w-24 shrink rounded-md" })] })] }), _jsx(Skeleton, { "data-slot": "command-search-skeleton-affordance", className: "ml-auto size-[var(--space-8)] shrink-0 self-center rounded-control opacity-60" })] }, index))) }));
138
+ }
139
+ function CommandSearchEmptyState({ query, resultsMinHeight, }) {
140
+ const normalizedQuery = query.trim();
141
+ return (_jsx(EmptyState, { "data-slot": "command-search-empty-state", icon: CommandSearchEmptyIcon, style: { minHeight: resultsMinHeight }, message: normalizedQuery ? "No results found" : "Start typing to search", description: normalizedQuery
142
+ ? `No results found for "${query}".`
143
+ : "Results will appear here as you type." }));
27
144
  }
28
145
  function useControllableOpen({ open, defaultOpen, onOpenChange, }) {
29
146
  const [internalOpen, setInternalOpen] = React.useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
@@ -37,7 +154,7 @@ function useControllableOpen({ open, defaultOpen, onOpenChange, }) {
37
154
  }, [isControlled, onOpenChange]);
38
155
  return [currentOpen, setOpen];
39
156
  }
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 }) {
157
+ function CommandSearch({ value, onValueChange, sections, children, className, isCollapsed = false, open, defaultOpen, onOpenChange, triggerVariant = "default", triggerPlaceholder = "Search...", inputPlaceholder = "Search...", shortcutKey = "k", shortcutPlatform = "mac", shortcutLabel, allFilter, filters = [], subFilters = [], allowMultiSelect = true, loading = false, loadingRows = 4, resultsMinHeight = 384, resultsMaxHeight = 560, onShowAllResults, showAllResultsLabel, triggerClassName, dialogClassName, resultsClassName, ...props }) {
41
158
  const [isOpen, setIsOpen] = useControllableOpen({
42
159
  open,
43
160
  defaultOpen,
@@ -53,7 +170,10 @@ function CommandSearch({ value, onValueChange, sections, children, className, is
53
170
  : showAllResultsLabel !== null && showAllResultsLabel !== void 0 ? showAllResultsLabel : (hasResults
54
171
  ? `Show all results for "${normalizedValue}"`
55
172
  : `Search all records for "${normalizedValue}"`);
56
- const resolvedLabel = shortcutLabel !== null && shortcutLabel !== void 0 ? shortcutLabel : `⌘${shortcutKey.toUpperCase()}`;
173
+ const shortcutKeyLabel = shortcutKey.toUpperCase();
174
+ const resolvedLabel = shortcutLabel !== null && shortcutLabel !== void 0 ? shortcutLabel : (shortcutPlatform === "windows"
175
+ ? `Ctrl ${shortcutKeyLabel}`
176
+ : `⌘${shortcutKeyLabel}`);
57
177
  React.useEffect(() => {
58
178
  const handleKeyDown = (event) => {
59
179
  if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === shortcutKey.toLowerCase()) {
@@ -72,29 +192,58 @@ function CommandSearch({ value, onValueChange, sections, children, className, is
72
192
  const preserveInputFocus = React.useCallback((event) => {
73
193
  event.preventDefault();
74
194
  }, []);
75
- const selectFilter = React.useCallback((onSelect) => {
76
- onSelect === null || onSelect === void 0 ? void 0 : onSelect();
195
+ const selectFilter = React.useCallback((filter) => {
196
+ var _a;
197
+ (_a = filter.onSelect) === null || _a === void 0 ? void 0 : _a.call(filter, {
198
+ value: filter.value,
199
+ selected: Boolean(filter.selected),
200
+ allowMultiSelect,
201
+ });
77
202
  window.setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
78
- }, []);
203
+ }, [allowMultiSelect]);
204
+ const clearSearch = React.useCallback(() => {
205
+ onValueChange("");
206
+ window.setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
207
+ }, [onValueChange]);
79
208
  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"
209
+ ? "mx-auto flex h-control min-h-control w-control min-w-control max-w-control shrink-0 gap-0 rounded-control border-none bg-transparent p-[var(--space-0)] !font-normal text-muted-foreground hover:bg-card hover:text-foreground focus-visible:bg-card focus-visible:ring-ring/30"
81
210
  : 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"
211
+ ? "h-control w-full min-w-[14rem] justify-start gap-[var(--space-2)] rounded-control border border-[hsl(var(--layout-sidebar-border))] bg-card px-[var(--space-2)] !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
212
  : 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: () => {
213
+ ? "h-control min-h-control w-control min-w-control max-w-control shrink-0 rounded-control p-[var(--space-0)] !font-normal text-muted-foreground hover:bg-card hover:text-foreground"
214
+ : "h-control w-full min-w-[14rem] justify-start gap-[var(--space-2)] rounded-control border border-border bg-card px-[var(--space-2)] !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-[var(--space-5)] items-center gap-[var(--space-1)] rounded border border-border bg-muted px-[var(--space-1)] 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-var(--space-8))] max-w-[60rem] gap-[var(--space-0)] overflow-hidden p-[var(--space-0)] sm:w-[min(72vw,60rem)]", 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, wrapperClassName: "border-b px-[var(--space-3)] py-[var(--space-2)]", iconClassName: "hidden", className: "h-control border-0 bg-transparent px-[var(--space-3)] py-[var(--space-0)] pr-[calc(var(--space-12)+var(--space-6))] text-sm shadow-none focus-visible:border-transparent focus-visible:ring-0", endAdornment: _jsxs(_Fragment, { children: [value ? (_jsx("button", { type: "button", "aria-label": "Clear search", onMouseDown: preserveInputFocus, onClick: clearSearch, className: "flex size-[var(--space-6)] items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: _jsx(XIcon, { size: 14, "aria-hidden": "true" }) })) : null, _jsx("kbd", { className: "inline-flex h-[var(--space-5)] items-center rounded border border-border bg-muted px-[var(--space-1)] font-mono text-[11px] leading-none text-muted-foreground", "aria-hidden": "true", children: "Esc" })] }) }), allFilter || filters.length ? (_jsxs("div", { role: "group", "aria-label": "Search filters", "data-multiselectable": allowMultiSelect ? "true" : "false", className: "flex flex-wrap items-center gap-[var(--space-1)] border-b px-[var(--space-3)] py-[var(--space-2)]", children: [allFilter ? (_jsxs(FilterToggle, { selected: allFilter.selected, "aria-pressed": Boolean(allFilter.selected), onMouseDown: preserveInputFocus, onClick: () => selectFilter(allFilter), children: [allFilter.label, typeof allFilter.count === "number" ? (_jsx(CountBadge, { count: allFilter.count, hideWhenZero: true, size: "sm", variant: allFilter.selected ? "default" : "muted" })) : null] })) : null, filters.map((filter) => (_jsxs(FilterToggle, { selected: filter.selected, "aria-pressed": Boolean(filter.selected), onMouseDown: preserveInputFocus, onClick: () => selectFilter(filter), children: [filter.label, typeof filter.count === "number" ? (_jsx(CountBadge, { count: filter.count, hideWhenZero: true, size: "sm", variant: filter.selected ? "default" : "muted" })) : null] }, filter.value)))] })) : null, subFilters.length ? (_jsxs("div", { role: "group", "aria-label": "Search sub-filters", "data-multiselectable": allowMultiSelect ? "true" : "false", className: "flex flex-wrap items-center gap-[var(--space-1)] border-b bg-muted/30 px-[var(--space-3)] py-[var(--space-2)]", children: [_jsx(SlidersHorizontal, { "data-slot": "command-search-sub-filter-icon", className: "mr-[var(--space-1)] h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": "true" }), subFilters.map((filter) => (_jsxs(FilterToggle, { selected: filter.selected, "aria-pressed": Boolean(filter.selected), onMouseDown: preserveInputFocus, onClick: () => selectFilter(filter), children: [filter.label, typeof filter.count === "number" ? (_jsx(CountBadge, { count: filter.count, hideWhenZero: true, size: "sm", variant: filter.selected ? "default" : "muted" })) : null] }, filter.value)))] })) : null, _jsx(CommandList, { "aria-busy": loading || undefined, className: cn("p-[var(--space-1)]", resultsClassName), style: {
215
+ minHeight: resultsMinHeight,
216
+ maxHeight: resultsMaxHeight,
217
+ }, children: loading ? (_jsx(CommandSearchSkeleton, { rows: loadingRows })) : children !== undefined ? children : sections ? (visibleSections.length ? (visibleSections.map((section, index) => {
218
+ var _a, _b;
219
+ return (_jsxs(React.Fragment, { children: [index > 0 ? (_jsx(CommandSeparator, { alwaysRender: true, className: "-mx-[var(--space-1)]" })) : null, _jsxs(CommandGroup, { heading: section.heading, children: [section.items.map((item) => (_jsx(CommandItem, { value: getCommandSearchItemValue(item), disabled: item.disabled, tabIndex: item.disabled ? -1 : 0, className: "group min-h-[var(--space-8)] w-full items-start gap-[var(--space-2)] px-[var(--space-2)] py-[var(--space-3)] text-left font-normal whitespace-normal focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring", onSelect: () => {
220
+ var _a;
221
+ (_a = item.onSelect) === null || _a === void 0 ? void 0 : _a.call(item);
222
+ if (item.closeOnSelect !== false) {
223
+ setIsOpen(false);
224
+ }
225
+ }, onKeyDown: (event) => handleCommandSearchItemKeyDown(event, () => {
226
+ var _a;
227
+ (_a = item.onSelect) === null || _a === void 0 ? void 0 : _a.call(item);
228
+ if (item.closeOnSelect !== false) {
229
+ setIsOpen(false);
230
+ }
231
+ }), children: _jsx(CommandSearchResultItem, { item: item, query: value }) }, item.id))), section.action ? (_jsx(CommandItem, { value: getCommandSearchSectionActionValue(section), tabIndex: 0, className: "group min-h-[var(--space-8)] w-full items-center px-[var(--space-0)] py-[var(--space-0)] text-left font-normal whitespace-normal text-muted-foreground focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring data-[selected=true]:bg-transparent data-[selected=true]:text-muted-foreground", onSelect: () => {
232
+ var _a, _b, _c;
233
+ (_b = (_a = section.action) === null || _a === void 0 ? void 0 : _a.onSelect) === null || _b === void 0 ? void 0 : _b.call(_a);
234
+ if (((_c = section.action) === null || _c === void 0 ? void 0 : _c.closeOnSelect) !== false) {
235
+ setIsOpen(false);
236
+ }
237
+ }, onKeyDown: (event) => handleCommandSearchItemKeyDown(event, () => {
238
+ var _a, _b, _c;
239
+ (_b = (_a = section.action) === null || _a === void 0 ? void 0 : _a.onSelect) === null || _b === void 0 ? void 0 : _b.call(_a);
240
+ if (((_c = section.action) === null || _c === void 0 ? void 0 : _c.closeOnSelect) !== false) {
241
+ setIsOpen(false);
242
+ }
243
+ }), children: _jsx(Button, { asChild: true, variant: "link", size: "compact", className: "w-full justify-start text-left font-medium group-data-[selected=true]:underline", children: _jsxs("span", { children: [_jsx("span", { className: "min-w-0 truncate", children: section.action.label }), _jsxs("span", { className: "inline-flex shrink-0 items-center justify-center text-primary", children: [(_a = section.action.affordance) !== null && _a !== void 0 ? _a : (_jsx(ArrowRight, { className: "size-[var(--space-4)]", "aria-hidden": "true" })), _jsx("span", { className: "sr-only", children: (_b = section.action.affordanceLabel) !== null && _b !== void 0 ? _b : "Show all results in group" })] })] }) }) }, `${section.key}-action`)) : null] })] }, section.key));
244
+ })) : (_jsx(CommandSearchEmptyState, { query: value, resultsMinHeight: resultsMinHeight }))) : (_jsx(CommandSearchEmptyState, { query: value, resultsMinHeight: resultsMinHeight })) }), shouldShowAllResults ? (_jsx(PopoverMenuFooter, { inset: "flush", children: _jsxs(PopoverMenuFooterAction, { className: "group", onClick: () => {
96
245
  onShowAllResults === null || onShowAllResults === void 0 ? void 0 : onShowAllResults(normalizedValue);
97
246
  setIsOpen(false);
98
- }, children: _jsx("span", { className: "truncate", children: resolvedShowAllResultsLabel }) }) })) : null] })] }) })] }));
247
+ }, children: [_jsx("span", { className: "min-w-0 truncate", children: resolvedShowAllResultsLabel }), _jsx(ArrowRight, { className: "size-[var(--space-4)] shrink-0 transition-transform duration-150 group-hover:translate-x-[var(--space-1)] group-focus-visible:translate-x-[var(--space-1)]", "aria-hidden": "true" })] }) })) : null] })] }) })] }));
99
248
  }
100
249
  export { CommandSearch };
package/dist/command.d.ts CHANGED
@@ -21,6 +21,8 @@ interface CommandInputProps extends React.ComponentPropsWithoutRef<typeof Comman
21
21
  wrapperClassName?: string;
22
22
  iconClassName?: string;
23
23
  iconSize?: number;
24
+ endAdornment?: React.ReactNode;
25
+ endAdornmentClassName?: string;
24
26
  }
25
27
  declare const CommandInput: React.ForwardRefExoticComponent<CommandInputProps & React.RefAttributes<HTMLInputElement>>;
26
28
  declare const CommandList: React.ForwardRefExoticComponent<Omit<{
package/dist/command.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Command as CommandPrimitive } from "cmdk";
5
5
  import { inputControlClassName } from "./input-control";
@@ -7,7 +7,7 @@ import { cn } from "./lib/utils";
7
7
  import { SearchInputShell } from "./search-input-shell";
8
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
- 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 }) }) })));
10
+ const CommandInput = React.forwardRef(({ className, wrapperClassName, iconClassName, iconSize = 16, endAdornment, endAdornmentClassName, ...props }, ref) => (_jsx("div", { "cmdk-input-wrapper": "", className: cn("border-b p-4", wrapperClassName), children: _jsxs(SearchInputShell, { iconClassName: iconClassName, iconSize: iconSize, children: [_jsx(CommandPrimitive.Input, { ref: ref, className: cn(inputControlClassName, "bg-background pl-9 pr-3", className), ...props }), endAdornment ? (_jsx("div", { "data-slot": "command-input-end-adornment", className: cn("absolute right-[var(--space-3)] top-1/2 flex -translate-y-1/2 items-center gap-[var(--space-1)]", endAdornmentClassName), children: endAdornment })) : null] }) })));
11
11
  CommandInput.displayName = CommandPrimitive.Input.displayName;
12
12
  const CommandList = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.List, { ref: ref, className: cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className), ...props })));
13
13
  CommandList.displayName = CommandPrimitive.List.displayName;
@@ -9,7 +9,7 @@ const DialogRoot = DialogPrimitive.Root;
9
9
  const DialogTrigger = DialogPrimitive.Trigger;
10
10
  const DialogPortal = DialogPrimitive.Portal;
11
11
  const DialogClose = DialogPrimitive.Close;
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
+ const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Overlay, { ref: ref, "data-slot": "dialog-overlay", 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 })));
13
13
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
14
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] })] })));
15
15
  DialogSurface.displayName = DialogPrimitive.Content.displayName;
@@ -1,11 +1,13 @@
1
1
  import * as React from "react";
2
2
  import { type BadgeProps } from "./badge";
3
+ import { type GuidanceTipProps } from "./guidance-tip";
3
4
  import { type TooltipProps } from "./tooltip";
4
5
  export interface DraggableTabBadge {
5
6
  label: React.ReactNode;
6
7
  variant?: BadgeProps["variant"];
7
8
  bordered?: BadgeProps["bordered"];
8
9
  }
10
+ export type DraggableTabGuidance = Omit<GuidanceTipProps, "trigger">;
9
11
  export interface DraggableTabItem {
10
12
  id: string;
11
13
  label: React.ReactNode;
@@ -17,6 +19,7 @@ export interface DraggableTabItem {
17
19
  tooltip?: React.ReactNode;
18
20
  tooltipContentClassName?: string;
19
21
  tooltipSide?: TooltipProps["side"];
22
+ guidance?: DraggableTabGuidance;
20
23
  onSelect?: () => void;
21
24
  onClose?: () => void;
22
25
  className?: string;
@@ -5,6 +5,7 @@ import { SortableContext, horizontalListSortingStrategy, sortableKeyboardCoordin
5
5
  import { CSS } from "@dnd-kit/utilities";
6
6
  import { XIcon } from "./animated-icons/x";
7
7
  import { Badge } from "./badge";
8
+ import { GuidanceTip } from "./guidance-tip";
8
9
  import { cn } from "./lib/utils";
9
10
  import { Tooltip } from "./tooltip";
10
11
  function TabButton({ item, dragHandleProps, }) {
@@ -26,6 +27,9 @@ function TabButton({ item, dragHandleProps, }) {
26
27
  event.stopPropagation();
27
28
  (_a = item.onClose) === null || _a === void 0 ? void 0 : _a.call(item);
28
29
  }, className: cn("cursor-pointer rounded p-1 text-muted-foreground hover:text-foreground", item.closeButtonClassName), "aria-label": "Close tab", children: _jsx(XIcon, { size: 12 }) })) : null] }));
30
+ if (item.guidance) {
31
+ return _jsx(GuidanceTip, { ...item.guidance, trigger: content });
32
+ }
29
33
  if (!item.tooltip) {
30
34
  return content;
31
35
  }
@@ -33,7 +33,7 @@ function setNestedValue(obj, path, value) {
33
33
  current[parts[parts.length - 1]] = value;
34
34
  return result;
35
35
  }
36
- function EditableTable({ value, onChange, columns, isEditing = false, className, emptyPlaceholder = "Enter text...", checkable = false, checkableHeader, activeField = "active", addRowLabel = "Add Row", renderRowActions, }) {
36
+ function EditableTable({ value, onChange, columns, isEditing = false, className, emptyPlaceholder = "Enter text", checkable = false, checkableHeader, activeField = "active", addRowLabel = "Add Row", renderRowActions, }) {
37
37
  const [editingCell, setEditingCell] = React.useState(null);
38
38
  const [editValue, setEditValue] = React.useState("");
39
39
  const inputRef = React.useRef(null);
@@ -130,7 +130,7 @@ function EditableTable({ value, onChange, columns, isEditing = false, className,
130
130
  const isEmptyCell = isEmptyCellValue(cellValue);
131
131
  const isEditingCell = (editingCell === null || editingCell === void 0 ? void 0 : editingCell.rowIndex) === rowIndex &&
132
132
  (editingCell === null || editingCell === void 0 ? void 0 : editingCell.colPath) === column.path;
133
- return (_jsx(TableCell, { className: cn("whitespace-normal", isEditing && "cursor-pointer hover:bg-muted/50"), width: column.width, minWidth: getColumnMinWidth(column), maxWidth: getColumnMaxWidth(column), onClick: () => handleCellClick(rowIndex, column.path), children: isEditingCell ? (_jsx("div", { className: "flex min-h-8 items-center", children: _jsx(InputControl, { ref: inputRef, value: editValue, placeholder: emptyPlaceholder, onChange: (event) => setEditValue(event.target.value), onClick: (event) => event.stopPropagation(), onBlur: commitEdit, onKeyDown: handleKeyDown, className: "h-8 w-full px-2 py-1 text-sm" }) })) : (_jsx("span", { className: cn("flex min-h-8 items-center whitespace-normal break-words", isEmptyCell
133
+ return (_jsx(TableCell, { className: cn("whitespace-normal", isEditing && "cursor-pointer hover:bg-muted/50"), width: column.width, minWidth: getColumnMinWidth(column), maxWidth: getColumnMaxWidth(column), onClick: () => handleCellClick(rowIndex, column.path), children: isEditingCell ? (_jsxs("div", { className: "relative min-h-8 min-w-0", children: [_jsx("span", { "aria-hidden": "true", className: "block min-h-8 whitespace-normal break-words opacity-0", style: { maxWidth: getColumnMaxWidth(column) }, children: editValue || emptyPlaceholder }), _jsx(InputControl, { ref: inputRef, value: editValue, placeholder: emptyPlaceholder, onChange: (event) => setEditValue(event.target.value), onClick: (event) => event.stopPropagation(), onBlur: commitEdit, onKeyDown: handleKeyDown, className: "absolute inset-x-0 top-0 h-8 w-full px-2 py-1 text-sm" })] })) : (_jsx("span", { className: cn("flex min-h-8 items-center whitespace-normal break-words", isEmptyCell
134
134
  ? "text-muted-foreground"
135
135
  : undefined), style: { maxWidth: getColumnMaxWidth(column) }, children: isEmptyCell && isEditing
136
136
  ? emptyPlaceholder
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import { type ButtonProps } from "./button";
3
+ import { type PopoverProps } from "./popover";
4
+ export interface GuidanceTipAction {
5
+ label?: React.ReactNode;
6
+ onClick?: ButtonProps["onClick"];
7
+ disabled?: boolean;
8
+ loading?: boolean;
9
+ loadingText?: React.ReactNode;
10
+ ariaLabel?: string;
11
+ }
12
+ export interface GuidanceTipProps extends Omit<PopoverProps, "children" | "contentClassName" | "surface" | "title"> {
13
+ title: React.ReactNode;
14
+ description: React.ReactNode;
15
+ step?: number;
16
+ totalSteps?: number;
17
+ stepLabel?: React.ReactNode;
18
+ previousAction?: GuidanceTipAction;
19
+ nextAction?: GuidanceTipAction;
20
+ dismissAction?: GuidanceTipAction;
21
+ highlightTrigger?: boolean;
22
+ focusOverlay?: boolean;
23
+ contentClassName?: string;
24
+ }
25
+ declare function GuidanceTip({ title, description, step, totalSteps, stepLabel, previousAction, nextAction, dismissAction, highlightTrigger, focusOverlay, contentClassName, trigger, open, defaultOpen, onOpenChange, align, side, sideOffset, onKeyDown, ...popoverProps }: GuidanceTipProps): import("react/jsx-runtime").JSX.Element;
26
+ export { GuidanceTip };
@@ -0,0 +1,162 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { ChevronLeft, ChevronRight } from "lucide-react";
6
+ import { Button } from "./button";
7
+ import { cn } from "./lib/utils";
8
+ import { Popover } from "./popover";
9
+ const triggerHighlightClassName = "rounded-control ring-[calc(var(--space-1)/2)] ring-[hsl(var(--ring))] ring-offset-background [--tw-ring-offset-width:var(--space-1)] shadow-[0_0_var(--space-10)_hsl(var(--ring)/0.55)]";
10
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
11
+ function setRef(ref, value) {
12
+ if (!ref) {
13
+ return;
14
+ }
15
+ if (typeof ref === "function") {
16
+ ref(value);
17
+ return;
18
+ }
19
+ ref.current = value;
20
+ }
21
+ function composeRefs(...refs) {
22
+ return (value) => {
23
+ refs.forEach((ref) => setRef(ref, value));
24
+ };
25
+ }
26
+ function getStepLabel({ stepLabel, step, totalSteps, }) {
27
+ if (stepLabel !== undefined) {
28
+ return stepLabel;
29
+ }
30
+ if (typeof step === "number" && typeof totalSteps === "number") {
31
+ return `Step ${step} of ${totalSteps}`;
32
+ }
33
+ if (typeof step === "number") {
34
+ return `Step ${step}`;
35
+ }
36
+ return null;
37
+ }
38
+ function isDoneActionLabel(label) {
39
+ return typeof label === "string" && label.trim().toLowerCase() === "done";
40
+ }
41
+ function isEditableKeyboardTarget(target) {
42
+ if (!(target instanceof HTMLElement)) {
43
+ return false;
44
+ }
45
+ return (target.isContentEditable ||
46
+ target.matches("input, select, textarea, [role='textbox']"));
47
+ }
48
+ function canClickActionButton(button) {
49
+ return !(!button ||
50
+ button.disabled ||
51
+ button.getAttribute("aria-disabled") === "true");
52
+ }
53
+ function getGuidanceTrigger({ trigger, highlightTrigger, triggerRef, }) {
54
+ if (!highlightTrigger || !React.isValidElement(trigger)) {
55
+ return trigger;
56
+ }
57
+ const triggerElement = trigger;
58
+ return React.cloneElement(triggerElement, {
59
+ "data-guidance-highlight": "true",
60
+ ref: composeRefs(triggerElement.ref, triggerRef),
61
+ });
62
+ }
63
+ function GuidanceTriggerHighlight({ enabled, focusOverlay, targetRef, }) {
64
+ var _a, _b;
65
+ const [rect, setRect] = React.useState(null);
66
+ const updateRect = React.useCallback(() => {
67
+ const element = targetRef.current;
68
+ setRect(element && enabled ? element.getBoundingClientRect() : null);
69
+ }, [enabled, targetRef]);
70
+ useIsomorphicLayoutEffect(() => {
71
+ var _a;
72
+ if (!enabled) {
73
+ setRect(null);
74
+ return undefined;
75
+ }
76
+ const element = targetRef.current;
77
+ if (!element) {
78
+ return undefined;
79
+ }
80
+ const ownerWindow = (_a = element.ownerDocument.defaultView) !== null && _a !== void 0 ? _a : window;
81
+ let frame = 0;
82
+ const requestUpdate = () => {
83
+ ownerWindow.cancelAnimationFrame(frame);
84
+ frame = ownerWindow.requestAnimationFrame(updateRect);
85
+ };
86
+ const resizeObserver = typeof ResizeObserver === "undefined"
87
+ ? null
88
+ : new ResizeObserver(requestUpdate);
89
+ updateRect();
90
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(element);
91
+ ownerWindow.addEventListener("resize", requestUpdate);
92
+ ownerWindow.addEventListener("scroll", requestUpdate, true);
93
+ return () => {
94
+ ownerWindow.cancelAnimationFrame(frame);
95
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
96
+ ownerWindow.removeEventListener("resize", requestUpdate);
97
+ ownerWindow.removeEventListener("scroll", requestUpdate, true);
98
+ };
99
+ }, [enabled, targetRef, updateRect]);
100
+ if (!enabled || !rect || typeof document === "undefined") {
101
+ return null;
102
+ }
103
+ const ownerDocument = (_b = (_a = targetRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document;
104
+ return createPortal(_jsx("div", { "aria-hidden": "true", "data-guidance-highlight-overlay": "true", className: cn("pointer-events-none fixed z-40", triggerHighlightClassName), style: {
105
+ left: rect.left,
106
+ top: rect.top,
107
+ width: rect.width,
108
+ height: rect.height,
109
+ boxShadow: focusOverlay
110
+ ? "0 0 0 9999px rgb(0 0 0 / 0.8), 0 0 var(--space-10) hsl(var(--ring) / 0.55)"
111
+ : "0 0 var(--space-10) hsl(var(--ring) / 0.55)",
112
+ } }), ownerDocument.body);
113
+ }
114
+ function GuidanceTip({ title, description, step, totalSteps, stepLabel, previousAction, nextAction, dismissAction, highlightTrigger = true, focusOverlay = false, contentClassName, trigger, open, defaultOpen, onOpenChange, align = "start", side = "bottom", sideOffset = 0, onKeyDown, ...popoverProps }) {
115
+ var _a, _b, _c;
116
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
117
+ const previousButtonRef = React.useRef(null);
118
+ const nextButtonRef = React.useRef(null);
119
+ const triggerHighlightRef = React.useRef(null);
120
+ const isControlled = open !== undefined;
121
+ const isOpen = isControlled ? open : internalOpen;
122
+ const resolvedStepLabel = getStepLabel({ stepLabel, step, totalSteps });
123
+ const showActions = Boolean(previousAction || nextAction || dismissAction);
124
+ const isFinalStep = typeof step === "number" &&
125
+ typeof totalSteps === "number" &&
126
+ step >= totalSteps;
127
+ const showNextActionIcon = !isFinalStep && !isDoneActionLabel(nextAction === null || nextAction === void 0 ? void 0 : nextAction.label);
128
+ const resolvedTrigger = getGuidanceTrigger({
129
+ trigger,
130
+ highlightTrigger: highlightTrigger && isOpen,
131
+ triggerRef: triggerHighlightRef,
132
+ });
133
+ const handleOpenChange = React.useCallback((nextOpen) => {
134
+ if (!isControlled) {
135
+ setInternalOpen(nextOpen);
136
+ }
137
+ onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
138
+ }, [isControlled, onOpenChange]);
139
+ const handleKeyDown = React.useCallback((event) => {
140
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
141
+ if (event.defaultPrevented ||
142
+ event.altKey ||
143
+ event.ctrlKey ||
144
+ event.metaKey ||
145
+ isEditableKeyboardTarget(event.target)) {
146
+ return;
147
+ }
148
+ if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") {
149
+ return;
150
+ }
151
+ const actionButton = event.key === "ArrowRight"
152
+ ? nextButtonRef.current
153
+ : previousButtonRef.current;
154
+ event.preventDefault();
155
+ event.stopPropagation();
156
+ if (canClickActionButton(actionButton)) {
157
+ actionButton.click();
158
+ }
159
+ }, [onKeyDown]);
160
+ return (_jsxs(_Fragment, { children: [_jsx(Popover, { open: isOpen, onOpenChange: handleOpenChange, trigger: resolvedTrigger, align: align, side: side, sideOffset: sideOffset, onKeyDown: handleKeyDown, contentClassName: cn("w-[calc(var(--space-12)*6)] max-w-[calc(100vw-var(--space-8))] p-[var(--space-3)]", "data-[side=bottom]:mt-[var(--space-3)] data-[side=left]:mr-[var(--space-3)] data-[side=right]:ml-[var(--space-3)] data-[side=top]:mb-[var(--space-3)]", contentClassName), ...popoverProps, children: _jsxs("div", { className: "space-y-[var(--space-2)]", children: [_jsxs("div", { className: "space-y-[var(--space-1)]", children: [resolvedStepLabel ? (_jsx("p", { className: "text-xs font-medium text-muted-foreground", children: resolvedStepLabel })) : null, _jsx("p", { className: "text-sm font-medium text-foreground", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), showActions ? (_jsxs("div", { className: cn("flex items-center gap-[var(--space-2)] pt-[var(--space-1)]", previousAction || dismissAction ? "justify-between" : "justify-end"), children: [previousAction || dismissAction ? (_jsxs("div", { className: "flex items-center gap-[var(--space-2)]", children: [dismissAction ? (_jsx(Button, { type: "button", variant: "ghost", size: "compact", onClick: dismissAction.onClick, disabled: dismissAction.disabled, loading: dismissAction.loading, loadingText: dismissAction.loadingText, "aria-label": dismissAction.ariaLabel, children: (_a = dismissAction.label) !== null && _a !== void 0 ? _a : "Skip" })) : null, previousAction ? (_jsxs(Button, { ref: previousButtonRef, type: "button", variant: "secondary", size: "compact", onClick: previousAction.onClick, disabled: previousAction.disabled, loading: previousAction.loading, loadingText: previousAction.loadingText, "aria-label": previousAction.ariaLabel, children: [_jsx(ChevronLeft, { className: "h-[var(--space-4)] w-[var(--space-4)]" }), (_b = previousAction.label) !== null && _b !== void 0 ? _b : "Previous"] })) : null] })) : null, nextAction ? (_jsxs(Button, { ref: nextButtonRef, type: "button", size: "compact", onClick: nextAction.onClick, disabled: nextAction.disabled, loading: nextAction.loading, loadingText: nextAction.loadingText, "aria-label": nextAction.ariaLabel, children: [(_c = nextAction.label) !== null && _c !== void 0 ? _c : "Next", showNextActionIcon ? (_jsx(ChevronRight, { className: "h-[var(--space-4)] w-[var(--space-4)]" })) : null] })) : null] })) : null] }) }), _jsx(GuidanceTriggerHighlight, { enabled: highlightTrigger && isOpen, focusOverlay: focusOverlay, targetRef: triggerHighlightRef })] }));
161
+ }
162
+ export { GuidanceTip };