@handled-ai/design-system 0.9.28 → 0.10.0

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 (64) hide show
  1. package/dist/components/badge.d.ts +1 -1
  2. package/dist/components/button.d.ts +1 -1
  3. package/dist/components/compliance-badge.d.ts +10 -0
  4. package/dist/components/compliance-badge.js +95 -0
  5. package/dist/components/compliance-badge.js.map +1 -0
  6. package/dist/components/contact-chip.d.ts +12 -0
  7. package/dist/components/contact-chip.js +98 -0
  8. package/dist/components/contact-chip.js.map +1 -0
  9. package/dist/components/empty-state.d.ts +11 -0
  10. package/dist/components/empty-state.js +46 -0
  11. package/dist/components/empty-state.js.map +1 -0
  12. package/dist/components/filter-chip.d.ts +9 -0
  13. package/dist/components/filter-chip.js +67 -0
  14. package/dist/components/filter-chip.js.map +1 -0
  15. package/dist/components/inline-banner.d.ts +10 -0
  16. package/dist/components/inline-banner.js +97 -0
  17. package/dist/components/inline-banner.js.map +1 -0
  18. package/dist/components/kbd-hint.d.ts +5 -0
  19. package/dist/components/kbd-hint.js +51 -0
  20. package/dist/components/kbd-hint.js.map +1 -0
  21. package/dist/components/rich-text-toolbar.d.ts +9 -0
  22. package/dist/components/rich-text-toolbar.js +103 -0
  23. package/dist/components/rich-text-toolbar.js.map +1 -0
  24. package/dist/components/step-timeline.d.ts +19 -0
  25. package/dist/components/step-timeline.js +134 -0
  26. package/dist/components/step-timeline.js.map +1 -0
  27. package/dist/components/sticky-action-bar.d.ts +10 -0
  28. package/dist/components/sticky-action-bar.js +56 -0
  29. package/dist/components/sticky-action-bar.js.map +1 -0
  30. package/dist/components/switch.d.ts +6 -0
  31. package/dist/components/switch.js +66 -0
  32. package/dist/components/switch.js.map +1 -0
  33. package/dist/components/tabs.d.ts +1 -1
  34. package/dist/components/variable-autocomplete.d.ts +21 -0
  35. package/dist/components/variable-autocomplete.js +171 -0
  36. package/dist/components/variable-autocomplete.js.map +1 -0
  37. package/dist/index.d.ts +12 -1
  38. package/dist/index.js +12 -1
  39. package/dist/index.js.map +1 -1
  40. package/package.json +2 -1
  41. package/src/components/__tests__/compliance-badge.test.tsx +88 -0
  42. package/src/components/__tests__/contact-chip.test.tsx +88 -0
  43. package/src/components/__tests__/empty-state.test.tsx +76 -0
  44. package/src/components/__tests__/filter-chip.test.tsx +73 -0
  45. package/src/components/__tests__/inline-banner.test.tsx +110 -0
  46. package/src/components/__tests__/kbd-hint.test.tsx +29 -0
  47. package/src/components/__tests__/rich-text-toolbar.test.tsx +92 -0
  48. package/src/components/__tests__/step-timeline.test.tsx +174 -0
  49. package/src/components/__tests__/sticky-action-bar.test.tsx +52 -0
  50. package/src/components/__tests__/switch.test.tsx +39 -0
  51. package/src/components/__tests__/variable-autocomplete.test.tsx +155 -0
  52. package/src/components/compliance-badge.tsx +68 -0
  53. package/src/components/contact-chip.tsx +68 -0
  54. package/src/components/empty-state.tsx +37 -0
  55. package/src/components/filter-chip.tsx +37 -0
  56. package/src/components/inline-banner.tsx +69 -0
  57. package/src/components/kbd-hint.tsx +21 -0
  58. package/src/components/rich-text-toolbar.tsx +90 -0
  59. package/src/components/step-timeline.tsx +149 -0
  60. package/src/components/sticky-action-bar.tsx +36 -0
  61. package/src/components/switch.tsx +29 -0
  62. package/src/components/variable-autocomplete.tsx +178 -0
  63. package/src/index.ts +12 -1
  64. package/src/styles/globals.css +60 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/variable-autocomplete.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"../lib/utils\"\n\ninterface VariableDef {\n name: string\n description?: string\n source?: string\n}\n\ninterface VariableGroup {\n label: string\n variables: VariableDef[]\n}\n\ninterface VariableAutocompleteProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n groups: VariableGroup[]\n filter?: string\n onSelect: (variable: VariableDef) => void\n onClose: () => void\n maxResults?: number\n}\n\nfunction VariableAutocomplete({\n groups,\n filter = \"\",\n onSelect,\n onClose,\n maxResults = 10,\n className,\n ...rest\n}: VariableAutocompleteProps) {\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [activeIndex, setActiveIndex] = React.useState(-1)\n\n // Build filtered results preserving group structure\n const filteredGroups = React.useMemo(\n () =>\n groups\n .map((group) => ({\n ...group,\n variables: group.variables.filter((v) =>\n v.name.toLowerCase().startsWith(filter.toLowerCase())\n ),\n }))\n .filter((group) => group.variables.length > 0),\n [groups, filter]\n )\n\n // Flatten for indexing / keyboard navigation, respecting maxResults\n const allFiltered = React.useMemo(() => {\n const result: { group: VariableGroup; variable: VariableDef }[] = []\n for (const group of filteredGroups) {\n for (const variable of group.variables) {\n if (result.length >= maxResults) break\n result.push({ group, variable })\n }\n if (result.length >= maxResults) break\n }\n return result\n }, [filteredGroups, maxResults])\n\n // Reset active index when results change\n React.useEffect(() => {\n setActiveIndex(-1)\n }, [filter])\n\n React.useEffect(() => {\n function handleMouseDown(e: MouseEvent) {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n onClose()\n }\n }\n function handleKeyDown(e: KeyboardEvent) {\n if (e.key === \"Escape\") {\n onClose()\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault()\n setActiveIndex((prev) => (prev < allFiltered.length - 1 ? prev + 1 : 0))\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault()\n setActiveIndex((prev) => (prev > 0 ? prev - 1 : allFiltered.length - 1))\n }\n if (e.key === \"Enter\" && activeIndex >= 0 && activeIndex < allFiltered.length) {\n e.preventDefault()\n onSelect(allFiltered[activeIndex].variable)\n }\n }\n document.addEventListener(\"mousedown\", handleMouseDown)\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n document.removeEventListener(\"mousedown\", handleMouseDown)\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [onClose, onSelect, allFiltered, activeIndex])\n\n if (allFiltered.length === 0) {\n return null\n }\n\n // Group the capped results back for rendering\n const groupsToRender: { label: string; items: { variable: VariableDef; flatIndex: number }[] }[] = []\n let flatIndex = 0\n for (const group of filteredGroups) {\n const items: { variable: VariableDef; flatIndex: number }[] = []\n for (const variable of group.variables) {\n if (flatIndex >= maxResults) break\n items.push({ variable, flatIndex })\n flatIndex++\n }\n if (items.length > 0) {\n groupsToRender.push({ label: group.label, items })\n }\n if (flatIndex >= maxResults) break\n }\n\n return (\n <div\n ref={containerRef}\n data-slot=\"variable-autocomplete\"\n className={cn(\n \"absolute z-50 bg-popover border border-border rounded-lg shadow-lg w-64 overflow-hidden\",\n className\n )}\n {...rest}\n >\n <div data-slot=\"variable-autocomplete-header\" className=\"px-3 py-1.5 border-b border-border\">\n <span className=\"text-[10px] font-medium text-muted-foreground uppercase tracking-wider\">\n Insert variable\n </span>\n </div>\n <div role=\"listbox\" aria-label=\"Variables\" className=\"overflow-y-auto\" style={{ maxHeight: \"312px\" }}>\n {groupsToRender.map((group) => (\n <div key={group.label} role=\"group\" aria-label={group.label}>\n <div data-slot=\"variable-autocomplete-group-label\" className=\"px-3 py-1 text-[10px] font-medium text-muted-foreground/60 uppercase tracking-wider\">\n {group.label}\n </div>\n {group.items.map(({ variable, flatIndex: idx }) => (\n <button\n type=\"button\"\n key={`${group.label}:${variable.name}`}\n role=\"option\"\n aria-selected={idx === activeIndex}\n data-slot=\"variable-autocomplete-item\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onSelect(variable)}\n onMouseEnter={() => setActiveIndex(idx)}\n className={cn(\n \"w-full text-left px-3 py-2 cursor-pointer transition-colors flex items-start justify-between gap-2\",\n idx === activeIndex ? \"bg-muted/50\" : \"hover:bg-muted/30\"\n )}\n >\n <div className=\"min-w-0\">\n <div className=\"text-xs font-mono text-blue-600\">{`{{${variable.name}}}`}</div>\n {variable.description && (\n <div className=\"text-[10px] text-muted-foreground\">{variable.description}</div>\n )}\n </div>\n {variable.source && (\n <span className=\"text-[9px] px-1.5 py-0.5 rounded bg-muted/30 text-muted-foreground shrink-0 mt-0.5 capitalize\">\n {variable.source}\n </span>\n )}\n </button>\n ))}\n </div>\n ))}\n </div>\n </div>\n )\n}\n\nexport { VariableAutocomplete, type VariableAutocompleteProps, type VariableDef, type VariableGroup }\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, Ava
9
9
  export { Badge, badgeVariants } from './components/badge.js';
10
10
  export { Button, buttonVariants } from './components/button.js';
11
11
  export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './components/card.js';
12
+ export { ComplianceBadge, ComplianceBadgeProps, ComplianceStatus } from './components/compliance-badge.js';
13
+ export { ContactChip, ContactChipProps } from './components/contact-chip.js';
12
14
  export { ContactChannel, ContactItem, ContactList, ContactListProps } from './components/contact-list.js';
13
15
  export { CheckInsCard, RecentlyCompletedCard, TopTasksCard, UpcomingMeetingsCard } from './components/dashboard-cards.js';
14
16
  export { DataRow, DataTable, DataTableProps } from './components/data-table.js';
@@ -19,15 +21,19 @@ export { DataTableToolbar } from './components/data-table-toolbar.js';
19
21
  export { Citation, DetailViewHeader, DetailViewSummary, DetailViewThread, SourceDef, SourceList, ThreadMessage } from './components/detail-view.js';
20
22
  export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from './components/dialog.js';
21
23
  export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from './components/dropdown-menu.js';
24
+ export { EmptyState, EmptyStateProps } from './components/empty-state.js';
22
25
  export { ActivityItem, ConnectedApps, EntityActivityItem, EntityDetails, EntityMetadataField, EntityMetadataGrid, EntityPanel, EntityPanelBrandIcons, EntityPanelHeader, EntityPanelTabs, EntitySection, PotentialContacts, RecentActivity, SystemActivity } from './components/entity-panel.js';
26
+ export { FilterChip, FilterChipProps } from './components/filter-chip.js';
23
27
  export { InboxGroupHeader, InboxRow, InboxRowProps } from './components/inbox-row.js';
24
28
  export { AssigneeFilter, InboxFilterCategory, InboxToolbar, InboxToolbarProps } from './components/inbox-toolbar.js';
29
+ export { InlineBanner, InlineBannerProps } from './components/inline-banner.js';
25
30
  export { Input } from './components/input.js';
26
31
  export { FilterDefinition, InsightsFilterBar, InsightsFilterBarProps } from './components/insights-filter-bar.js';
27
32
  export { GroupedListGroup, GroupedListView, GroupedListViewProps, ItemList } from './components/item-list.js';
28
33
  export { ItemListDisplay, ItemListDisplayState, ItemListGrouping, ItemListViewMode } from './components/item-list-display.js';
29
34
  export { ItemListFilter, ItemListFilterCategory } from './components/item-list-filter.js';
30
35
  export { ItemListQuickView, ItemListToolbar } from './components/item-list-toolbar.js';
36
+ export { KbdHint } from './components/kbd-hint.js';
31
37
  export { Label } from './components/label.js';
32
38
  export { Message, MessageAvatar, MessageAvatarProps, MessageContent, MessageContentProps, MessageProps } from './components/message.js';
33
39
  export { MetricCard, MetricCardProps, MetricDataPoint } from './components/metric-card.js';
@@ -39,6 +45,7 @@ export { QuickActionModal, QuickActionPriority, QuickActionTaskDraft, QuickActio
39
45
  export { ActiveVariant, QuickActionSidebarNav, SidebarNavItem, SidebarNavSection, SidebarUserProfile, UserMenuItem } from './components/quick-action-sidebar-nav.js';
40
46
  export { RecommendedAction, RecommendedActionsSection } from './components/recommended-actions-section.js';
41
47
  export { ReportCard, ReportCardProps } from './components/report-card.js';
48
+ export { RichTextAction, RichTextToolbar, RichTextToolbarProps } from './components/rich-text-toolbar.js';
42
49
  export { ScoreAnalysisModal, ScoreAnalysisModalProps, ScoreAnalysisPanel } from './components/score-analysis-modal.js';
43
50
  export { ScoreBreakdown, ScoreBreakdownProps, ScoreFactor } from './components/score-breakdown.js';
44
51
  export { ScoreFeedback, useScoreFeedback } from './components/score-feedback.js';
@@ -50,17 +57,21 @@ export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHe
50
57
  export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar } from './components/sidebar.js';
51
58
  export { ApprovalState, SignalApproval, SignalApprovalActions, SignalApprovalContextValue, SignalApprovalGate, SignalApprovalLabels, SignalApprovalRoot, SignalApprovalRootProps, useSignalApproval } from './components/signal-feedback-inline.js';
52
59
  export { SimpleDataTable, SimpleDataTableProps } from './components/simple-data-table.js';
53
- export { VirtualizedDataTable, VirtualizedDataTableProps } from './components/virtualized-data-table.js';
54
60
  export { Skeleton } from './components/skeleton.js';
55
61
  export { StatusBadge, StatusBadgeProps, StatusType } from './components/status-badge.js';
62
+ export { StepTimeline, StepTimelineProps, StepType, TimelineStep } from './components/step-timeline.js';
63
+ export { StickyActionBar, StickyActionBarProps } from './components/sticky-action-bar.js';
56
64
  export { StyledBarItem, StyledBarList, StyledBarListProps } from './components/styled-bar-list.js';
57
65
  export { SuggestedAction, SuggestedActionBrowserMeta, SuggestedActionCallMeta, SuggestedActionEmailMeta, SuggestedActionFollowUp, SuggestedActionManualMeta, SuggestedActionReplyTo, SuggestedActionThreadMessage, SuggestedActionTicket, SuggestedActions, SuggestedActionsIconMap, SuggestedActionsProps, SuggestedContact } from './components/suggested-actions.js';
66
+ export { Switch } from './components/switch.js';
58
67
  export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from './components/table.js';
59
68
  export { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants } from './components/tabs.js';
60
69
  export { Textarea } from './components/textarea.js';
61
70
  export { TimelineActivity, TimelineActivityProps, TimelineEvent } from './components/timeline-activity.js';
62
71
  export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './components/tooltip.js';
72
+ export { VariableAutocomplete, VariableAutocompleteProps, VariableDef, VariableGroup } from './components/variable-autocomplete.js';
63
73
  export { ViewMode, ViewModeToggle, ViewModeToggleProps } from './components/view-mode-toggle.js';
74
+ export { VirtualizedDataTable, VirtualizedDataTableProps } from './components/virtualized-data-table.js';
64
75
  export { ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent } from './charts/chart.js';
65
76
  export { CHART_CURSOR_STYLE, CHART_TOOLTIP_STYLE, ChartTooltipEntry, SimpleChartTooltip, SimpleChartTooltipProps } from './charts/chart-tooltip.js';
66
77
  export { BarChartComponent, BarChartComponentProps, BarSeries } from './charts/bar-chart-component.js';
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ export * from "./components/avatar.js";
9
9
  export * from "./components/badge.js";
10
10
  export * from "./components/button.js";
11
11
  export * from "./components/card.js";
12
+ export * from "./components/compliance-badge.js";
13
+ export * from "./components/contact-chip.js";
12
14
  export * from "./components/contact-list.js";
13
15
  export * from "./components/dashboard-cards.js";
14
16
  export * from "./components/data-table.js";
@@ -19,15 +21,19 @@ export * from "./components/data-table-toolbar.js";
19
21
  export * from "./components/detail-view.js";
20
22
  export * from "./components/dialog.js";
21
23
  export * from "./components/dropdown-menu.js";
24
+ export * from "./components/empty-state.js";
22
25
  export * from "./components/entity-panel.js";
26
+ export * from "./components/filter-chip.js";
23
27
  export * from "./components/inbox-row.js";
24
28
  export * from "./components/inbox-toolbar.js";
29
+ export * from "./components/inline-banner.js";
25
30
  export * from "./components/input.js";
26
31
  export * from "./components/insights-filter-bar.js";
27
32
  export * from "./components/item-list.js";
28
33
  export * from "./components/item-list-display.js";
29
34
  export * from "./components/item-list-filter.js";
30
35
  export * from "./components/item-list-toolbar.js";
36
+ export * from "./components/kbd-hint.js";
31
37
  export * from "./components/label.js";
32
38
  export * from "./components/message.js";
33
39
  export * from "./components/metric-card.js";
@@ -41,6 +47,7 @@ import {
41
47
  export * from "./components/quick-action-sidebar-nav.js";
42
48
  export * from "./components/recommended-actions-section.js";
43
49
  export * from "./components/report-card.js";
50
+ export * from "./components/rich-text-toolbar.js";
44
51
  export * from "./components/score-analysis-modal.js";
45
52
  export * from "./components/score-breakdown.js";
46
53
  export * from "./components/score-feedback.js";
@@ -52,17 +59,21 @@ export * from "./components/sheet.js";
52
59
  export * from "./components/sidebar.js";
53
60
  export * from "./components/signal-feedback-inline.js";
54
61
  export * from "./components/simple-data-table.js";
55
- export * from "./components/virtualized-data-table.js";
56
62
  export * from "./components/skeleton.js";
57
63
  export * from "./components/status-badge.js";
64
+ export * from "./components/step-timeline.js";
65
+ export * from "./components/sticky-action-bar.js";
58
66
  export * from "./components/styled-bar-list.js";
59
67
  export * from "./components/suggested-actions.js";
68
+ export * from "./components/switch.js";
60
69
  export * from "./components/table.js";
61
70
  export * from "./components/tabs.js";
62
71
  export * from "./components/textarea.js";
63
72
  export * from "./components/timeline-activity.js";
64
73
  export * from "./components/tooltip.js";
74
+ export * from "./components/variable-autocomplete.js";
65
75
  export * from "./components/view-mode-toggle.js";
76
+ export * from "./components/virtualized-data-table.js";
66
77
  export * from "./charts/index.js";
67
78
  export * from "./prototype/prototype-config.js";
68
79
  export * from "./prototype/prototype-shell.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @handled-ai/design-system\n * UI components and utilities (shadcn-style, New York)\n */\n\n// Utilities\nexport { cn } from \"./lib/utils\"\nexport { BRAND_ICONS } from \"./lib/icons\"\n\n// Hooks\nexport { useIsMobile } from \"./hooks/use-mobile\"\n\n// Components (light — no recharts/nivo/three transitive deps)\nexport * from \"./components/activity-detail\"\nexport * from \"./components/activity-log\"\nexport * from \"./components/agent-popover\"\nexport * from \"./components/agent-widget\"\nexport * from \"./components/avatar\"\nexport * from \"./components/badge\"\nexport * from \"./components/button\"\nexport * from \"./components/card\"\nexport * from \"./components/contact-list\"\nexport * from \"./components/dashboard-cards\"\nexport * from \"./components/data-table\"\nexport * from \"./components/data-table-display\"\nexport * from \"./components/data-table-filter\"\nexport * from \"./components/data-table-quick-views\"\nexport * from \"./components/data-table-toolbar\"\nexport * from \"./components/detail-view\"\nexport * from \"./components/dialog\"\nexport * from \"./components/dropdown-menu\"\nexport * from \"./components/entity-panel\"\nexport * from \"./components/inbox-row\"\nexport * from \"./components/inbox-toolbar\"\nexport * from \"./components/input\"\nexport * from \"./components/insights-filter-bar\"\nexport * from \"./components/item-list\"\nexport * from \"./components/item-list-display\"\nexport * from \"./components/item-list-filter\"\nexport * from \"./components/item-list-toolbar\"\nexport * from \"./components/label\"\nexport * from \"./components/message\"\nexport * from \"./components/metric-card\"\nexport * from \"./components/performance-metrics-table\"\nexport * from \"./components/preview-list\"\nexport * from \"./components/progress\"\nexport * from \"./components/quick-action-chat-area\"\nexport {\n QuickActionModal,\n type QuickActionPriority,\n type QuickActionTaskDraft,\n type QuickActionTemplate,\n} from \"./components/quick-action-modal\"\nexport * from \"./components/quick-action-sidebar-nav\"\nexport * from \"./components/recommended-actions-section\"\nexport * from \"./components/report-card\"\nexport * from \"./components/score-analysis-modal\"\nexport * from \"./components/score-breakdown\"\nexport * from \"./components/score-feedback\"\nexport * from \"./components/score-ring\"\nexport * from \"./components/scroll-area\"\nexport * from \"./components/select\"\nexport * from \"./components/separator\"\nexport * from \"./components/sheet\"\nexport * from \"./components/sidebar\"\nexport * from \"./components/signal-feedback-inline\"\nexport * from \"./components/simple-data-table\"\nexport * from \"./components/virtualized-data-table\"\nexport * from \"./components/skeleton\"\nexport * from \"./components/status-badge\"\nexport * from \"./components/styled-bar-list\"\nexport * from \"./components/suggested-actions\"\nexport * from \"./components/table\"\nexport * from \"./components/tabs\"\nexport * from \"./components/textarea\"\nexport * from \"./components/timeline-activity\"\nexport * from \"./components/tooltip\"\nexport * from \"./components/view-mode-toggle\"\n\n// Charts (re-exported for backward compatibility with root imports)\nexport * from \"./charts/index\"\n\n// Prototype template system (re-exported for backward compatibility)\nexport * from \"./prototype/prototype-config\"\nexport * from \"./prototype/prototype-shell\"\nexport * from \"./prototype/prototype-inbox-view\"\nexport * from \"./prototype/prototype-insights-view\"\nexport * from \"./prototype/prototype-accounts-view\"\nexport * from \"./prototype/prototype-admin-view\"\nexport * from \"./prototype/prototype-work-queue-view\"\n"],"mappings":"AAMA,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAG5B,SAAS,mBAAmB;AAG5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @handled-ai/design-system\n * UI components and utilities (shadcn-style, New York)\n */\n\n// Utilities\nexport { cn } from \"./lib/utils\"\nexport { BRAND_ICONS } from \"./lib/icons\"\n\n// Hooks\nexport { useIsMobile } from \"./hooks/use-mobile\"\n\n// Components (light — no recharts/nivo/three transitive deps)\nexport * from \"./components/activity-detail\"\nexport * from \"./components/activity-log\"\nexport * from \"./components/agent-popover\"\nexport * from \"./components/agent-widget\"\nexport * from \"./components/avatar\"\nexport * from \"./components/badge\"\nexport * from \"./components/button\"\nexport * from \"./components/card\"\nexport * from \"./components/compliance-badge\"\nexport * from \"./components/contact-chip\"\nexport * from \"./components/contact-list\"\nexport * from \"./components/dashboard-cards\"\nexport * from \"./components/data-table\"\nexport * from \"./components/data-table-display\"\nexport * from \"./components/data-table-filter\"\nexport * from \"./components/data-table-quick-views\"\nexport * from \"./components/data-table-toolbar\"\nexport * from \"./components/detail-view\"\nexport * from \"./components/dialog\"\nexport * from \"./components/dropdown-menu\"\nexport * from \"./components/empty-state\"\nexport * from \"./components/entity-panel\"\nexport * from \"./components/filter-chip\"\nexport * from \"./components/inbox-row\"\nexport * from \"./components/inbox-toolbar\"\nexport * from \"./components/inline-banner\"\nexport * from \"./components/input\"\nexport * from \"./components/insights-filter-bar\"\nexport * from \"./components/item-list\"\nexport * from \"./components/item-list-display\"\nexport * from \"./components/item-list-filter\"\nexport * from \"./components/item-list-toolbar\"\nexport * from \"./components/kbd-hint\"\nexport * from \"./components/label\"\nexport * from \"./components/message\"\nexport * from \"./components/metric-card\"\nexport * from \"./components/performance-metrics-table\"\nexport * from \"./components/preview-list\"\nexport * from \"./components/progress\"\nexport * from \"./components/quick-action-chat-area\"\nexport {\n QuickActionModal,\n type QuickActionPriority,\n type QuickActionTaskDraft,\n type QuickActionTemplate,\n} from \"./components/quick-action-modal\"\nexport * from \"./components/quick-action-sidebar-nav\"\nexport * from \"./components/recommended-actions-section\"\nexport * from \"./components/report-card\"\nexport * from \"./components/rich-text-toolbar\"\nexport * from \"./components/score-analysis-modal\"\nexport * from \"./components/score-breakdown\"\nexport * from \"./components/score-feedback\"\nexport * from \"./components/score-ring\"\nexport * from \"./components/scroll-area\"\nexport * from \"./components/select\"\nexport * from \"./components/separator\"\nexport * from \"./components/sheet\"\nexport * from \"./components/sidebar\"\nexport * from \"./components/signal-feedback-inline\"\nexport * from \"./components/simple-data-table\"\nexport * from \"./components/skeleton\"\nexport * from \"./components/status-badge\"\nexport * from \"./components/step-timeline\"\nexport * from \"./components/sticky-action-bar\"\nexport * from \"./components/styled-bar-list\"\nexport * from \"./components/suggested-actions\"\nexport * from \"./components/switch\"\nexport * from \"./components/table\"\nexport * from \"./components/tabs\"\nexport * from \"./components/textarea\"\nexport * from \"./components/timeline-activity\"\nexport * from \"./components/tooltip\"\nexport * from \"./components/variable-autocomplete\"\nexport * from \"./components/view-mode-toggle\"\nexport * from \"./components/virtualized-data-table\"\n\n// Charts (re-exported for backward compatibility with root imports)\nexport * from \"./charts/index\"\n\n// Prototype template system (re-exported for backward compatibility)\nexport * from \"./prototype/prototype-config\"\nexport * from \"./prototype/prototype-shell\"\nexport * from \"./prototype/prototype-inbox-view\"\nexport * from \"./prototype/prototype-insights-view\"\nexport * from \"./prototype/prototype-accounts-view\"\nexport * from \"./prototype/prototype-admin-view\"\nexport * from \"./prototype/prototype-work-queue-view\"\n"],"mappings":"AAMA,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAG5B,SAAS,mBAAmB;AAG5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.9.28",
3
+ "version": "0.10.0",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -103,6 +103,7 @@
103
103
  "typecheck": "tsc --noEmit",
104
104
  "lint": "next lint",
105
105
  "registry:build": "shadcn build",
106
+ "test": "vitest run",
106
107
  "prepublishOnly": "npm run build:lib"
107
108
  },
108
109
  "peerDependencies": {
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import React from "react";
3
+ import { render, screen } from "@testing-library/react";
4
+ import { ComplianceBadge } from "../compliance-badge";
5
+
6
+ describe("ComplianceBadge", () => {
7
+ it("renders with data-slot='compliance-badge'", () => {
8
+ const { container } = render(<ComplianceBadge status="verified" />);
9
+ const el = container.querySelector('[data-slot="compliance-badge"]');
10
+ expect(el).not.toBeNull();
11
+ });
12
+
13
+ it("defaults to variant='line'", () => {
14
+ const { container } = render(<ComplianceBadge status="verified" />);
15
+ const el = container.querySelector('[data-slot="compliance-badge"]')!;
16
+ expect(el.getAttribute("data-variant")).toBe("line");
17
+ });
18
+
19
+ it("verified line variant shows 'Compliance verified' text", () => {
20
+ render(<ComplianceBadge status="verified" variant="line" />);
21
+ expect(screen.getByText("Compliance verified")).not.toBeNull();
22
+ });
23
+
24
+ it("verified pill variant shows 'Verified' text", () => {
25
+ render(<ComplianceBadge status="verified" variant="pill" />);
26
+ expect(screen.getByText("Verified")).not.toBeNull();
27
+ });
28
+
29
+ it("pending line variant shows 'Pending compliance review'", () => {
30
+ render(<ComplianceBadge status="pending" variant="line" />);
31
+ expect(screen.getByText("Pending compliance review")).not.toBeNull();
32
+ });
33
+
34
+ it("pending pill variant shows 'Pending review'", () => {
35
+ render(<ComplianceBadge status="pending" variant="pill" />);
36
+ expect(screen.getByText("Pending review")).not.toBeNull();
37
+ });
38
+
39
+ it("changed_since_verified line variant shows 'Changed since last verification'", () => {
40
+ render(<ComplianceBadge status="changed_since_verified" variant="line" />);
41
+ expect(screen.getByText("Changed since last verification")).not.toBeNull();
42
+ });
43
+
44
+ it("changed_since_verified pill variant shows 'Changed'", () => {
45
+ render(<ComplianceBadge status="changed_since_verified" variant="pill" />);
46
+ expect(screen.getByText("Changed")).not.toBeNull();
47
+ });
48
+
49
+ it("never_verified shows 'Never verified' for both variants", () => {
50
+ const { unmount } = render(<ComplianceBadge status="never_verified" variant="line" />);
51
+ expect(screen.getByText("Never verified")).not.toBeNull();
52
+ unmount();
53
+
54
+ render(<ComplianceBadge status="never_verified" variant="pill" />);
55
+ expect(screen.getByText("Never verified")).not.toBeNull();
56
+ });
57
+
58
+ it("sets data-status attribute correctly for each status", () => {
59
+ const statuses = ["verified", "pending", "changed_since_verified", "never_verified"] as const;
60
+ for (const status of statuses) {
61
+ const { container, unmount } = render(<ComplianceBadge status={status} />);
62
+ const el = container.querySelector('[data-slot="compliance-badge"]')!;
63
+ expect(el.getAttribute("data-status")).toBe(status);
64
+ unmount();
65
+ }
66
+ });
67
+
68
+ it("sets data-variant attribute correctly", () => {
69
+ const { container: c1, unmount: u1 } = render(
70
+ <ComplianceBadge status="verified" variant="line" />
71
+ );
72
+ expect(c1.querySelector('[data-slot="compliance-badge"]')!.getAttribute("data-variant")).toBe("line");
73
+ u1();
74
+
75
+ const { container: c2 } = render(
76
+ <ComplianceBadge status="verified" variant="pill" />
77
+ );
78
+ expect(c2.querySelector('[data-slot="compliance-badge"]')!.getAttribute("data-variant")).toBe("pill");
79
+ });
80
+
81
+ it("merges custom className", () => {
82
+ const { container } = render(
83
+ <ComplianceBadge status="verified" className="my-custom-class" />
84
+ );
85
+ const el = container.querySelector('[data-slot="compliance-badge"]')!;
86
+ expect(el.classList.contains("my-custom-class")).toBe(true);
87
+ });
88
+ });
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import React from "react";
3
+ import { render, screen, fireEvent } from "@testing-library/react";
4
+ import { ContactChip } from "../contact-chip";
5
+
6
+ describe("ContactChip", () => {
7
+ it("renders with data-slot='contact-chip'", () => {
8
+ const { container } = render(<ContactChip name="Alice" />);
9
+ const el = container.querySelector('[data-slot="contact-chip"]');
10
+ expect(el).not.toBeNull();
11
+ });
12
+
13
+ it("renders name text", () => {
14
+ render(<ContactChip name="Alice" />);
15
+ expect(screen.getByText("Alice")).toBeDefined();
16
+ });
17
+
18
+ it("renders email when provided", () => {
19
+ render(<ContactChip name="Alice" email="alice@example.com" />);
20
+ expect(screen.getByText("alice@example.com")).toBeDefined();
21
+ });
22
+
23
+ it("does not render email when not provided", () => {
24
+ const { container } = render(<ContactChip name="Alice" />);
25
+ const spans = container.querySelectorAll("span.text-muted-foreground");
26
+ expect(spans.length).toBe(0);
27
+ });
28
+
29
+ it("verified state (default) has data-verified='true'", () => {
30
+ const { container } = render(<ContactChip name="Alice" />);
31
+ const el = container.querySelector('[data-slot="contact-chip"]')!;
32
+ expect(el.getAttribute("data-verified")).toBe("true");
33
+ });
34
+
35
+ it("unverified state has data-verified='false'", () => {
36
+ const { container } = render(<ContactChip name="Alice" verified={false} />);
37
+ const el = container.querySelector('[data-slot="contact-chip"]')!;
38
+ expect(el.getAttribute("data-verified")).toBe("false");
39
+ });
40
+
41
+ it("shows Confirm button when verified=false and onConfirm provided", () => {
42
+ render(<ContactChip name="Alice" verified={false} onConfirm={() => {}} />);
43
+ expect(screen.getByText("Confirm")).toBeDefined();
44
+ });
45
+
46
+ it("does not show Confirm button when verified=true", () => {
47
+ const { container } = render(<ContactChip name="Alice" verified={true} onConfirm={() => {}} />);
48
+ const confirmBtn = container.querySelector('[data-slot="contact-chip-confirm"]');
49
+ expect(confirmBtn).toBeNull();
50
+ });
51
+
52
+ it("Confirm button fires onConfirm on click", () => {
53
+ const handler = vi.fn();
54
+ render(<ContactChip name="Alice" verified={false} onConfirm={handler} />);
55
+ fireEvent.click(screen.getByText("Confirm"));
56
+ expect(handler).toHaveBeenCalledTimes(1);
57
+ });
58
+
59
+ it("Confirm button has aria-label='Confirm Alice' and type='button'", () => {
60
+ const { container } = render(
61
+ <ContactChip name="Alice" verified={false} onConfirm={() => {}} />
62
+ );
63
+ const btn = container.querySelector('[data-slot="contact-chip-confirm"]') as HTMLButtonElement;
64
+ expect(btn.getAttribute("aria-label")).toBe("Confirm Alice");
65
+ expect(btn.type).toBe("button");
66
+ });
67
+
68
+ it("shows remove button when onRemove provided", () => {
69
+ const { container } = render(<ContactChip name="Alice" onRemove={() => {}} />);
70
+ const removeBtn = container.querySelector('[data-slot="contact-chip-remove"]');
71
+ expect(removeBtn).not.toBeNull();
72
+ });
73
+
74
+ it("remove button fires onRemove on click", () => {
75
+ const handler = vi.fn();
76
+ const { container } = render(<ContactChip name="Alice" onRemove={handler} />);
77
+ const removeBtn = container.querySelector('[data-slot="contact-chip-remove"]')!;
78
+ fireEvent.click(removeBtn);
79
+ expect(handler).toHaveBeenCalledTimes(1);
80
+ });
81
+
82
+ it("remove button has aria-label='Remove Alice' and type='button'", () => {
83
+ const { container } = render(<ContactChip name="Alice" onRemove={() => {}} />);
84
+ const btn = container.querySelector('[data-slot="contact-chip-remove"]') as HTMLButtonElement;
85
+ expect(btn.getAttribute("aria-label")).toBe("Remove Alice");
86
+ expect(btn.type).toBe("button");
87
+ });
88
+ });
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import React from "react";
3
+ import { render, screen } from "@testing-library/react";
4
+ import { EmptyState } from "../empty-state";
5
+
6
+ describe("EmptyState", () => {
7
+ it("renders with data-slot='empty-state'", () => {
8
+ const { container } = render(<EmptyState description="No items" />);
9
+ const el = container.querySelector('[data-slot="empty-state"]');
10
+ expect(el).not.toBeNull();
11
+ });
12
+
13
+ it("renders description text", () => {
14
+ render(<EmptyState description="Nothing to show here" />);
15
+ expect(screen.getByText("Nothing to show here")).not.toBeNull();
16
+ });
17
+
18
+ it("renders icon when provided", () => {
19
+ const { container } = render(
20
+ <EmptyState
21
+ description="No items"
22
+ icon={<span data-testid="test-icon">★</span>}
23
+ />
24
+ );
25
+ const iconWrapper = container.querySelector('[data-slot="empty-state-icon"]');
26
+ expect(iconWrapper).not.toBeNull();
27
+ expect(screen.getByTestId("test-icon")).not.toBeNull();
28
+ });
29
+
30
+ it("renders title when provided", () => {
31
+ const { container } = render(
32
+ <EmptyState description="No items" title="Empty" />
33
+ );
34
+ const titleEl = container.querySelector('[data-slot="empty-state-title"]');
35
+ expect(titleEl).not.toBeNull();
36
+ expect(screen.getByText("Empty")).not.toBeNull();
37
+ });
38
+
39
+ it("renders action when provided", () => {
40
+ const { container } = render(
41
+ <EmptyState
42
+ description="No items"
43
+ action={<button type="button">Add item</button>}
44
+ />
45
+ );
46
+ const actionWrapper = container.querySelector('[data-slot="empty-state-action"]');
47
+ expect(actionWrapper).not.toBeNull();
48
+ expect(screen.getByText("Add item")).not.toBeNull();
49
+ });
50
+
51
+ it("omits icon wrapper when icon not provided", () => {
52
+ const { container } = render(<EmptyState description="No items" />);
53
+ const iconWrapper = container.querySelector('[data-slot="empty-state-icon"]');
54
+ expect(iconWrapper).toBeNull();
55
+ });
56
+
57
+ it("omits title when not provided", () => {
58
+ const { container } = render(<EmptyState description="No items" />);
59
+ const titleEl = container.querySelector('[data-slot="empty-state-title"]');
60
+ expect(titleEl).toBeNull();
61
+ });
62
+
63
+ it("omits action wrapper when action not provided", () => {
64
+ const { container } = render(<EmptyState description="No items" />);
65
+ const actionWrapper = container.querySelector('[data-slot="empty-state-action"]');
66
+ expect(actionWrapper).toBeNull();
67
+ });
68
+
69
+ it("merges custom className", () => {
70
+ const { container } = render(
71
+ <EmptyState description="No items" className="my-custom-class" />
72
+ );
73
+ const el = container.querySelector('[data-slot="empty-state"]')!;
74
+ expect(el.classList.contains("my-custom-class")).toBe(true);
75
+ });
76
+ });
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import React from "react";
3
+ import { render, screen, fireEvent } from "@testing-library/react";
4
+ import { FilterChip } from "../filter-chip";
5
+
6
+ describe("FilterChip", () => {
7
+ it("renders with data-slot='filter-chip'", () => {
8
+ const { container } = render(<FilterChip>Status</FilterChip>);
9
+ const el = container.querySelector('[data-slot="filter-chip"]');
10
+ expect(el).not.toBeNull();
11
+ });
12
+
13
+ it("renders children as label text", () => {
14
+ render(<FilterChip>Status</FilterChip>);
15
+ expect(screen.getByText("Status")).toBeDefined();
16
+ });
17
+
18
+ it("renders default Filter icon (svg present)", () => {
19
+ const { container } = render(<FilterChip>Status</FilterChip>);
20
+ const svgs = container.querySelectorAll("svg");
21
+ expect(svgs.length).toBeGreaterThanOrEqual(1);
22
+ });
23
+
24
+ it("renders ChevronDown icon", () => {
25
+ const { container } = render(<FilterChip>Status</FilterChip>);
26
+ // Filter + ChevronDown = at least 2 svgs
27
+ const svgs = container.querySelectorAll("svg");
28
+ expect(svgs.length).toBe(2);
29
+ });
30
+
31
+ it("fires onClick when clicked", () => {
32
+ const handler = vi.fn();
33
+ render(<FilterChip onClick={handler}>Status</FilterChip>);
34
+ fireEvent.click(screen.getByText("Status"));
35
+ expect(handler).toHaveBeenCalledTimes(1);
36
+ });
37
+
38
+ it("has type='button'", () => {
39
+ const { container } = render(<FilterChip>Status</FilterChip>);
40
+ const btn = container.querySelector('[data-slot="filter-chip"]') as HTMLButtonElement;
41
+ expect(btn.type).toBe("button");
42
+ });
43
+
44
+ it("applies data-active attribute when active=true", () => {
45
+ const { container } = render(<FilterChip active>Status</FilterChip>);
46
+ const el = container.querySelector('[data-slot="filter-chip"]')!;
47
+ expect(el.getAttribute("data-active")).toBe("");
48
+ });
49
+
50
+ it("does not have data-active when active is false/undefined", () => {
51
+ const { container } = render(<FilterChip>Status</FilterChip>);
52
+ const el = container.querySelector('[data-slot="filter-chip"]')!;
53
+ expect(el.hasAttribute("data-active")).toBe(false);
54
+ });
55
+
56
+ it("merges custom className", () => {
57
+ const { container } = render(<FilterChip className="my-custom">Status</FilterChip>);
58
+ const el = container.querySelector('[data-slot="filter-chip"]')!;
59
+ expect(el.classList.contains("my-custom")).toBe(true);
60
+ });
61
+
62
+ it("sets aria-pressed when active", () => {
63
+ const { container } = render(<FilterChip active>Status</FilterChip>);
64
+ const el = container.querySelector('[data-slot="filter-chip"]')!;
65
+ expect(el.getAttribute("aria-pressed")).toBe("true");
66
+ });
67
+
68
+ it("sets aria-pressed=false when not active", () => {
69
+ const { container } = render(<FilterChip active={false}>Status</FilterChip>);
70
+ const el = container.querySelector('[data-slot="filter-chip"]')!;
71
+ expect(el.getAttribute("aria-pressed")).toBe("false");
72
+ });
73
+ });
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import React from "react";
3
+ import { render, screen, fireEvent } from "@testing-library/react";
4
+ import { InlineBanner } from "../inline-banner";
5
+
6
+ describe("InlineBanner", () => {
7
+ it("renders with data-slot='inline-banner'", () => {
8
+ const { container } = render(<InlineBanner>Test</InlineBanner>);
9
+ const el = container.querySelector('[data-slot="inline-banner"]');
10
+ expect(el).not.toBeNull();
11
+ });
12
+
13
+ it("defaults to variant='warning'", () => {
14
+ const { container } = render(<InlineBanner>Test</InlineBanner>);
15
+ const el = container.querySelector('[data-slot="inline-banner"]')!;
16
+ expect(el.getAttribute("data-variant")).toBe("warning");
17
+ });
18
+
19
+ it("renders children content", () => {
20
+ render(<InlineBanner>Important message</InlineBanner>);
21
+ expect(screen.getByText("Important message")).not.toBeNull();
22
+ });
23
+
24
+ it("shows dismiss button when onDismiss provided", () => {
25
+ const { container } = render(
26
+ <InlineBanner onDismiss={() => {}}>Test</InlineBanner>
27
+ );
28
+ const dismissBtn = container.querySelector('[data-slot="inline-banner-dismiss"]');
29
+ expect(dismissBtn).not.toBeNull();
30
+ });
31
+
32
+ it("dismiss button fires onDismiss on click", () => {
33
+ const onDismiss = vi.fn();
34
+ const { container } = render(
35
+ <InlineBanner onDismiss={onDismiss}>Test</InlineBanner>
36
+ );
37
+ const dismissBtn = container.querySelector('[data-slot="inline-banner-dismiss"]')!;
38
+ fireEvent.click(dismissBtn);
39
+ expect(onDismiss).toHaveBeenCalledTimes(1);
40
+ });
41
+
42
+ it("dismiss button has aria-label='Dismiss'", () => {
43
+ const { container } = render(
44
+ <InlineBanner onDismiss={() => {}}>Test</InlineBanner>
45
+ );
46
+ const dismissBtn = container.querySelector('[data-slot="inline-banner-dismiss"]')!;
47
+ expect(dismissBtn.getAttribute("aria-label")).toBe("Dismiss");
48
+ });
49
+
50
+ it("dismiss button has type='button'", () => {
51
+ const { container } = render(
52
+ <InlineBanner onDismiss={() => {}}>Test</InlineBanner>
53
+ );
54
+ const dismissBtn = container.querySelector('[data-slot="inline-banner-dismiss"]')!;
55
+ expect(dismissBtn.getAttribute("type")).toBe("button");
56
+ });
57
+
58
+ it("hides dismiss button when onDismiss not provided", () => {
59
+ const { container } = render(<InlineBanner>Test</InlineBanner>);
60
+ const dismissBtn = container.querySelector('[data-slot="inline-banner-dismiss"]');
61
+ expect(dismissBtn).toBeNull();
62
+ });
63
+
64
+ it("sets data-variant attribute correctly", () => {
65
+ const variants = ["info", "warning", "destructive"] as const;
66
+ for (const variant of variants) {
67
+ const { container, unmount } = render(
68
+ <InlineBanner variant={variant}>Test</InlineBanner>
69
+ );
70
+ const el = container.querySelector('[data-slot="inline-banner"]')!;
71
+ expect(el.getAttribute("data-variant")).toBe(variant);
72
+ unmount();
73
+ }
74
+ });
75
+
76
+ it("merges custom className", () => {
77
+ const { container } = render(
78
+ <InlineBanner className="my-custom-class">Test</InlineBanner>
79
+ );
80
+ const el = container.querySelector('[data-slot="inline-banner"]')!;
81
+ expect(el.classList.contains("my-custom-class")).toBe(true);
82
+ });
83
+
84
+ it("has role='alert' for warning variant", () => {
85
+ const { container } = render(<InlineBanner variant="warning">Test</InlineBanner>);
86
+ const el = container.querySelector('[data-slot="inline-banner"]')!;
87
+ expect(el.getAttribute("role")).toBe("alert");
88
+ });
89
+
90
+ it("has role='alert' for destructive variant", () => {
91
+ const { container } = render(<InlineBanner variant="destructive">Test</InlineBanner>);
92
+ const el = container.querySelector('[data-slot="inline-banner"]')!;
93
+ expect(el.getAttribute("role")).toBe("alert");
94
+ });
95
+
96
+ it("has role='status' for info variant", () => {
97
+ const { container } = render(<InlineBanner variant="info">Test</InlineBanner>);
98
+ const el = container.querySelector('[data-slot="inline-banner"]')!;
99
+ expect(el.getAttribute("role")).toBe("status");
100
+ });
101
+
102
+ it("renders custom icon when provided", () => {
103
+ const { container } = render(
104
+ <InlineBanner icon={<span data-testid="custom-icon">!</span>}>Test</InlineBanner>
105
+ );
106
+ expect(screen.getByTestId("custom-icon")).not.toBeNull();
107
+ const iconSlot = container.querySelector('[data-slot="inline-banner-icon"]');
108
+ expect(iconSlot).not.toBeNull();
109
+ });
110
+ });