@devalok/shilp-sutra 0.57.0 → 0.60.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 (288) hide show
  1. package/MIGRATION.md +163 -0
  2. package/dist/_chunks/badge-group.js +75 -188
  3. package/dist/_chunks/badge-group.js.map +1 -1
  4. package/dist/_chunks/chat.js +244 -243
  5. package/dist/_chunks/chat.js.map +1 -1
  6. package/dist/_chunks/image-preview.js +71 -70
  7. package/dist/_chunks/image-preview.js.map +1 -1
  8. package/dist/_chunks/motion-preference.js +15 -0
  9. package/dist/_chunks/motion-preference.js.map +1 -0
  10. package/dist/_chunks/motion-provider.js +1 -1
  11. package/dist/_chunks/motion-provider.js.map +1 -1
  12. package/dist/_chunks/popover.js +50 -49
  13. package/dist/_chunks/popover.js.map +1 -1
  14. package/dist/_chunks/primitives-internal.js +29 -29
  15. package/dist/_chunks/primitives-internal.js.map +1 -1
  16. package/dist/_chunks/shared.js +48 -47
  17. package/dist/_chunks/shared.js.map +1 -1
  18. package/dist/_chunks/success.js +34 -34
  19. package/dist/_chunks/tree-view.js +1 -1
  20. package/dist/_chunks/tree-view.js.map +1 -1
  21. package/dist/ai/block-renderer.js +8 -8
  22. package/dist/ai/command-bar.js +65 -65
  23. package/dist/ai/command-bar.js.map +1 -1
  24. package/dist/ai/conversation.js +35 -35
  25. package/dist/ai/conversation.js.map +1 -1
  26. package/dist/ai/index.js +4 -4
  27. package/dist/composed/activity-feed.d.ts.map +1 -1
  28. package/dist/composed/activity-feed.js +108 -107
  29. package/dist/composed/activity-feed.js.map +1 -1
  30. package/dist/composed/avatar-group.js +3 -3
  31. package/dist/composed/avatar-group.js.map +1 -1
  32. package/dist/composed/bulk-action-bar.d.ts +9 -35
  33. package/dist/composed/bulk-action-bar.d.ts.map +1 -1
  34. package/dist/composed/bulk-action-bar.js +2 -139
  35. package/dist/composed/command-palette.js +137 -137
  36. package/dist/composed/command-palette.js.map +1 -1
  37. package/dist/composed/content-card.js +1 -1
  38. package/dist/composed/content-card.js.map +1 -1
  39. package/dist/composed/date-picker/index.js +5 -5
  40. package/dist/composed/date-picker/index.js.map +1 -1
  41. package/dist/composed/empty-state.js +4 -4
  42. package/dist/composed/file-preview/image-preview.d.ts.map +1 -1
  43. package/dist/composed/file-preview/shared.d.ts.map +1 -1
  44. package/dist/composed/file-preview/video-preview.d.ts.map +1 -1
  45. package/dist/composed/file-preview.js +151 -150
  46. package/dist/composed/file-preview.js.map +1 -1
  47. package/dist/composed/filter-bar.d.ts.map +1 -1
  48. package/dist/composed/filter-bar.js +53 -52
  49. package/dist/composed/filter-bar.js.map +1 -1
  50. package/dist/composed/form-section.d.ts.map +1 -1
  51. package/dist/composed/form-section.js +38 -37
  52. package/dist/composed/form-section.js.map +1 -1
  53. package/dist/composed/index.js +6 -5
  54. package/dist/composed/master-detail.d.ts.map +1 -1
  55. package/dist/composed/master-detail.js +1 -1
  56. package/dist/composed/master-detail.js.map +1 -1
  57. package/dist/composed/multi-select-popover.d.ts.map +1 -1
  58. package/dist/composed/multi-select-popover.js +109 -108
  59. package/dist/composed/multi-select-popover.js.map +1 -1
  60. package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
  61. package/dist/composed/rich-chat-input/reply-banner.d.ts.map +1 -1
  62. package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
  63. package/dist/composed/rich-chat-input.d.ts.map +1 -1
  64. package/dist/composed/rich-chat-input.js +467 -466
  65. package/dist/composed/rich-chat-input.js.map +1 -1
  66. package/dist/composed/rich-text-editor.js +3 -3
  67. package/dist/composed/rich-text-editor.js.map +1 -1
  68. package/dist/composed/schedule-view.js +2 -2
  69. package/dist/composed/schedule-view.js.map +1 -1
  70. package/dist/composed/status-badge.d.ts.map +1 -1
  71. package/dist/composed/status-badge.js +29 -28
  72. package/dist/composed/status-badge.js.map +1 -1
  73. package/dist/motion/index.d.ts +1 -0
  74. package/dist/motion/index.d.ts.map +1 -1
  75. package/dist/motion/index.js +2 -15
  76. package/dist/motion/motion-preference.d.ts +36 -0
  77. package/dist/motion/motion-preference.d.ts.map +1 -0
  78. package/dist/motion/motion-provider.d.ts +2 -1
  79. package/dist/motion/motion-provider.d.ts.map +1 -1
  80. package/dist/motion/primitives-index.js +103 -102
  81. package/dist/motion/primitives-index.js.map +1 -1
  82. package/dist/motion/primitives.d.ts.map +1 -1
  83. package/dist/shell/bottom-navbar.js +19 -19
  84. package/dist/shell/bottom-navbar.js.map +1 -1
  85. package/dist/shell/index.d.ts +1 -1
  86. package/dist/shell/index.d.ts.map +1 -1
  87. package/dist/shell/notification-center.d.ts +17 -0
  88. package/dist/shell/notification-center.d.ts.map +1 -1
  89. package/dist/shell/notification-center.js +109 -104
  90. package/dist/shell/notification-center.js.map +1 -1
  91. package/dist/shell/notification-preferences.js +1 -1
  92. package/dist/shell/notification-preferences.js.map +1 -1
  93. package/dist/shell/top-bar.js +1 -1
  94. package/dist/shell/top-bar.js.map +1 -1
  95. package/dist/tokens/index.css +1 -0
  96. package/dist/tokens/palettes.css +292 -0
  97. package/dist/tokens/semantic.css +157 -11
  98. package/dist/tokens/shilp-sutra.css +1 -0
  99. package/dist/tokens/utilities.css +10 -7
  100. package/dist/ui/accordion.d.ts.map +1 -1
  101. package/dist/ui/accordion.js +27 -26
  102. package/dist/ui/accordion.js.map +1 -1
  103. package/dist/ui/alert-dialog.d.ts.map +1 -1
  104. package/dist/ui/alert-dialog.js +60 -59
  105. package/dist/ui/alert-dialog.js.map +1 -1
  106. package/dist/ui/alert.d.ts.map +1 -1
  107. package/dist/ui/alert.js +55 -113
  108. package/dist/ui/alert.js.map +1 -1
  109. package/dist/ui/autocomplete.js +80 -80
  110. package/dist/ui/autocomplete.js.map +1 -1
  111. package/dist/ui/avatar.d.ts.map +1 -1
  112. package/dist/ui/avatar.js +71 -70
  113. package/dist/ui/avatar.js.map +1 -1
  114. package/dist/ui/badge-indicator.d.ts +4 -8
  115. package/dist/ui/badge-indicator.d.ts.map +1 -1
  116. package/dist/ui/badge-indicator.js +3 -8
  117. package/dist/ui/badge-indicator.js.map +1 -1
  118. package/dist/ui/badge.d.ts +8 -1
  119. package/dist/ui/badge.d.ts.map +1 -1
  120. package/dist/ui/banner.d.ts.map +1 -1
  121. package/dist/ui/banner.js +42 -40
  122. package/dist/ui/banner.js.map +1 -1
  123. package/dist/ui/bulk-action-bar.d.ts +59 -0
  124. package/dist/ui/bulk-action-bar.d.ts.map +1 -0
  125. package/dist/ui/bulk-action-bar.js +145 -0
  126. package/dist/ui/bulk-action-bar.js.map +1 -0
  127. package/dist/ui/button-group.d.ts.map +1 -1
  128. package/dist/ui/button-group.js +3 -23
  129. package/dist/ui/button-group.js.map +1 -1
  130. package/dist/ui/button.d.ts +10 -1
  131. package/dist/ui/button.d.ts.map +1 -1
  132. package/dist/ui/button.js +79 -212
  133. package/dist/ui/button.js.map +1 -1
  134. package/dist/ui/card.d.ts.map +1 -1
  135. package/dist/ui/card.js +75 -72
  136. package/dist/ui/card.js.map +1 -1
  137. package/dist/ui/charts/radar-chart.d.ts.map +1 -1
  138. package/dist/ui/charts/radar-chart.js +1 -1
  139. package/dist/ui/charts/radar-chart.js.map +1 -1
  140. package/dist/ui/chat/message-list.d.ts.map +1 -1
  141. package/dist/ui/chat/message.d.ts.map +1 -1
  142. package/dist/ui/chat/typing-indicator.d.ts.map +1 -1
  143. package/dist/ui/checkbox.d.ts.map +1 -1
  144. package/dist/ui/checkbox.js +1 -1
  145. package/dist/ui/checkbox.js.map +1 -1
  146. package/dist/ui/code.js +1 -1
  147. package/dist/ui/code.js.map +1 -1
  148. package/dist/ui/color-input.d.ts.map +1 -1
  149. package/dist/ui/color-input.js +151 -150
  150. package/dist/ui/color-input.js.map +1 -1
  151. package/dist/ui/combobox.d.ts +14 -0
  152. package/dist/ui/combobox.d.ts.map +1 -1
  153. package/dist/ui/combobox.js +162 -158
  154. package/dist/ui/combobox.js.map +1 -1
  155. package/dist/ui/context-menu.d.ts.map +1 -1
  156. package/dist/ui/context-menu.js +70 -69
  157. package/dist/ui/context-menu.js.map +1 -1
  158. package/dist/ui/data-table-body.d.ts.map +1 -1
  159. package/dist/ui/data-table-body.js +4 -3
  160. package/dist/ui/data-table-body.js.map +1 -1
  161. package/dist/ui/data-table-bulk-actions.d.ts +26 -1
  162. package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
  163. package/dist/ui/data-table-bulk-actions.js +21 -46
  164. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  165. package/dist/ui/data-table-context.d.ts +36 -2
  166. package/dist/ui/data-table-context.d.ts.map +1 -1
  167. package/dist/ui/data-table-context.js +25 -21
  168. package/dist/ui/data-table-context.js.map +1 -1
  169. package/dist/ui/data-table-header.d.ts.map +1 -1
  170. package/dist/ui/data-table-header.js +42 -40
  171. package/dist/ui/data-table-header.js.map +1 -1
  172. package/dist/ui/data-table-pagination.js +2 -2
  173. package/dist/ui/data-table-pagination.js.map +1 -1
  174. package/dist/ui/dialog.d.ts.map +1 -1
  175. package/dist/ui/dialog.js +61 -60
  176. package/dist/ui/dialog.js.map +1 -1
  177. package/dist/ui/dot.d.ts.map +1 -1
  178. package/dist/ui/dot.js +10 -41
  179. package/dist/ui/dot.js.map +1 -1
  180. package/dist/ui/dropdown-menu.d.ts.map +1 -1
  181. package/dist/ui/dropdown-menu.js +79 -78
  182. package/dist/ui/dropdown-menu.js.map +1 -1
  183. package/dist/ui/file-upload.d.ts.map +1 -1
  184. package/dist/ui/file-upload.js +116 -115
  185. package/dist/ui/file-upload.js.map +1 -1
  186. package/dist/ui/form.d.ts.map +1 -1
  187. package/dist/ui/form.js +27 -26
  188. package/dist/ui/form.js.map +1 -1
  189. package/dist/ui/hover-card.d.ts.map +1 -1
  190. package/dist/ui/hover-card.js +26 -25
  191. package/dist/ui/hover-card.js.map +1 -1
  192. package/dist/ui/icon.js +7 -7
  193. package/dist/ui/index.d.ts +3 -2
  194. package/dist/ui/index.d.ts.map +1 -1
  195. package/dist/ui/index.js +60 -59
  196. package/dist/ui/index.js.map +1 -1
  197. package/dist/ui/input.d.ts.map +1 -1
  198. package/dist/ui/input.js +7 -7
  199. package/dist/ui/input.js.map +1 -1
  200. package/dist/ui/menubar.d.ts.map +1 -1
  201. package/dist/ui/menubar.js +73 -72
  202. package/dist/ui/menubar.js.map +1 -1
  203. package/dist/ui/navigation-menu.d.ts.map +1 -1
  204. package/dist/ui/navigation-menu.js +80 -79
  205. package/dist/ui/navigation-menu.js.map +1 -1
  206. package/dist/ui/popover.d.ts.map +1 -1
  207. package/dist/ui/progress.d.ts.map +1 -1
  208. package/dist/ui/progress.js +72 -68
  209. package/dist/ui/progress.js.map +1 -1
  210. package/dist/ui/radio.d.ts.map +1 -1
  211. package/dist/ui/radio.js +35 -34
  212. package/dist/ui/radio.js.map +1 -1
  213. package/dist/ui/search-input.d.ts.map +1 -1
  214. package/dist/ui/search-input.js +28 -27
  215. package/dist/ui/search-input.js.map +1 -1
  216. package/dist/ui/select.d.ts.map +1 -1
  217. package/dist/ui/select.js +70 -69
  218. package/dist/ui/select.js.map +1 -1
  219. package/dist/ui/sidebar.d.ts.map +1 -1
  220. package/dist/ui/sidebar.js +4 -4
  221. package/dist/ui/sidebar.js.map +1 -1
  222. package/dist/ui/slider.d.ts +63 -9
  223. package/dist/ui/slider.d.ts.map +1 -1
  224. package/dist/ui/slider.js +74 -32
  225. package/dist/ui/slider.js.map +1 -1
  226. package/dist/ui/split-button.d.ts +8 -2
  227. package/dist/ui/split-button.d.ts.map +1 -1
  228. package/dist/ui/split-button.js +49 -95
  229. package/dist/ui/split-button.js.map +1 -1
  230. package/dist/ui/stat-card.d.ts.map +1 -1
  231. package/dist/ui/stat-card.js +89 -88
  232. package/dist/ui/stat-card.js.map +1 -1
  233. package/dist/ui/stat-flash.js +16 -16
  234. package/dist/ui/stepper.d.ts.map +1 -1
  235. package/dist/ui/stepper.js +63 -62
  236. package/dist/ui/stepper.js.map +1 -1
  237. package/dist/ui/switch.d.ts.map +1 -1
  238. package/dist/ui/switch.js +2 -2
  239. package/dist/ui/switch.js.map +1 -1
  240. package/dist/ui/table.d.ts.map +1 -1
  241. package/dist/ui/table.js +3 -3
  242. package/dist/ui/table.js.map +1 -1
  243. package/dist/ui/tabs.d.ts.map +1 -1
  244. package/dist/ui/tabs.js +67 -66
  245. package/dist/ui/tabs.js.map +1 -1
  246. package/dist/ui/textarea.d.ts.map +1 -1
  247. package/dist/ui/textarea.js +7 -7
  248. package/dist/ui/textarea.js.map +1 -1
  249. package/dist/ui/toast.d.ts.map +1 -1
  250. package/dist/ui/toast.js +196 -195
  251. package/dist/ui/toast.js.map +1 -1
  252. package/dist/ui/toggle.d.ts.map +1 -1
  253. package/dist/ui/toggle.js +24 -22
  254. package/dist/ui/toggle.js.map +1 -1
  255. package/dist/ui/tooltip.d.ts.map +1 -1
  256. package/dist/ui/tooltip.js +37 -36
  257. package/dist/ui/tooltip.js.map +1 -1
  258. package/dist/ui/tree-view/tree-item.d.ts.map +1 -1
  259. package/docs/components/composed/bulk-action-bar.md +14 -7
  260. package/docs/components/ui/bulk-action-bar.md +72 -0
  261. package/docs/recipes/customize-brand.md +58 -0
  262. package/docs/recipes/install-astro.md +1 -1
  263. package/docs/recipes/install-next-app-router.md +1 -1
  264. package/docs/recipes/install-next-pages.md +22 -1
  265. package/docs/recipes/install-remix.md +1 -1
  266. package/docs/recipes/install-tanstack-start.md +1 -1
  267. package/docs/recipes/install-vite.md +2 -1
  268. package/docs/recipes/server-components.md +7 -4
  269. package/docs/recipes/troubleshoot.md +1 -0
  270. package/llms.txt +2 -2
  271. package/make-kit/foundations/color.md +3 -1
  272. package/mcp-manifest.json +184 -151
  273. package/package.json +8 -2
  274. package/skill/SKILL.md +1 -1
  275. package/skill/references/components.md +2 -2
  276. package/skill/references/customize-brand.md +58 -0
  277. package/skill/references/server-components.md +7 -4
  278. package/skill/references/setup-astro.md +1 -1
  279. package/skill/references/setup-next-app-router.md +1 -1
  280. package/skill/references/setup-next-pages.md +22 -1
  281. package/skill/references/setup-remix.md +1 -1
  282. package/skill/references/setup-tanstack-start.md +1 -1
  283. package/skill/references/setup-vite.md +2 -1
  284. package/skill/references/troubleshoot.md +1 -0
  285. package/dist/composed/bulk-action-bar.js.map +0 -1
  286. package/dist/motion/check-motion-provider.d.ts +0 -2
  287. package/dist/motion/check-motion-provider.d.ts.map +0 -1
  288. package/dist/motion/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"notification-preferences.js","names":[],"sources":["../../src/shell/notification-preferences.tsx"],"sourcesContent":["'use client'\n\n/**\n * NotificationPreferences -- Manage notification rules per channel/project.\n *\n * Props-driven: accepts preferences array, projects, and callbacks.\n * No internal fetching -- the consumer is responsible for data loading.\n */\nimport { IconBell, IconMessage,IconPlus, IconTrash } from '@tabler/icons-react'\nimport * as React from 'react'\nimport { useState } from 'react'\n\nimport { Button } from '../ui/button'\nimport { Card, CardContent,CardHeader, CardTitle } from '../ui/card'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Icon } from '../ui/icon'\nimport { IconButton } from '../ui/icon-button'\nimport { cn } from '../ui/lib/utils'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../ui/select'\nimport { Spinner } from '../ui/spinner'\nimport { Switch } from '../ui/switch'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface NotificationPreference {\n id: string\n userId?: string\n projectId: string | null\n channel: string\n minTier: string\n muted: boolean\n}\n\nexport interface NotificationProject {\n id: string\n title: string\n}\n\nexport interface NotificationPreferencesProps\n extends Omit<React.ComponentPropsWithRef<'div'>, 'onSave'> {\n /** Current preference rules */\n preferences?: NotificationPreference[]\n /** Available projects for scoping rules */\n projects?: NotificationProject[]\n /** Whether data is loading */\n isLoading?: boolean\n /** Called to add or update a preference */\n onSave?: (preference: {\n projectId: string | null\n channel: string\n minTier: string\n muted: boolean\n }) => void | Promise<void>\n /** Called to toggle mute on a preference */\n onToggleMute?: (preference: NotificationPreference) => void | Promise<void>\n /** Called to update the tier on a preference */\n onUpdateTier?: (\n preference: NotificationPreference,\n newTier: string,\n ) => void | Promise<void>\n /** Called to delete a preference */\n onDelete?: (preferenceId: string) => void | Promise<void>\n /** Additional className */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------\n\nconst CHANNEL_LABELS: Record<string, { label: string; icon: React.ReactNode }> = {\n IN_APP: { label: 'In-App', icon: <Icon icon={IconBell} size=\"sm\" /> },\n GOOGLE_CHAT: { label: 'Google Chat', icon: <Icon icon={IconMessage} size=\"sm\" /> },\n}\n\nconst TIER_LABELS: Record<string, string> = {\n INFO: 'All (Info+)',\n IMPORTANT: 'Important+',\n CRITICAL: 'Critical only',\n}\n\n// -----------------------------------------------------------------------\n// NotificationPreferences\n// -----------------------------------------------------------------------\n\nconst NotificationPreferences = React.forwardRef<HTMLDivElement, NotificationPreferencesProps>(\n function NotificationPreferences(\n {\n preferences = [],\n projects = [],\n isLoading = false,\n onSave,\n onToggleMute,\n onUpdateTier,\n onDelete,\n className,\n ...props\n },\n ref,\n ) {\n const [showAddDialog, setShowAddDialog] = useState(false)\n const [newProjectId, setNewProjectId] = useState<string>('global')\n const [newChannel, setNewChannel] = useState<string>('IN_APP')\n const [newMinTier, setNewMinTier] = useState<string>('INFO')\n const [newMuted, setNewMuted] = useState(false)\n const [isSaving, setIsSaving] = useState(false)\n\n const handleAdd = async () => {\n if (!onSave) return\n setIsSaving(true)\n try {\n await onSave({\n projectId: newProjectId === 'global' ? null : newProjectId,\n channel: newChannel,\n minTier: newMinTier,\n muted: newMuted,\n })\n setShowAddDialog(false)\n resetForm()\n } catch (error) {\n console.error('[Preferences] Failed to save:', error)\n } finally {\n setIsSaving(false)\n }\n }\n\n const resetForm = () => {\n setNewProjectId('global')\n setNewChannel('IN_APP')\n setNewMinTier('INFO')\n setNewMuted(false)\n }\n\n const getProjectName = (projectId: string | null) => {\n if (!projectId) return 'Global (all projects)'\n const project = projects.find((p) => p.id === projectId)\n return project?.title || 'Unknown project'\n }\n\n return (\n <>\n <Card ref={ref} className={className} {...(props as Omit<typeof props, 'color'>)}>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <CardTitle className=\"text-body-md font-semibold\">\n Notification Preferences\n </CardTitle>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setShowAddDialog(true)}\n >\n <Icon icon={IconPlus} size=\"sm\" className=\"mr-ds-02b\" />\n Add Rule\n </Button>\n </CardHeader>\n <CardContent>\n {isLoading ? (\n <div className=\"flex items-center justify-center py-ds-07\">\n <Spinner />\n </div>\n ) : preferences.length === 0 ? (\n <div className=\"py-ds-07 text-center\">\n <p className=\"text-body-md text-surface-fg-subtle\">\n No custom preferences set. All notifications are delivered by\n default.\n </p>\n </div>\n ) : (\n <div className=\"flex flex-col\">\n {preferences.map((pref, i) => {\n const channelInfo =\n CHANNEL_LABELS[pref.channel] || CHANNEL_LABELS.IN_APP\n return (\n <div\n key={pref.id}\n className={cn(\n 'flex items-center gap-ds-05 py-ds-04',\n i < preferences.length - 1 &&\n 'border-b border-surface-border-strong',\n )}\n >\n {/* Channel icon */}\n <div className=\"flex h-ds-sm w-ds-sm shrink-0 items-center justify-center rounded-surface bg-surface-panel\">\n <span className=\"[&>svg]:h-ico-sm [&>svg]:w-ico-sm text-surface-fg-muted\" aria-hidden=\"true\">{channelInfo.icon}</span>\n </div>\n\n {/* Info */}\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-body-md font-medium text-surface-fg\">\n {channelInfo.label}\n </p>\n <p className=\"text-body-sm text-surface-fg-subtle\">\n {getProjectName(pref.projectId)}\n </p>\n </div>\n\n {/* Min tier */}\n <Select\n value={pref.minTier}\n onValueChange={(v) => onUpdateTier?.(pref, v)}\n >\n <SelectTrigger\n aria-label={`Minimum notification tier for ${channelInfo.label} on ${getProjectName(pref.projectId)}`}\n className=\"h-ds-xs-plus w-[130px] text-body-sm\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(TIER_LABELS).map(([key, label]) => (\n <SelectItem key={key} value={key}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Muted toggle */}\n <div className=\"flex items-center gap-ds-02b\">\n <span className=\"text-body-sm text-surface-fg-subtle\">\n {pref.muted ? 'Muted' : 'Active'}\n </span>\n <Switch\n aria-label={`${pref.muted ? 'Unmute' : 'Mute'} ${channelInfo.label} for ${getProjectName(pref.projectId)}`}\n checked={!pref.muted}\n onCheckedChange={() => onToggleMute?.(pref)}\n />\n </div>\n\n {/* Delete */}\n <IconButton\n aria-label={`Delete ${channelInfo.label} notification rule`}\n icon={<Icon icon={IconTrash} />}\n variant=\"ghost\"\n color=\"error\"\n size=\"sm\"\n onClick={() => onDelete?.(pref.id)}\n className=\"shrink-0\"\n />\n </div>\n )\n })}\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Add Preference Dialog */}\n <Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>Add Notification Rule</DialogTitle>\n <DialogDescription>\n Customize how you receive notifications for a specific channel\n and project.\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-ds-05 pt-ds-03\">\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-scope\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Scope\n </label>\n <Select value={newProjectId} onValueChange={setNewProjectId}>\n <SelectTrigger id=\"pref-scope\">\n <SelectValue placeholder=\"Select scope\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"global\">\n Global (all projects)\n </SelectItem>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-channel\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Channel\n </label>\n <Select value={newChannel} onValueChange={setNewChannel}>\n <SelectTrigger id=\"pref-channel\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"IN_APP\">In-App</SelectItem>\n <SelectItem value=\"GOOGLE_CHAT\">Google Chat</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-min-tier\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Minimum Tier\n </label>\n <Select value={newMinTier} onValueChange={setNewMinTier}>\n <SelectTrigger id=\"pref-min-tier\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(TIER_LABELS).map(([key, label]) => (\n <SelectItem key={key} value={key}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <label htmlFor=\"pref-muted\" className=\"text-body-md text-surface-fg\">\n Mute this channel\n </label>\n <Switch id=\"pref-muted\" checked={newMuted} onCheckedChange={setNewMuted} />\n </div>\n\n <div className=\"flex justify-end gap-ds-03 pt-ds-03\">\n <Button\n variant=\"ghost\"\n onClick={() => {\n setShowAddDialog(false)\n resetForm()\n }}\n >\n Cancel\n </Button>\n <Button\n onClick={handleAdd}\n disabled={isSaving}\n >\n {isSaving ? 'Saving...' : 'Save Rule'}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n },\n)\n\nNotificationPreferences.displayName = 'NotificationPreferences'\n\nexport { NotificationPreferences }\n"],"mappings":"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"}
1
+ {"version":3,"file":"notification-preferences.js","names":[],"sources":["../../src/shell/notification-preferences.tsx"],"sourcesContent":["'use client'\n\n/**\n * NotificationPreferences -- Manage notification rules per channel/project.\n *\n * Props-driven: accepts preferences array, projects, and callbacks.\n * No internal fetching -- the consumer is responsible for data loading.\n */\nimport { IconBell, IconMessage,IconPlus, IconTrash } from '@tabler/icons-react'\nimport * as React from 'react'\nimport { useState } from 'react'\n\nimport { Button } from '../ui/button'\nimport { Card, CardContent,CardHeader, CardTitle } from '../ui/card'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Icon } from '../ui/icon'\nimport { IconButton } from '../ui/icon-button'\nimport { cn } from '../ui/lib/utils'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../ui/select'\nimport { Spinner } from '../ui/spinner'\nimport { Switch } from '../ui/switch'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface NotificationPreference {\n id: string\n userId?: string\n projectId: string | null\n channel: string\n minTier: string\n muted: boolean\n}\n\nexport interface NotificationProject {\n id: string\n title: string\n}\n\nexport interface NotificationPreferencesProps\n extends Omit<React.ComponentPropsWithRef<'div'>, 'onSave'> {\n /** Current preference rules */\n preferences?: NotificationPreference[]\n /** Available projects for scoping rules */\n projects?: NotificationProject[]\n /** Whether data is loading */\n isLoading?: boolean\n /** Called to add or update a preference */\n onSave?: (preference: {\n projectId: string | null\n channel: string\n minTier: string\n muted: boolean\n }) => void | Promise<void>\n /** Called to toggle mute on a preference */\n onToggleMute?: (preference: NotificationPreference) => void | Promise<void>\n /** Called to update the tier on a preference */\n onUpdateTier?: (\n preference: NotificationPreference,\n newTier: string,\n ) => void | Promise<void>\n /** Called to delete a preference */\n onDelete?: (preferenceId: string) => void | Promise<void>\n /** Additional className */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------\n\nconst CHANNEL_LABELS: Record<string, { label: string; icon: React.ReactNode }> = {\n IN_APP: { label: 'In-App', icon: <Icon icon={IconBell} size=\"sm\" /> },\n GOOGLE_CHAT: { label: 'Google Chat', icon: <Icon icon={IconMessage} size=\"sm\" /> },\n}\n\nconst TIER_LABELS: Record<string, string> = {\n INFO: 'All (Info+)',\n IMPORTANT: 'Important+',\n CRITICAL: 'Critical only',\n}\n\n// -----------------------------------------------------------------------\n// NotificationPreferences\n// -----------------------------------------------------------------------\n\nconst NotificationPreferences = React.forwardRef<HTMLDivElement, NotificationPreferencesProps>(\n function NotificationPreferences(\n {\n preferences = [],\n projects = [],\n isLoading = false,\n onSave,\n onToggleMute,\n onUpdateTier,\n onDelete,\n className,\n ...props\n },\n ref,\n ) {\n const [showAddDialog, setShowAddDialog] = useState(false)\n const [newProjectId, setNewProjectId] = useState<string>('global')\n const [newChannel, setNewChannel] = useState<string>('IN_APP')\n const [newMinTier, setNewMinTier] = useState<string>('INFO')\n const [newMuted, setNewMuted] = useState(false)\n const [isSaving, setIsSaving] = useState(false)\n\n const handleAdd = async () => {\n if (!onSave) return\n setIsSaving(true)\n try {\n await onSave({\n projectId: newProjectId === 'global' ? null : newProjectId,\n channel: newChannel,\n minTier: newMinTier,\n muted: newMuted,\n })\n setShowAddDialog(false)\n resetForm()\n } catch (error) {\n console.error('[Preferences] Failed to save:', error)\n } finally {\n setIsSaving(false)\n }\n }\n\n const resetForm = () => {\n setNewProjectId('global')\n setNewChannel('IN_APP')\n setNewMinTier('INFO')\n setNewMuted(false)\n }\n\n const getProjectName = (projectId: string | null) => {\n if (!projectId) return 'Global (all projects)'\n const project = projects.find((p) => p.id === projectId)\n return project?.title || 'Unknown project'\n }\n\n return (\n <>\n <Card ref={ref} className={className} {...(props as Omit<typeof props, 'color'>)}>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <CardTitle className=\"text-body-md font-semibold\">\n Notification Preferences\n </CardTitle>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setShowAddDialog(true)}\n >\n <Icon icon={IconPlus} size=\"sm\" className=\"mr-ds-02b\" />\n Add Rule\n </Button>\n </CardHeader>\n <CardContent>\n {isLoading ? (\n <div className=\"flex items-center justify-center py-ds-07\">\n <Spinner />\n </div>\n ) : preferences.length === 0 ? (\n <div className=\"py-ds-07 text-center\">\n <p className=\"text-body-md text-surface-fg-subtle\">\n No custom preferences set. All notifications are delivered by\n default.\n </p>\n </div>\n ) : (\n <div className=\"flex flex-col\">\n {preferences.map((pref, i) => {\n const channelInfo =\n CHANNEL_LABELS[pref.channel] || CHANNEL_LABELS.IN_APP\n return (\n <div\n key={pref.id}\n className={cn(\n 'flex items-center gap-ds-05 py-ds-04',\n i < preferences.length - 1 &&\n 'border-b border-surface-border-strong',\n )}\n >\n {/* Channel icon */}\n <div className=\"flex h-ds-sm w-ds-sm shrink-0 items-center justify-center rounded-surface bg-surface-panel-hover\">\n <span className=\"[&>svg]:h-ico-sm [&>svg]:w-ico-sm text-surface-fg-muted\" aria-hidden=\"true\">{channelInfo.icon}</span>\n </div>\n\n {/* Info */}\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-body-md font-medium text-surface-fg\">\n {channelInfo.label}\n </p>\n <p className=\"text-body-sm text-surface-fg-subtle\">\n {getProjectName(pref.projectId)}\n </p>\n </div>\n\n {/* Min tier */}\n <Select\n value={pref.minTier}\n onValueChange={(v) => onUpdateTier?.(pref, v)}\n >\n <SelectTrigger\n aria-label={`Minimum notification tier for ${channelInfo.label} on ${getProjectName(pref.projectId)}`}\n className=\"h-ds-xs-plus w-[130px] text-body-sm\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(TIER_LABELS).map(([key, label]) => (\n <SelectItem key={key} value={key}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Muted toggle */}\n <div className=\"flex items-center gap-ds-02b\">\n <span className=\"text-body-sm text-surface-fg-subtle\">\n {pref.muted ? 'Muted' : 'Active'}\n </span>\n <Switch\n aria-label={`${pref.muted ? 'Unmute' : 'Mute'} ${channelInfo.label} for ${getProjectName(pref.projectId)}`}\n checked={!pref.muted}\n onCheckedChange={() => onToggleMute?.(pref)}\n />\n </div>\n\n {/* Delete */}\n <IconButton\n aria-label={`Delete ${channelInfo.label} notification rule`}\n icon={<Icon icon={IconTrash} />}\n variant=\"ghost\"\n color=\"error\"\n size=\"sm\"\n onClick={() => onDelete?.(pref.id)}\n className=\"shrink-0\"\n />\n </div>\n )\n })}\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Add Preference Dialog */}\n <Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>Add Notification Rule</DialogTitle>\n <DialogDescription>\n Customize how you receive notifications for a specific channel\n and project.\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-ds-05 pt-ds-03\">\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-scope\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Scope\n </label>\n <Select value={newProjectId} onValueChange={setNewProjectId}>\n <SelectTrigger id=\"pref-scope\">\n <SelectValue placeholder=\"Select scope\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"global\">\n Global (all projects)\n </SelectItem>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-channel\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Channel\n </label>\n <Select value={newChannel} onValueChange={setNewChannel}>\n <SelectTrigger id=\"pref-channel\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"IN_APP\">In-App</SelectItem>\n <SelectItem value=\"GOOGLE_CHAT\">Google Chat</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-min-tier\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Minimum Tier\n </label>\n <Select value={newMinTier} onValueChange={setNewMinTier}>\n <SelectTrigger id=\"pref-min-tier\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(TIER_LABELS).map(([key, label]) => (\n <SelectItem key={key} value={key}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <label htmlFor=\"pref-muted\" className=\"text-body-md text-surface-fg\">\n Mute this channel\n </label>\n <Switch id=\"pref-muted\" checked={newMuted} onCheckedChange={setNewMuted} />\n </div>\n\n <div className=\"flex justify-end gap-ds-03 pt-ds-03\">\n <Button\n variant=\"ghost\"\n onClick={() => {\n setShowAddDialog(false)\n resetForm()\n }}\n >\n Cancel\n </Button>\n <Button\n onClick={handleAdd}\n disabled={isSaving}\n >\n {isSaving ? 'Saving...' : 'Save Rule'}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n },\n)\n\nNotificationPreferences.displayName = 'NotificationPreferences'\n\nexport { NotificationPreferences }\n"],"mappings":"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"}
@@ -60,7 +60,7 @@ var j = _.forwardRef(({ icon: n, tooltip: r, className: i, ...a }, s) => /* @__P
60
60
  ref: s,
61
61
  type: "button",
62
62
  "aria-label": r,
63
- className: t("flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-card-strong bg-surface-panel-hover text-surface-fg-muted transition-[color,background-color,border-color,transform] hover:bg-surface-panel-active active:scale-90 duration-fast-01 ease-productive-standard", i),
63
+ className: t("flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-surface-border-interactive bg-surface-panel-hover text-surface-fg-muted transition-[color,background-color,border-color,transform] hover:bg-surface-panel-active active:scale-90 duration-fast-01 ease-productive-standard", i),
64
64
  ...a,
65
65
  children: /* @__PURE__ */ y("span", {
66
66
  className: "[&>svg]:h-ico-sm [&>svg]:w-ico-sm",
@@ -1 +1 @@
1
- {"version":3,"file":"top-bar.js","names":[],"sources":["../../src/shell/top-bar.tsx"],"sourcesContent":["'use client'\n\n/**\n * TopBar v2 — Composition-based application top bar.\n *\n * Subcomponents: TopBar.Left, TopBar.Center, TopBar.Right,\n * TopBar.Section, TopBar.IconButton, TopBar.Title, TopBar.UserMenu.\n */\nimport { IconLogout, IconMoon, IconSun,IconUser } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { useColorMode } from '../hooks/use-color-mode'\nimport { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from '../ui/tooltip'\nimport { TruncatedText } from '../ui/truncated-text'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface TopBarUser {\n name: string\n email?: string\n image?: string | null\n}\n\nexport interface UserMenuItem {\n /** Display label */\n label: string\n /** Optional icon. Accepts any `IconInput`. */\n icon?: IconInput\n /** Navigate via onNavigate when clicked */\n href?: string\n /** Custom click handler */\n onClick?: () => void\n /** Render a separator before this item */\n separator?: boolean\n /** Text color semantic token (e.g. \"error\") — maps to text-{color} */\n color?: string\n /** Badge content — string for count, true for dot indicator */\n badge?: string | boolean\n /** Whether the item is disabled */\n disabled?: boolean\n}\n\n// -----------------------------------------------------------------------\n// TopBar (root)\n// -----------------------------------------------------------------------\n\ninterface TopBarRootProps extends React.HTMLAttributes<HTMLElement> {\n children: React.ReactNode\n className?: string\n}\n\nconst TopBarRoot = React.forwardRef<HTMLElement, TopBarRootProps>(\n ({ className, children, ...props }, ref) => {\n // Detect if a Center zone is present to switch to grid layout\n const hasCenter = React.Children.toArray(children).some(\n (child) => React.isValidElement(child) && child.type === TopBarCenter,\n )\n\n return (\n <header\n {...props}\n ref={ref}\n className={cn(\n 'z-sticky w-full border-b border-surface-border-strong bg-surface-base px-ds-05 py-ds-04 md:px-ds-06',\n hasCenter\n ? 'grid grid-cols-[1fr_auto_1fr] items-center'\n : 'flex items-center',\n className,\n )}\n >\n {children}\n </header>\n )\n },\n)\nTopBarRoot.displayName = 'TopBar'\n\n// -----------------------------------------------------------------------\n// TopBar.Left\n// -----------------------------------------------------------------------\n\ninterface TopBarLeftProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst TopBarLeft = React.forwardRef<HTMLDivElement, TopBarLeftProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex items-center gap-ds-04', className)}\n {...props}\n >\n {children}\n </div>\n ),\n)\nTopBarLeft.displayName = 'TopBar.Left'\n\n// -----------------------------------------------------------------------\n// TopBar.Center\n// -----------------------------------------------------------------------\n\ninterface TopBarCenterProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst TopBarCenter = React.forwardRef<HTMLDivElement, TopBarCenterProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex items-center justify-center px-ds-04', className)}\n {...props}\n >\n {children}\n </div>\n ),\n)\nTopBarCenter.displayName = 'TopBar.Center'\n\n// -----------------------------------------------------------------------\n// TopBar.Right\n// -----------------------------------------------------------------------\n\ninterface TopBarRightProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst TopBarRight = React.forwardRef<HTMLDivElement, TopBarRightProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('ml-auto flex items-center gap-ds-04', className)}\n {...props}\n >\n {children}\n </div>\n ),\n)\nTopBarRight.displayName = 'TopBar.Right'\n\n// -----------------------------------------------------------------------\n// TopBar.Section\n// -----------------------------------------------------------------------\n\nconst gapMap = {\n tight: 'gap-ds-02',\n default: 'gap-ds-04',\n loose: 'gap-ds-06',\n} as const\n\ninterface TopBarSectionProps extends React.HTMLAttributes<HTMLDivElement> {\n gap?: keyof typeof gapMap\n children: React.ReactNode\n}\n\nconst TopBarSection = React.forwardRef<HTMLDivElement, TopBarSectionProps>(\n ({ gap = 'default', className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex items-center', gapMap[gap], className)}\n {...props}\n >\n {children}\n </div>\n ),\n)\nTopBarSection.displayName = 'TopBar.Section'\n\n// -----------------------------------------------------------------------\n// TopBar.IconButton\n// -----------------------------------------------------------------------\n\ninterface TopBarIconButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n icon: IconInput\n tooltip: string\n}\n\nconst TopBarIconButton = React.forwardRef<\n HTMLButtonElement,\n TopBarIconButtonProps\n>(({ icon, tooltip, className, ...props }, ref) => (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n ref={ref}\n type=\"button\"\n aria-label={tooltip}\n className={cn(\n 'flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-card-strong bg-surface-panel-hover text-surface-fg-muted transition-[color,background-color,border-color,transform] hover:bg-surface-panel-active active:scale-90 duration-fast-01 ease-productive-standard',\n className,\n )}\n {...props}\n >\n <span className=\"[&>svg]:h-ico-sm [&>svg]:w-ico-sm\" aria-hidden=\"true\">\n <IconProvider size=\"sm\">{normalizeIcon(icon)}</IconProvider>\n </span>\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" align=\"center\">\n {tooltip}\n </TooltipContent>\n </Tooltip>\n))\nTopBarIconButton.displayName = 'TopBar.IconButton'\n\n// -----------------------------------------------------------------------\n// TopBar.Title\n// -----------------------------------------------------------------------\n\ninterface TopBarTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {\n children: React.ReactNode\n}\n\nconst TopBarTitle = React.forwardRef<HTMLHeadingElement, TopBarTitleProps>(\n ({ className, children, ...props }, ref) => (\n <h2\n ref={ref}\n className={cn(\n 'hidden text-heading-xs text-surface-fg md:block',\n className,\n )}\n {...props}\n >\n {children}\n </h2>\n ),\n)\nTopBarTitle.displayName = 'TopBar.Title'\n\n// -----------------------------------------------------------------------\n// TopBar.UserMenu\n// -----------------------------------------------------------------------\n\ninterface TopBarUserMenuProps {\n user: TopBarUser\n onNavigate?: (path: string) => void\n onLogout?: () => void\n userMenuItems?: UserMenuItem[]\n className?: string\n}\n\nconst TopBarUserMenu = React.forwardRef<HTMLButtonElement, TopBarUserMenuProps>(\n ({ user, onNavigate, onLogout, userMenuItems, className }, ref) => {\n const { colorMode, toggleColorMode } = useColorMode()\n\n return (\n <DropdownMenu>\n <Tooltip>\n <TooltipTrigger asChild>\n <DropdownMenuTrigger asChild>\n <button\n ref={ref}\n type=\"button\"\n className={cn('flex items-center gap-ds-03 outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2', className)}\n >\n <Avatar className=\"h-ds-sm-plus w-ds-sm-plus cursor-pointer\">\n {user.image ? (\n <AvatarImage src={user.image} alt={user.name} />\n ) : null}\n <AvatarFallback className=\"bg-surface-panel-hover text-surface-fg\">\n {user.name?.charAt(0).toUpperCase()}\n </AvatarFallback>\n </Avatar>\n </button>\n </DropdownMenuTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" align=\"center\">\n User Menu\n </TooltipContent>\n </Tooltip>\n\n <DropdownMenuContent\n className=\"w-[200px] rounded-overlay-lg bg-surface-overlay p-0 shadow-floating\"\n sideOffset={8}\n align=\"end\"\n >\n {/* User Info */}\n <div className=\"border-b border-surface-border-strong px-ds-05 py-ds-04\">\n <p className=\"text-body-md text-surface-fg\">{user.name}</p>\n {user.email && (\n <TruncatedText as=\"p\" mode=\"middle\" className=\"text-body-sm text-surface-fg-subtle\">\n {user.email}\n </TruncatedText>\n )}\n </div>\n\n <DropdownMenuItem\n className=\"flex w-full cursor-pointer items-center gap-ds-03 px-ds-05 py-ds-04 hover:bg-surface-panel-hover\"\n onClick={() => onNavigate?.('/profile')}\n >\n <Icon icon={IconUser} size=\"sm\" className=\"text-surface-fg-muted\" />\n <span className=\"text-body-md text-surface-fg-muted\">Profile</span>\n </DropdownMenuItem>\n\n {/* Custom user menu items */}\n {userMenuItems?.map((item, index) => {\n const colorMap: Record<string, string> = {\n error: 'text-error-11',\n success: 'text-success-11',\n warning: 'text-warning-11',\n info: 'text-info-11',\n }\n const textColor = item.color\n ? (colorMap[item.color] ?? 'text-surface-fg-muted')\n : 'text-surface-fg-muted'\n return (\n <React.Fragment key={item.label + index}>\n {item.separator && (\n <DropdownMenuSeparator className=\"bg-surface-border\" />\n )}\n <DropdownMenuItem\n className={cn(\n 'flex w-full cursor-pointer items-center gap-ds-03 px-ds-05 py-ds-04 hover:bg-surface-panel-hover',\n item.disabled && 'pointer-events-none opacity-action-disabled',\n )}\n disabled={item.disabled}\n onClick={() => {\n if (item.disabled) return\n if (item.onClick) item.onClick()\n else if (item.href) onNavigate?.(item.href)\n }}\n >\n {item.icon && (\n <span\n className={cn(\n '[&>svg]:h-ico-sm [&>svg]:w-ico-sm',\n textColor,\n )}\n >\n <IconProvider size=\"sm\">{normalizeIcon(item.icon)}</IconProvider>\n </span>\n )}\n <span className={cn('text-body-md', textColor)}>\n {item.label}\n </span>\n {item.badge != null &&\n item.badge !== false &&\n (typeof item.badge === 'string' ? (\n <span className=\"ml-auto inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-pill bg-error-9 px-ds-02 text-[10px] font-semibold leading-none text-error-fg\">\n {item.badge}\n </span>\n ) : (\n <span className=\"ml-auto h-[8px] w-[8px] rounded-pill bg-error-9\" />\n ))}\n </DropdownMenuItem>\n </React.Fragment>\n )\n })}\n\n <DropdownMenuItem\n className=\"flex w-full cursor-pointer items-center gap-ds-03 px-ds-05 py-ds-04 hover:bg-surface-panel-hover\"\n onClick={toggleColorMode}\n >\n {colorMode === 'dark' ? (\n <Icon icon={IconSun} size=\"sm\" className=\"text-surface-fg-muted\" />\n ) : (\n <Icon icon={IconMoon} size=\"sm\" className=\"text-surface-fg-muted\" />\n )}\n <span className=\"text-body-md text-surface-fg-muted\">\n {colorMode === 'dark' ? 'Light Mode' : 'Dark Mode'}\n </span>\n </DropdownMenuItem>\n\n {onLogout && (\n <>\n <DropdownMenuSeparator className=\"bg-surface-border\" />\n <DropdownMenuItem\n className=\"flex w-full cursor-pointer items-center gap-ds-03 px-ds-05 py-ds-04 hover:bg-surface-panel-hover\"\n onClick={onLogout}\n >\n <Icon icon={IconLogout} size=\"sm\" className=\"text-error-11\" />\n <span className=\"text-body-md text-error-11\">Logout</span>\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n },\n)\nTopBarUserMenu.displayName = 'TopBar.UserMenu'\n\n// -----------------------------------------------------------------------\n// Compound export\n// -----------------------------------------------------------------------\n\nexport type TopBarProps = TopBarRootProps\n\nconst TopBar = Object.assign(TopBarRoot, {\n Left: TopBarLeft,\n Center: TopBarCenter,\n Right: TopBarRight,\n Section: TopBarSection,\n IconButton: TopBarIconButton,\n Title: TopBarTitle,\n UserMenu: TopBarUserMenu,\n})\n\nexport { TopBar }\n"],"mappings":"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"}
1
+ {"version":3,"file":"top-bar.js","names":[],"sources":["../../src/shell/top-bar.tsx"],"sourcesContent":["'use client'\n\n/**\n * TopBar v2 — Composition-based application top bar.\n *\n * Subcomponents: TopBar.Left, TopBar.Center, TopBar.Right,\n * TopBar.Section, TopBar.IconButton, TopBar.Title, TopBar.UserMenu.\n */\nimport { IconLogout, IconMoon, IconSun,IconUser } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { useColorMode } from '../hooks/use-color-mode'\nimport { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from '../ui/tooltip'\nimport { TruncatedText } from '../ui/truncated-text'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface TopBarUser {\n name: string\n email?: string\n image?: string | null\n}\n\nexport interface UserMenuItem {\n /** Display label */\n label: string\n /** Optional icon. Accepts any `IconInput`. */\n icon?: IconInput\n /** Navigate via onNavigate when clicked */\n href?: string\n /** Custom click handler */\n onClick?: () => void\n /** Render a separator before this item */\n separator?: boolean\n /** Text color semantic token (e.g. \"error\") — maps to text-{color} */\n color?: string\n /** Badge content — string for count, true for dot indicator */\n badge?: string | boolean\n /** Whether the item is disabled */\n disabled?: boolean\n}\n\n// -----------------------------------------------------------------------\n// TopBar (root)\n// -----------------------------------------------------------------------\n\ninterface TopBarRootProps extends React.HTMLAttributes<HTMLElement> {\n children: React.ReactNode\n className?: string\n}\n\nconst TopBarRoot = React.forwardRef<HTMLElement, TopBarRootProps>(\n ({ className, children, ...props }, ref) => {\n // Detect if a Center zone is present to switch to grid layout\n const hasCenter = React.Children.toArray(children).some(\n (child) => React.isValidElement(child) && child.type === TopBarCenter,\n )\n\n return (\n <header\n {...props}\n ref={ref}\n className={cn(\n 'z-sticky w-full border-b border-surface-border-strong bg-surface-base px-ds-05 py-ds-04 md:px-ds-06',\n hasCenter\n ? 'grid grid-cols-[1fr_auto_1fr] items-center'\n : 'flex items-center',\n className,\n )}\n >\n {children}\n </header>\n )\n },\n)\nTopBarRoot.displayName = 'TopBar'\n\n// -----------------------------------------------------------------------\n// TopBar.Left\n// -----------------------------------------------------------------------\n\ninterface TopBarLeftProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst TopBarLeft = React.forwardRef<HTMLDivElement, TopBarLeftProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex items-center gap-ds-04', className)}\n {...props}\n >\n {children}\n </div>\n ),\n)\nTopBarLeft.displayName = 'TopBar.Left'\n\n// -----------------------------------------------------------------------\n// TopBar.Center\n// -----------------------------------------------------------------------\n\ninterface TopBarCenterProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst TopBarCenter = React.forwardRef<HTMLDivElement, TopBarCenterProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex items-center justify-center px-ds-04', className)}\n {...props}\n >\n {children}\n </div>\n ),\n)\nTopBarCenter.displayName = 'TopBar.Center'\n\n// -----------------------------------------------------------------------\n// TopBar.Right\n// -----------------------------------------------------------------------\n\ninterface TopBarRightProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst TopBarRight = React.forwardRef<HTMLDivElement, TopBarRightProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('ml-auto flex items-center gap-ds-04', className)}\n {...props}\n >\n {children}\n </div>\n ),\n)\nTopBarRight.displayName = 'TopBar.Right'\n\n// -----------------------------------------------------------------------\n// TopBar.Section\n// -----------------------------------------------------------------------\n\nconst gapMap = {\n tight: 'gap-ds-02',\n default: 'gap-ds-04',\n loose: 'gap-ds-06',\n} as const\n\ninterface TopBarSectionProps extends React.HTMLAttributes<HTMLDivElement> {\n gap?: keyof typeof gapMap\n children: React.ReactNode\n}\n\nconst TopBarSection = React.forwardRef<HTMLDivElement, TopBarSectionProps>(\n ({ gap = 'default', className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex items-center', gapMap[gap], className)}\n {...props}\n >\n {children}\n </div>\n ),\n)\nTopBarSection.displayName = 'TopBar.Section'\n\n// -----------------------------------------------------------------------\n// TopBar.IconButton\n// -----------------------------------------------------------------------\n\ninterface TopBarIconButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n icon: IconInput\n tooltip: string\n}\n\nconst TopBarIconButton = React.forwardRef<\n HTMLButtonElement,\n TopBarIconButtonProps\n>(({ icon, tooltip, className, ...props }, ref) => (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n ref={ref}\n type=\"button\"\n aria-label={tooltip}\n className={cn(\n 'flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-surface-border-interactive bg-surface-panel-hover text-surface-fg-muted transition-[color,background-color,border-color,transform] hover:bg-surface-panel-active active:scale-90 duration-fast-01 ease-productive-standard',\n className,\n )}\n {...props}\n >\n <span className=\"[&>svg]:h-ico-sm [&>svg]:w-ico-sm\" aria-hidden=\"true\">\n <IconProvider size=\"sm\">{normalizeIcon(icon)}</IconProvider>\n </span>\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" align=\"center\">\n {tooltip}\n </TooltipContent>\n </Tooltip>\n))\nTopBarIconButton.displayName = 'TopBar.IconButton'\n\n// -----------------------------------------------------------------------\n// TopBar.Title\n// -----------------------------------------------------------------------\n\ninterface TopBarTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {\n children: React.ReactNode\n}\n\nconst TopBarTitle = React.forwardRef<HTMLHeadingElement, TopBarTitleProps>(\n ({ className, children, ...props }, ref) => (\n <h2\n ref={ref}\n className={cn(\n 'hidden text-heading-xs text-surface-fg md:block',\n className,\n )}\n {...props}\n >\n {children}\n </h2>\n ),\n)\nTopBarTitle.displayName = 'TopBar.Title'\n\n// -----------------------------------------------------------------------\n// TopBar.UserMenu\n// -----------------------------------------------------------------------\n\ninterface TopBarUserMenuProps {\n user: TopBarUser\n onNavigate?: (path: string) => void\n onLogout?: () => void\n userMenuItems?: UserMenuItem[]\n className?: string\n}\n\nconst TopBarUserMenu = React.forwardRef<HTMLButtonElement, TopBarUserMenuProps>(\n ({ user, onNavigate, onLogout, userMenuItems, className }, ref) => {\n const { colorMode, toggleColorMode } = useColorMode()\n\n return (\n <DropdownMenu>\n <Tooltip>\n <TooltipTrigger asChild>\n <DropdownMenuTrigger asChild>\n <button\n ref={ref}\n type=\"button\"\n className={cn('flex items-center gap-ds-03 outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2', className)}\n >\n <Avatar className=\"h-ds-sm-plus w-ds-sm-plus cursor-pointer\">\n {user.image ? (\n <AvatarImage src={user.image} alt={user.name} />\n ) : null}\n <AvatarFallback className=\"bg-surface-panel-hover text-surface-fg\">\n {user.name?.charAt(0).toUpperCase()}\n </AvatarFallback>\n </Avatar>\n </button>\n </DropdownMenuTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" align=\"center\">\n User Menu\n </TooltipContent>\n </Tooltip>\n\n <DropdownMenuContent\n className=\"w-[200px] rounded-overlay-lg bg-surface-overlay p-0 shadow-floating\"\n sideOffset={8}\n align=\"end\"\n >\n {/* User Info */}\n <div className=\"border-b border-surface-border-strong px-ds-05 py-ds-04\">\n <p className=\"text-body-md text-surface-fg\">{user.name}</p>\n {user.email && (\n <TruncatedText as=\"p\" mode=\"middle\" className=\"text-body-sm text-surface-fg-subtle\">\n {user.email}\n </TruncatedText>\n )}\n </div>\n\n <DropdownMenuItem\n className=\"flex w-full cursor-pointer items-center gap-ds-03 px-ds-05 py-ds-04 hover:bg-surface-panel-hover\"\n onClick={() => onNavigate?.('/profile')}\n >\n <Icon icon={IconUser} size=\"sm\" className=\"text-surface-fg-muted\" />\n <span className=\"text-body-md text-surface-fg-muted\">Profile</span>\n </DropdownMenuItem>\n\n {/* Custom user menu items */}\n {userMenuItems?.map((item, index) => {\n const colorMap: Record<string, string> = {\n error: 'text-error-11',\n success: 'text-success-11',\n warning: 'text-warning-11',\n info: 'text-info-11',\n }\n const textColor = item.color\n ? (colorMap[item.color] ?? 'text-surface-fg-muted')\n : 'text-surface-fg-muted'\n return (\n <React.Fragment key={item.label + index}>\n {item.separator && (\n <DropdownMenuSeparator className=\"bg-surface-border\" />\n )}\n <DropdownMenuItem\n className={cn(\n 'flex w-full cursor-pointer items-center gap-ds-03 px-ds-05 py-ds-04 hover:bg-surface-panel-hover',\n item.disabled && 'pointer-events-none opacity-action-disabled',\n )}\n disabled={item.disabled}\n onClick={() => {\n if (item.disabled) return\n if (item.onClick) item.onClick()\n else if (item.href) onNavigate?.(item.href)\n }}\n >\n {item.icon && (\n <span\n className={cn(\n '[&>svg]:h-ico-sm [&>svg]:w-ico-sm',\n textColor,\n )}\n >\n <IconProvider size=\"sm\">{normalizeIcon(item.icon)}</IconProvider>\n </span>\n )}\n <span className={cn('text-body-md', textColor)}>\n {item.label}\n </span>\n {item.badge != null &&\n item.badge !== false &&\n (typeof item.badge === 'string' ? (\n <span className=\"ml-auto inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-pill bg-error-9 px-ds-02 text-[10px] font-semibold leading-none text-error-fg\">\n {item.badge}\n </span>\n ) : (\n <span className=\"ml-auto h-[8px] w-[8px] rounded-pill bg-error-9\" />\n ))}\n </DropdownMenuItem>\n </React.Fragment>\n )\n })}\n\n <DropdownMenuItem\n className=\"flex w-full cursor-pointer items-center gap-ds-03 px-ds-05 py-ds-04 hover:bg-surface-panel-hover\"\n onClick={toggleColorMode}\n >\n {colorMode === 'dark' ? (\n <Icon icon={IconSun} size=\"sm\" className=\"text-surface-fg-muted\" />\n ) : (\n <Icon icon={IconMoon} size=\"sm\" className=\"text-surface-fg-muted\" />\n )}\n <span className=\"text-body-md text-surface-fg-muted\">\n {colorMode === 'dark' ? 'Light Mode' : 'Dark Mode'}\n </span>\n </DropdownMenuItem>\n\n {onLogout && (\n <>\n <DropdownMenuSeparator className=\"bg-surface-border\" />\n <DropdownMenuItem\n className=\"flex w-full cursor-pointer items-center gap-ds-03 px-ds-05 py-ds-04 hover:bg-surface-panel-hover\"\n onClick={onLogout}\n >\n <Icon icon={IconLogout} size=\"sm\" className=\"text-error-11\" />\n <span className=\"text-body-md text-error-11\">Logout</span>\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n },\n)\nTopBarUserMenu.displayName = 'TopBar.UserMenu'\n\n// -----------------------------------------------------------------------\n// Compound export\n// -----------------------------------------------------------------------\n\nexport type TopBarProps = TopBarRootProps\n\nconst TopBar = Object.assign(TopBarRoot, {\n Left: TopBarLeft,\n Center: TopBarCenter,\n Right: TopBarRight,\n Section: TopBarSection,\n IconButton: TopBarIconButton,\n Title: TopBarTitle,\n UserMenu: TopBarUserMenu,\n})\n\nexport { TopBar }\n"],"mappings":"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"}
@@ -18,6 +18,7 @@
18
18
  */
19
19
  @import './primitives.css';
20
20
  @import './semantic.css';
21
+ @import './palettes.css';
21
22
  @import './typography.css';
22
23
  @import './typography-semantic.css';
23
24
  @import './base.css';
@@ -0,0 +1,292 @@
1
+ /* ═══════════════════════════════════════════════════════════════════
2
+ PALETTE — one indirection so a component references *a* colour role
3
+ rather than *a* colour.
4
+
5
+ A component styles itself once:
6
+
7
+ bg-palette-solid text-palette-fg hover:bg-palette-solid-hover
8
+
9
+ and the palette is chosen by an ancestor or by the element itself:
10
+
11
+ <button data-palette="error" class="bg-palette-solid …">
12
+
13
+ This replaces the variants × colours cross-product. Adding a colour is
14
+ one block here, not N rows in every component. See
15
+ docs/plans/2026-08-27-colour-palette-indirection.md.
16
+
17
+ WHY NAMED ROLES AND NOT palette-1..12
18
+ -------------------------------------
19
+ `neutral` does not follow the chromatic mapping: its solid is step 5
20
+ (step 9 is a mid grey that reads as disabled), its on-solid text is
21
+ `surface-fg`, and half its roles resolve from the *surface* family
22
+ rather than a ramp. Under numeric naming it would have to claim
23
+ `--color-palette-9: var(--color-neutral-5)`, which is a lie in the
24
+ token name. Roles let each palette resolve from wherever is correct.
25
+
26
+ THEME
27
+ -----
28
+ Nothing here is duplicated per theme. Every source token — the
29
+ `--color-*` semantics and the private primitives alike — is already
30
+ redefined under `.dark`, so a palette inherits dark mode for free.
31
+
32
+ CASCADE
33
+ -------
34
+ `[data-palette]` inherits, so a palette set on a container themes
35
+ everything inside it. An explicit `data-palette` on a descendant wins,
36
+ because it is the nearer rule. Radix Themes and Chakra both behave this
37
+ way. Unlayered rules beat `@layer theme`, so these always override the
38
+ defaults below regardless of import order.
39
+ ═══════════════════════════════════════════════════════════════════ */
40
+
41
+ /* The contract. Eleven roles. Defaults are `accent`, matching the current
42
+ default of every component that has a `color` prop.
43
+
44
+ `solid-active` and `text` resolve to the same step on every chromatic
45
+ palette, which looks redundant until you reach `neutral`, where one is
46
+ `neutral-8` and the other is `surface-fg-muted`. Two roles, one value, is
47
+ exactly what a role contract is for. */
48
+ @theme {
49
+ --color-palette-subtle: var(--color-accent-2); /* faintest tinted ground */
50
+ --color-palette-soft: var(--color-accent-3); /* soft fill */
51
+ --color-palette-soft-hover: var(--color-accent-4); /* soft fill, hovered */
52
+ --color-palette-soft-active: var(--color-accent-5); /* soft fill, pressed */
53
+ --color-palette-border-subtle: var(--color-accent-6); /* divider inside a coloured thing */
54
+ /* The object's edge. Step 4, not 7 — a deliberate call to keep coloured
55
+ edges a whisper. Measured 1.37–1.49:1 against white, where the plain
56
+ uncoloured edge we already ship is 1.23:1, so the colour barely reads.
57
+ Recorded as PALETTE-EDGE-WHISPER in docs/deviations.md. */
58
+ --color-palette-border: var(--color-accent-4); --color-palette-border-strong: var(--color-accent-7);
59
+ --color-palette-solid: var(--color-accent-9); /* filled control */
60
+ --color-palette-solid-hover: var(--color-accent-10); /* filled control, hovered */
61
+ --color-palette-solid-active: var(--color-accent-11); /* filled control, pressed */
62
+ --color-palette-text: var(--color-accent-11); /* coloured text on a light ground */
63
+ --color-palette-fg: var(--color-accent-fg); /* text ON the solid fill */
64
+ }
65
+
66
+ /* Derived on-solid text, for palettes we do not ship.
67
+
68
+ Declared BEFORE the built-ins deliberately: every block below has the
69
+ same specificity (0,1,0), so source order decides and each built-in
70
+ overwrites this with its measured value. Only a consumer-registered
71
+ palette that omits `--color-palette-fg` keeps it.
72
+
73
+ Black or white by the solid's lightness. Readable, but blunt on
74
+ mid-tone brand colours — supply the value explicitly and you get
75
+ exactly what you chose. `contrast-color()` would be the purpose-built
76
+ answer; it is Safari 26+ only, so not yet. */
77
+ @supports (color: oklch(from white l c h)) {
78
+ [data-palette] {
79
+ --color-palette-fg: oklch(from var(--color-palette-solid)
80
+ clamp(0, (0.62 - l) * 1000, 1) 0 0);
81
+ }
82
+ }
83
+
84
+ /* ───────────────────────────────────────────────────────────────────
85
+ Brand-following palettes — resolve from the SEMANTIC layer, so a
86
+ consumer who overrides `--color-accent-*` to rebrand gets it here too.
87
+ ─────────────────────────────────────────────────────────────────── */
88
+
89
+ [data-palette='accent'] {
90
+ --color-palette-subtle: var(--color-accent-2);
91
+ --color-palette-soft: var(--color-accent-3);
92
+ --color-palette-soft-hover: var(--color-accent-4);
93
+ --color-palette-soft-active: var(--color-accent-5);
94
+ --color-palette-border-subtle: var(--color-accent-6);
95
+ --color-palette-border: var(--color-accent-4); --color-palette-border-strong: var(--color-accent-7);
96
+ --color-palette-solid: var(--color-accent-9);
97
+ --color-palette-solid-hover: var(--color-accent-10);
98
+ --color-palette-solid-active: var(--color-accent-11);
99
+ --color-palette-text: var(--color-accent-11);
100
+ --color-palette-fg: var(--color-accent-fg);
101
+ }
102
+
103
+ [data-palette='secondary'] {
104
+ --color-palette-subtle: var(--color-secondary-2);
105
+ --color-palette-soft: var(--color-secondary-3);
106
+ --color-palette-soft-hover: var(--color-secondary-4);
107
+ --color-palette-soft-active: var(--color-secondary-5);
108
+ --color-palette-border-subtle: var(--color-secondary-6);
109
+ --color-palette-border: var(--color-secondary-4); --color-palette-border-strong: var(--color-secondary-7);
110
+ --color-palette-solid: var(--color-secondary-9);
111
+ --color-palette-solid-hover: var(--color-secondary-10);
112
+ --color-palette-solid-active: var(--color-secondary-11);
113
+ --color-palette-text: var(--color-secondary-11);
114
+ --color-palette-fg: var(--color-secondary-fg);
115
+ }
116
+
117
+ [data-palette='error'] {
118
+ --color-palette-subtle: var(--color-error-2);
119
+ --color-palette-soft: var(--color-error-3);
120
+ --color-palette-soft-hover: var(--color-error-4);
121
+ --color-palette-soft-active: var(--color-error-5);
122
+ --color-palette-border-subtle: var(--color-error-6);
123
+ --color-palette-border: var(--color-error-4); --color-palette-border-strong: var(--color-error-7);
124
+ --color-palette-solid: var(--color-error-9);
125
+ --color-palette-solid-hover: var(--color-error-10);
126
+ --color-palette-solid-active: var(--color-error-11);
127
+ --color-palette-text: var(--color-error-11);
128
+ --color-palette-fg: var(--color-error-fg);
129
+ }
130
+
131
+ [data-palette='success'] {
132
+ --color-palette-subtle: var(--color-success-2);
133
+ --color-palette-soft: var(--color-success-3);
134
+ --color-palette-soft-hover: var(--color-success-4);
135
+ --color-palette-soft-active: var(--color-success-5);
136
+ --color-palette-border-subtle: var(--color-success-6);
137
+ --color-palette-border: var(--color-success-4); --color-palette-border-strong: var(--color-success-7);
138
+ --color-palette-solid: var(--color-success-9);
139
+ --color-palette-solid-hover: var(--color-success-10);
140
+ --color-palette-solid-active: var(--color-success-11);
141
+ --color-palette-text: var(--color-success-11);
142
+ --color-palette-fg: var(--color-success-fg);
143
+ }
144
+
145
+ [data-palette='warning'] {
146
+ --color-palette-subtle: var(--color-warning-2);
147
+ --color-palette-soft: var(--color-warning-3);
148
+ --color-palette-soft-hover: var(--color-warning-4);
149
+ --color-palette-soft-active: var(--color-warning-5);
150
+ --color-palette-border-subtle: var(--color-warning-6);
151
+ --color-palette-border: var(--color-warning-4); --color-palette-border-strong: var(--color-warning-7);
152
+ --color-palette-solid: var(--color-warning-9);
153
+ --color-palette-solid-hover: var(--color-warning-10);
154
+ --color-palette-solid-active: var(--color-warning-11);
155
+ --color-palette-text: var(--color-warning-11);
156
+ --color-palette-fg: var(--color-warning-fg);
157
+ }
158
+
159
+ [data-palette='info'] {
160
+ --color-palette-subtle: var(--color-info-2);
161
+ --color-palette-soft: var(--color-info-3);
162
+ --color-palette-soft-hover: var(--color-info-4);
163
+ --color-palette-soft-active: var(--color-info-5);
164
+ --color-palette-border-subtle: var(--color-info-6);
165
+ --color-palette-border: var(--color-info-4); --color-palette-border-strong: var(--color-info-7);
166
+ --color-palette-solid: var(--color-info-9);
167
+ --color-palette-solid-hover: var(--color-info-10);
168
+ --color-palette-solid-active: var(--color-info-11);
169
+ --color-palette-text: var(--color-info-11);
170
+ --color-palette-fg: var(--color-info-fg);
171
+ }
172
+
173
+ /* `neutral` is the reason this file uses roles rather than step numbers.
174
+ Its solid is step 5 — step 9 is a mid grey that reads as a disabled
175
+ control, not a filled one — and more than half its roles resolve from
176
+ the SURFACE family, which is a different token tree entirely. This
177
+ mirrors what `Component/Intent` already does in the Figma library. */
178
+ [data-palette='neutral'] {
179
+ --color-palette-subtle: var(--color-surface-panel-hover);
180
+ --color-palette-soft: var(--color-surface-panel-hover);
181
+ --color-palette-soft-hover: var(--color-surface-panel-active);
182
+ --color-palette-soft-active: var(--color-neutral-5);
183
+ --color-palette-border-subtle: var(--color-surface-border-subtle);
184
+ --color-palette-border: var(--color-surface-border);
185
+ --color-palette-border-strong: var(--color-surface-border-strong);
186
+ --color-palette-solid: var(--color-neutral-5);
187
+ --color-palette-solid-hover: var(--color-neutral-7);
188
+ --color-palette-solid-active: var(--color-neutral-8);
189
+ --color-palette-text: var(--color-surface-fg-muted);
190
+ --color-palette-fg: var(--color-surface-fg);
191
+ }
192
+
193
+ /* ───────────────────────────────────────────────────────────────────
194
+ Category palettes — a fixed, non-rebrandable set for labelling things
195
+ that are different rather than better or worse (teams, tags, projects).
196
+
197
+ These resolve from the PRIVATE PRIMITIVES rather than the semantic
198
+ layer, because the semantic layer exposes only six steps for them
199
+ (2, 3, 4, 7, 9, 11) while the roles need ten. Exposing the missing
200
+ four semantically would add 84 declarations and generate a utility
201
+ surface (`bg-category-teal-5`, …) nobody asked for. The primitives are
202
+ already complete and already theme-aware.
203
+
204
+ On-solid text uses `--color-category-fg`, which inverts with the theme
205
+ — category step 9 lightens in dark, the opposite of the intent ramps.
206
+ That token exists because a hardcoded white failed AA in dark; see the
207
+ 0.57.0 changelog.
208
+ ─────────────────────────────────────────────────────────────────── */
209
+
210
+ [data-palette='teal'] {
211
+ --color-palette-subtle: var(--teal-2); --color-palette-soft: var(--teal-3);
212
+ --color-palette-soft-hover: var(--teal-4); --color-palette-soft-active: var(--teal-5);
213
+ --color-palette-border-subtle: var(--teal-6);--color-palette-border: var(--teal-4); --color-palette-border-strong: var(--teal-7);
214
+ --color-palette-solid: var(--teal-9); --color-palette-solid-hover: var(--teal-10);
215
+ --color-palette-solid-active: var(--teal-11);
216
+ --color-palette-text: var(--teal-11); --color-palette-fg: var(--color-category-fg);
217
+ }
218
+
219
+ [data-palette='amber'] {
220
+ --color-palette-subtle: var(--amber-2); --color-palette-soft: var(--amber-3);
221
+ --color-palette-soft-hover: var(--amber-4); --color-palette-soft-active: var(--amber-5);
222
+ --color-palette-border-subtle: var(--amber-6);--color-palette-border: var(--amber-4); --color-palette-border-strong: var(--amber-7);
223
+ --color-palette-solid: var(--amber-9); --color-palette-solid-hover: var(--amber-10);
224
+ --color-palette-solid-active: var(--amber-11);
225
+ --color-palette-text: var(--amber-11); --color-palette-fg: var(--color-category-fg);
226
+ }
227
+
228
+ [data-palette='slate'] {
229
+ --color-palette-subtle: var(--slate-2); --color-palette-soft: var(--slate-3);
230
+ --color-palette-soft-hover: var(--slate-4); --color-palette-soft-active: var(--slate-5);
231
+ --color-palette-border-subtle: var(--slate-6);--color-palette-border: var(--slate-4); --color-palette-border-strong: var(--slate-7);
232
+ --color-palette-solid: var(--slate-9); --color-palette-solid-hover: var(--slate-10);
233
+ --color-palette-solid-active: var(--slate-11);
234
+ --color-palette-text: var(--slate-11); --color-palette-fg: var(--color-category-fg);
235
+ }
236
+
237
+ [data-palette='indigo'] {
238
+ --color-palette-subtle: var(--indigo-2); --color-palette-soft: var(--indigo-3);
239
+ --color-palette-soft-hover: var(--indigo-4); --color-palette-soft-active: var(--indigo-5);
240
+ --color-palette-border-subtle: var(--indigo-6);--color-palette-border: var(--indigo-4); --color-palette-border-strong: var(--indigo-7);
241
+ --color-palette-solid: var(--indigo-9); --color-palette-solid-hover: var(--indigo-10);
242
+ --color-palette-solid-active: var(--indigo-11);
243
+ --color-palette-text: var(--indigo-11); --color-palette-fg: var(--color-category-fg);
244
+ }
245
+
246
+ [data-palette='cyan'] {
247
+ --color-palette-subtle: var(--cyan-2); --color-palette-soft: var(--cyan-3);
248
+ --color-palette-soft-hover: var(--cyan-4); --color-palette-soft-active: var(--cyan-5);
249
+ --color-palette-border-subtle: var(--cyan-6);--color-palette-border: var(--cyan-4); --color-palette-border-strong: var(--cyan-7);
250
+ --color-palette-solid: var(--cyan-9); --color-palette-solid-hover: var(--cyan-10);
251
+ --color-palette-solid-active: var(--cyan-11);
252
+ --color-palette-text: var(--cyan-11); --color-palette-fg: var(--color-category-fg);
253
+ }
254
+
255
+ [data-palette='orange'] {
256
+ --color-palette-subtle: var(--orange-2); --color-palette-soft: var(--orange-3);
257
+ --color-palette-soft-hover: var(--orange-4); --color-palette-soft-active: var(--orange-5);
258
+ --color-palette-border-subtle: var(--orange-6);--color-palette-border: var(--orange-4); --color-palette-border-strong: var(--orange-7);
259
+ --color-palette-solid: var(--orange-9); --color-palette-solid-hover: var(--orange-10);
260
+ --color-palette-solid-active: var(--orange-11);
261
+ --color-palette-text: var(--orange-11); --color-palette-fg: var(--color-category-fg);
262
+ }
263
+
264
+ [data-palette='emerald'] {
265
+ --color-palette-subtle: var(--emerald-2); --color-palette-soft: var(--emerald-3);
266
+ --color-palette-soft-hover: var(--emerald-4); --color-palette-soft-active: var(--emerald-5);
267
+ --color-palette-border-subtle: var(--emerald-6);--color-palette-border: var(--emerald-4); --color-palette-border-strong: var(--emerald-7);
268
+ --color-palette-solid: var(--emerald-9); --color-palette-solid-hover: var(--emerald-10);
269
+ --color-palette-solid-active: var(--emerald-11);
270
+ --color-palette-text: var(--emerald-11); --color-palette-fg: var(--color-category-fg);
271
+ }
272
+
273
+ /* ───────────────────────────────────────────────────────────────────
274
+ Registering your own palette
275
+
276
+ [data-palette='brand-purple'] {
277
+ --color-palette-solid: #6d28d9;
278
+ --color-palette-solid-hover: #5b21b6;
279
+ --color-palette-fg: #ffffff; ← supply this
280
+
281
+ }
282
+
283
+ Supply `--color-palette-fg` explicitly. If you omit it, the derivation
284
+ near the top of this file gives you black-or-white from the solid's
285
+ lightness — readable, but blunt on a mid-tone brand colour, and outside
286
+ the contrast guarantee we measure for the built-in palettes.
287
+
288
+ That derivation is declared once, before the built-in blocks, so every
289
+ built-in overwrites it and only an unrecognised palette keeps it. See
290
+ the note there for why source order rather than specificity does the
291
+ work.
292
+ ─────────────────────────────────────────────────────────────────── */