@devalok/shilp-sutra 0.57.0 → 0.59.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.
- package/MIGRATION.md +91 -0
- package/dist/_chunks/badge-group.js +75 -188
- package/dist/_chunks/badge-group.js.map +1 -1
- package/dist/_chunks/chat.js +244 -243
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/image-preview.js +71 -70
- package/dist/_chunks/image-preview.js.map +1 -1
- package/dist/_chunks/motion-preference.js +15 -0
- package/dist/_chunks/motion-preference.js.map +1 -0
- package/dist/_chunks/motion-provider.js +1 -1
- package/dist/_chunks/motion-provider.js.map +1 -1
- package/dist/_chunks/popover.js +50 -49
- package/dist/_chunks/popover.js.map +1 -1
- package/dist/_chunks/primitives-internal.js +29 -29
- package/dist/_chunks/primitives-internal.js.map +1 -1
- package/dist/_chunks/shared.js +48 -47
- package/dist/_chunks/shared.js.map +1 -1
- package/dist/_chunks/success.js +34 -34
- package/dist/_chunks/tree-view.js +1 -1
- package/dist/_chunks/tree-view.js.map +1 -1
- package/dist/ai/block-renderer.js +8 -8
- package/dist/ai/command-bar.js +65 -65
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js +35 -35
- package/dist/ai/conversation.js.map +1 -1
- package/dist/ai/index.js +4 -4
- package/dist/composed/activity-feed.d.ts.map +1 -1
- package/dist/composed/activity-feed.js +108 -107
- package/dist/composed/activity-feed.js.map +1 -1
- package/dist/composed/command-palette.js +137 -137
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/content-card.js +1 -1
- package/dist/composed/content-card.js.map +1 -1
- package/dist/composed/date-picker/index.js +5 -5
- package/dist/composed/date-picker/index.js.map +1 -1
- package/dist/composed/empty-state.js +4 -4
- package/dist/composed/file-preview/image-preview.d.ts.map +1 -1
- package/dist/composed/file-preview/shared.d.ts.map +1 -1
- package/dist/composed/file-preview/video-preview.d.ts.map +1 -1
- package/dist/composed/file-preview.js +151 -150
- package/dist/composed/file-preview.js.map +1 -1
- package/dist/composed/filter-bar.d.ts.map +1 -1
- package/dist/composed/filter-bar.js +53 -52
- package/dist/composed/filter-bar.js.map +1 -1
- package/dist/composed/form-section.d.ts.map +1 -1
- package/dist/composed/form-section.js +38 -37
- package/dist/composed/form-section.js.map +1 -1
- package/dist/composed/master-detail.d.ts.map +1 -1
- package/dist/composed/master-detail.js +1 -1
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/multi-select-popover.d.ts.map +1 -1
- package/dist/composed/multi-select-popover.js +109 -108
- package/dist/composed/multi-select-popover.js.map +1 -1
- package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/reply-banner.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
- package/dist/composed/rich-chat-input.d.ts.map +1 -1
- package/dist/composed/rich-chat-input.js +467 -466
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.js +3 -3
- package/dist/composed/rich-text-editor.js.map +1 -1
- package/dist/composed/schedule-view.js +2 -2
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/composed/status-badge.d.ts.map +1 -1
- package/dist/composed/status-badge.js +29 -28
- package/dist/composed/status-badge.js.map +1 -1
- package/dist/motion/index.d.ts +1 -0
- package/dist/motion/index.d.ts.map +1 -1
- package/dist/motion/index.js +2 -15
- package/dist/motion/motion-preference.d.ts +36 -0
- package/dist/motion/motion-preference.d.ts.map +1 -0
- package/dist/motion/motion-provider.d.ts +2 -1
- package/dist/motion/motion-provider.d.ts.map +1 -1
- package/dist/motion/primitives-index.js +103 -102
- package/dist/motion/primitives-index.js.map +1 -1
- package/dist/motion/primitives.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.js +19 -19
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/shell/index.d.ts +1 -1
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/notification-center.d.ts +17 -0
- package/dist/shell/notification-center.d.ts.map +1 -1
- package/dist/shell/notification-center.js +109 -104
- package/dist/shell/notification-center.js.map +1 -1
- package/dist/shell/notification-preferences.js +1 -1
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/shell/top-bar.js +1 -1
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/tokens/index.css +1 -0
- package/dist/tokens/palettes.css +292 -0
- package/dist/tokens/semantic.css +72 -8
- package/dist/tokens/shilp-sutra.css +1 -0
- package/dist/tokens/utilities.css +10 -7
- package/dist/ui/accordion.d.ts.map +1 -1
- package/dist/ui/accordion.js +27 -26
- package/dist/ui/accordion.js.map +1 -1
- package/dist/ui/alert-dialog.d.ts.map +1 -1
- package/dist/ui/alert-dialog.js +60 -59
- package/dist/ui/alert-dialog.js.map +1 -1
- package/dist/ui/alert.d.ts.map +1 -1
- package/dist/ui/alert.js +55 -113
- package/dist/ui/alert.js.map +1 -1
- package/dist/ui/autocomplete.js +80 -80
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/avatar.d.ts.map +1 -1
- package/dist/ui/avatar.js +69 -68
- package/dist/ui/avatar.js.map +1 -1
- package/dist/ui/badge-indicator.d.ts +4 -8
- package/dist/ui/badge-indicator.d.ts.map +1 -1
- package/dist/ui/badge-indicator.js +3 -8
- package/dist/ui/badge-indicator.js.map +1 -1
- package/dist/ui/badge.d.ts +8 -1
- package/dist/ui/badge.d.ts.map +1 -1
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +42 -40
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/button-group.d.ts.map +1 -1
- package/dist/ui/button-group.js +3 -23
- package/dist/ui/button-group.js.map +1 -1
- package/dist/ui/button.d.ts +10 -1
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +79 -212
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/card.d.ts.map +1 -1
- package/dist/ui/card.js +75 -72
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/charts/radar-chart.d.ts.map +1 -1
- package/dist/ui/charts/radar-chart.js +1 -1
- package/dist/ui/charts/radar-chart.js.map +1 -1
- package/dist/ui/chat/message-list.d.ts.map +1 -1
- package/dist/ui/chat/message.d.ts.map +1 -1
- package/dist/ui/chat/typing-indicator.d.ts.map +1 -1
- package/dist/ui/code.js +1 -1
- package/dist/ui/code.js.map +1 -1
- package/dist/ui/color-input.d.ts.map +1 -1
- package/dist/ui/color-input.js +151 -150
- package/dist/ui/color-input.js.map +1 -1
- package/dist/ui/combobox.d.ts +14 -0
- package/dist/ui/combobox.d.ts.map +1 -1
- package/dist/ui/combobox.js +152 -148
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/context-menu.d.ts.map +1 -1
- package/dist/ui/context-menu.js +70 -69
- package/dist/ui/context-menu.js.map +1 -1
- package/dist/ui/data-table-body.js +2 -2
- package/dist/ui/data-table-body.js.map +1 -1
- package/dist/ui/data-table-context.js +2 -2
- package/dist/ui/data-table-context.js.map +1 -1
- package/dist/ui/data-table-header.d.ts.map +1 -1
- package/dist/ui/data-table-header.js +41 -40
- package/dist/ui/data-table-header.js.map +1 -1
- package/dist/ui/data-table-pagination.js +2 -2
- package/dist/ui/data-table-pagination.js.map +1 -1
- package/dist/ui/dialog.d.ts.map +1 -1
- package/dist/ui/dialog.js +61 -60
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/dot.d.ts.map +1 -1
- package/dist/ui/dot.js +10 -41
- package/dist/ui/dot.js.map +1 -1
- package/dist/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/ui/dropdown-menu.js +79 -78
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload.js +116 -115
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +27 -26
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/hover-card.d.ts.map +1 -1
- package/dist/ui/hover-card.js +26 -25
- package/dist/ui/hover-card.js.map +1 -1
- package/dist/ui/icon.js +7 -7
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +3 -3
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input.js +1 -1
- package/dist/ui/input.js.map +1 -1
- package/dist/ui/menubar.d.ts.map +1 -1
- package/dist/ui/menubar.js +73 -72
- package/dist/ui/menubar.js.map +1 -1
- package/dist/ui/navigation-menu.d.ts.map +1 -1
- package/dist/ui/navigation-menu.js +80 -79
- package/dist/ui/navigation-menu.js.map +1 -1
- package/dist/ui/popover.d.ts.map +1 -1
- package/dist/ui/progress.d.ts.map +1 -1
- package/dist/ui/progress.js +72 -68
- package/dist/ui/progress.js.map +1 -1
- package/dist/ui/radio.d.ts.map +1 -1
- package/dist/ui/radio.js +32 -31
- package/dist/ui/radio.js.map +1 -1
- package/dist/ui/search-input.d.ts.map +1 -1
- package/dist/ui/search-input.js +28 -27
- package/dist/ui/search-input.js.map +1 -1
- package/dist/ui/select.d.ts.map +1 -1
- package/dist/ui/select.js +64 -63
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/sidebar.d.ts.map +1 -1
- package/dist/ui/sidebar.js +4 -4
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/slider.d.ts +63 -9
- package/dist/ui/slider.d.ts.map +1 -1
- package/dist/ui/slider.js +74 -32
- package/dist/ui/slider.js.map +1 -1
- package/dist/ui/split-button.d.ts +8 -2
- package/dist/ui/split-button.d.ts.map +1 -1
- package/dist/ui/split-button.js +49 -95
- package/dist/ui/split-button.js.map +1 -1
- package/dist/ui/stat-card.d.ts.map +1 -1
- package/dist/ui/stat-card.js +89 -88
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/stat-flash.js +16 -16
- package/dist/ui/stepper.d.ts.map +1 -1
- package/dist/ui/stepper.js +63 -62
- package/dist/ui/stepper.js.map +1 -1
- package/dist/ui/table.js +3 -3
- package/dist/ui/table.js.map +1 -1
- package/dist/ui/tabs.d.ts.map +1 -1
- package/dist/ui/tabs.js +67 -66
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/textarea.js +1 -1
- package/dist/ui/textarea.js.map +1 -1
- package/dist/ui/toast.d.ts.map +1 -1
- package/dist/ui/toast.js +195 -194
- package/dist/ui/toast.js.map +1 -1
- package/dist/ui/toggle.d.ts.map +1 -1
- package/dist/ui/toggle.js +24 -22
- package/dist/ui/toggle.js.map +1 -1
- package/dist/ui/tooltip.d.ts.map +1 -1
- package/dist/ui/tooltip.js +37 -36
- package/dist/ui/tooltip.js.map +1 -1
- package/dist/ui/tree-view/tree-item.d.ts.map +1 -1
- package/docs/recipes/customize-brand.md +58 -0
- package/docs/recipes/install-astro.md +1 -1
- package/docs/recipes/install-next-app-router.md +1 -1
- package/docs/recipes/install-next-pages.md +22 -1
- package/docs/recipes/install-remix.md +1 -1
- package/docs/recipes/install-tanstack-start.md +1 -1
- package/docs/recipes/install-vite.md +2 -1
- package/docs/recipes/server-components.md +7 -4
- package/docs/recipes/troubleshoot.md +1 -0
- package/llms.txt +1 -1
- package/mcp-manifest.json +9 -4
- package/package.json +3 -2
- package/skill/SKILL.md +1 -1
- package/skill/references/components.md +1 -1
- package/skill/references/customize-brand.md +58 -0
- package/skill/references/server-components.md +7 -4
- package/skill/references/setup-astro.md +1 -1
- package/skill/references/setup-next-app-router.md +1 -1
- package/skill/references/setup-next-pages.md +22 -1
- package/skill/references/setup-remix.md +1 -1
- package/skill/references/setup-tanstack-start.md +1 -1
- package/skill/references/setup-vite.md +2 -1
- package/skill/references/troubleshoot.md +1 -0
- package/dist/motion/check-motion-provider.d.ts +0 -2
- package/dist/motion/check-motion-provider.d.ts.map +0 -1
- 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":";;;;;;;;;;;;;;;AAoFA,IAAM,IAA2E;CAC/E,QAAQ;EAAE,OAAO;EAAU,MAAM,kBAAC,GAAD;GAAM,MAAM;GAAU,MAAK;EAAM,CAAA;CAAE;CACpE,aAAa;EAAE,OAAO;EAAe,MAAM,kBAAC,GAAD;GAAM,MAAM;GAAa,MAAK;EAAM,CAAA;CAAE;AACnF,GAEM,IAAsC;CAC1C,MAAM;CACN,WAAW;CACX,UAAU;AACZ,GAMM,IAA0B,EAAM,WACpC,SACE,EACE,iBAAc,CAAC,GACf,cAAW,CAAC,GACZ,eAAY,IACZ,WACA,iBACA,iBACA,aACA,cACA,GAAG,KAEL,GACA;CACF,IAAM,CAAC,GAAe,KAAoB,EAAS,EAAK,GAClD,CAAC,GAAc,KAAmB,EAAiB,QAAQ,GAC3D,CAAC,GAAY,KAAiB,EAAiB,QAAQ,GACvD,CAAC,GAAY,KAAiB,EAAiB,MAAM,GACrD,CAAC,GAAU,KAAe,EAAS,EAAK,GACxC,CAAC,GAAU,KAAe,EAAS,EAAK,GAExC,IAAY,YAAY;EACvB,OACL;KAAY,EAAI;GAChB,IAAI;IAQF,AAPA,MAAM,EAAO;KACX,WAAW,MAAiB,WAAW,OAAO;KAC9C,SAAS;KACT,SAAS;KACT,OAAO;IACT,CAAC,GACD,EAAiB,EAAK,GACtB,EAAU;GACZ,SAAS,GAAO;IACd,QAAQ,MAAM,iCAAiC,CAAK;GACtD,UAAU;IACR,EAAY,EAAK;GACnB;EAdgB;CAelB,GAEM,UAAkB;EAItB,AAHA,EAAgB,QAAQ,GACxB,EAAc,QAAQ,GACtB,EAAc,MAAM,GACpB,EAAY,EAAK;CACnB,GAEM,KAAkB,MACjB,IACW,EAAS,MAAM,MAAM,EAAE,OAAO,CACvC,CAAA,EAAS,SAAS,oBAFF;CAKzB,OACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EAAW;EAAgB;EAAW,GAAK;YAA3C,CACE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD;IAAW,WAAU;cAA6B;GAEvC,CAAA,GACX,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,eAAe,EAAiB,EAAI;cAHtC,CAKE,kBAAC,GAAD;KAAM,MAAM;KAAU,MAAK;KAAK,WAAU;IAAa,CAAA,GAAC,UAElD;KACE;MACZ,kBAAC,GAAD,EAAA,UACG,IACC,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,CAAU,CAAA;EACP,CAAA,IACH,EAAY,WAAW,IACzB,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,KAAD;IAAG,WAAU;cAAsC;GAGhD,CAAA;EACA,CAAA,IAEL,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAY,KAAK,GAAM,MAAM;IAC5B,IAAM,IACJ,EAAe,EAAK,YAAY,EAAe;IACjD,OACE,kBAAC,OAAD;KAEE,WAAW,EACT,wCACA,IAAI,EAAY,SAAS,KACvB,uCACJ;eANF;MASE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,QAAD;QAAM,WAAU;QAA0D,eAAY;kBAAQ,EAAY;OAAW,CAAA;MAClH,CAAA;MAGL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAY;OACZ,CAAA,GACH,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAe,EAAK,SAAS;OAC7B,CAAA,CACA;;MAGL,kBAAC,GAAD;OACE,OAAO,EAAK;OACZ,gBAAgB,MAAM,IAAe,GAAM,CAAC;iBAF9C,CAIE,kBAAC,GAAD;QACE,cAAY,iCAAiC,EAAY,MAAM,MAAM,EAAe,EAAK,SAAS;QAClG,WAAU;kBAEV,kBAAC,GAAD,CAAc,CAAA;OACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,OAAO,QAAQ,CAAW,CAAC,CAAC,KAAK,CAAC,GAAK,OACtC,kBAAC,GAAD;QAAsB,OAAO;kBAC1B;OACS,GAFK,CAEL,CACb,EACY,CAAA,CACT;;MAGR,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAK,QAAQ,UAAU;OACpB,CAAA,GACN,kBAAC,GAAD;QACE,cAAY,GAAG,EAAK,QAAQ,WAAW,OAAO,GAAG,EAAY,MAAM,OAAO,EAAe,EAAK,SAAS;QACvG,SAAS,CAAC,EAAK;QACf,uBAAuB,IAAe,CAAI;OAC3C,CAAA,CACE;;MAGL,kBAAC,GAAD;OACE,cAAY,UAAU,EAAY,MAAM;OACxC,MAAM,kBAAC,GAAD,EAAM,MAAM,EAAY,CAAA;OAC9B,SAAQ;OACR,OAAM;OACN,MAAK;OACL,eAAe,IAAW,EAAK,EAAE;OACjC,WAAU;MACX,CAAA;KACE;OAhEE,EAAK,EAgEP;GAET,CAAC;EACE,CAAA,EAEI,CAAA,CACT;KAGN,kBAAC,GAAD;EAAQ,MAAM;EAAe,cAAc;YACzC,kBAAC,GAAD;GAAe,WAAU;aAAzB,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAa,wBAAkC,CAAA,GAC/C,kBAAC,GAAD,EAAA,UAAmB,8EAGA,CAAA,CACP,EAAA,CAAA,GACd,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAa,WAAU;iBAAiD;MAEhF,CAAA,GACP,kBAAC,GAAD;OAAQ,OAAO;OAAc,eAAe;iBAA5C,CACE,kBAAC,GAAD;QAAe,IAAG;kBAChB,kBAAC,GAAD,EAAa,aAAY,eAAgB,CAAA;OAC5B,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;QAAY,OAAM;kBAAS;OAEf,CAAA,GACX,EAAS,KAAK,MACb,kBAAC,GAAD;QAAuB,OAAO,EAAE;kBAC7B,EAAE;OACO,GAFK,EAAE,EAEP,CACb,CACY,EAAA,CAAA,CACT;QACL;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAe,WAAU;iBAAiD;MAElF,CAAA,GACP,kBAAC,GAAD;OAAQ,OAAO;OAAY,eAAe;iBAA1C,CACE,kBAAC,GAAD;QAAe,IAAG;kBAChB,kBAAC,GAAD,CAAc,CAAA;OACD,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;QAAY,OAAM;kBAAS;OAAkB,CAAA,GAC7C,kBAAC,GAAD;QAAY,OAAM;kBAAc;OAAuB,CAAA,CAC1C,EAAA,CAAA,CACT;QACL;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAgB,WAAU;iBAAiD;MAEnF,CAAA,GACP,kBAAC,GAAD;OAAQ,OAAO;OAAY,eAAe;iBAA1C,CACE,kBAAC,GAAD;QAAe,IAAG;kBAChB,kBAAC,GAAD,CAAc,CAAA;OACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,OAAO,QAAQ,CAAW,CAAC,CAAC,KAAK,CAAC,GAAK,OACtC,kBAAC,GAAD;QAAsB,OAAO;kBAC1B;OACS,GAFK,CAEL,CACb,EACY,CAAA,CACT;QACL;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAa,WAAU;iBAA+B;MAE9D,CAAA,GACP,kBAAC,GAAD;OAAQ,IAAG;OAAa,SAAS;OAAU,iBAAiB;MAAc,CAAA,CACvE;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,SAAQ;OACR,eAAe;QAEb,AADA,EAAiB,EAAK,GACtB,EAAU;OACZ;iBACD;MAEO,CAAA,GACR,kBAAC,GAAD;OACE,SAAS;OACT,UAAU;iBAET,IAAW,cAAc;MACpB,CAAA,CACL;;IACF;KACQ;;CACT,CAAA,CACR,EAAA,CAAA;AAEJ,CACF;AAEA,EAAwB,cAAc"}
|
|
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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"}
|
package/dist/shell/top-bar.js
CHANGED
|
@@ -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-
|
|
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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"}
|
package/dist/tokens/index.css
CHANGED
|
@@ -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
|
+
─────────────────────────────────────────────────────────────────── */
|
package/dist/tokens/semantic.css
CHANGED
|
@@ -486,6 +486,10 @@
|
|
|
486
486
|
--shadow-warning: 0 2px 8px oklch(0.78 0.16 65 / 0.22), 0 6px 20px oklch(0.78 0.16 65 / 0.15);
|
|
487
487
|
|
|
488
488
|
/* Keyboard shortcut badge shadow */
|
|
489
|
+
/* A key cap's bottom bevel. Black works on a light cap; on a dark one it is
|
|
490
|
+
invisible (1.02:1), so the cap loses the only thing making it look pressable.
|
|
491
|
+
`.dark` flips it to a top highlight, which is how a raised surface catches
|
|
492
|
+
light from above in a dark UI. See the .dark block below. */
|
|
489
493
|
--shadow-kbd: inset 0 -1px 0 oklch(0 0 0 / 0.1);
|
|
490
494
|
|
|
491
495
|
/* ───────────────────────────────────────────────────────────
|
|
@@ -540,21 +544,31 @@
|
|
|
540
544
|
--shadow-strength: 1;
|
|
541
545
|
--shadow-transition: box-shadow var(--duration-fast-02) var(--ease-productive-standard);
|
|
542
546
|
|
|
547
|
+
/* Hairline edge ring — the shadow's own 1px border-substitute (make-kit rule #6:
|
|
548
|
+
shadow already carries the ring, so components never add an explicit border).
|
|
549
|
+
Light: cool tint. Dark: a light ring, since drop shadows barely read on dark.
|
|
550
|
+
|
|
551
|
+
Declared BEFORE the internals below because all four consume it. It used to
|
|
552
|
+
sit between sm and md, and only md/lg referenced it — xs and sm hardcoded
|
|
553
|
+
their own near-black ring and so never received the dark-mode correction.
|
|
554
|
+
That left `shadow-raised` / `-raised-hover` at 1.00–1.03:1 in dark, i.e. no
|
|
555
|
+
edge, for every component using them as its only boundary. */
|
|
556
|
+
--shadow-edge-ring: 0 0 0 1px oklch(var(--shadow-color) / calc(0.09 * var(--shadow-strength)));
|
|
557
|
+
/* xs/sm take the ring at a lighter weight than md/lg — a raised card should
|
|
558
|
+
not read as strongly as a floating overlay. `--shadow-edge-ring-subtle`
|
|
559
|
+
mirrors `--shadow-edge-ring` and is likewise dark-corrected below. */
|
|
560
|
+
--shadow-edge-ring-subtle: 0 0 0 1px oklch(var(--shadow-color) / calc(0.035 * var(--shadow-strength)));
|
|
543
561
|
--shadow-xs-internal:
|
|
544
|
-
|
|
562
|
+
var(--shadow-edge-ring-subtle),
|
|
545
563
|
0 0.5px 1px -0.5px oklch(var(--shadow-color) / calc(0.045 * var(--shadow-strength))),
|
|
546
564
|
0 1.5px 3px -1px oklch(var(--shadow-color) / calc(0.04 * var(--shadow-strength))),
|
|
547
565
|
0 3px 7px -2px oklch(var(--shadow-color) / calc(0.03 * var(--shadow-strength)));
|
|
548
566
|
--shadow-sm-internal:
|
|
549
|
-
|
|
567
|
+
var(--shadow-edge-ring-subtle),
|
|
550
568
|
0 0.5px 1px -0.5px oklch(var(--shadow-color) / calc(0.04 * var(--shadow-strength))),
|
|
551
569
|
0 2px 4px -1.5px oklch(var(--shadow-color) / calc(0.045 * var(--shadow-strength))),
|
|
552
570
|
0 6px 12px -3px oklch(var(--shadow-color) / calc(0.035 * var(--shadow-strength))),
|
|
553
571
|
0 14px 28px -8px oklch(var(--shadow-color) / calc(0.025 * var(--shadow-strength)));
|
|
554
|
-
/* Hairline edge ring — the shadow's own 1px border-substitute (make-kit rule #6:
|
|
555
|
-
shadow already carries the ring, so components never add an explicit border).
|
|
556
|
-
Light: cool tint. Dark: a light ring, since drop shadows barely read on dark. */
|
|
557
|
-
--shadow-edge-ring: 0 0 0 1px oklch(var(--shadow-color) / calc(0.09 * var(--shadow-strength)));
|
|
558
572
|
--shadow-md-internal:
|
|
559
573
|
var(--shadow-edge-ring),
|
|
560
574
|
0 1px 2px -1px oklch(var(--shadow-color) / calc(0.05 * var(--shadow-strength))),
|
|
@@ -668,9 +682,14 @@
|
|
|
668
682
|
--color-backdrop: oklch(0 0 0 / 0.6);
|
|
669
683
|
|
|
670
684
|
/* Segmented control — inverted elevation in dark: track is a faint LIGHTER
|
|
671
|
-
fill (can't go darker than a near-black page), thumb steps lighter still.
|
|
685
|
+
fill (can't go darker than a near-black page), thumb steps lighter still.
|
|
686
|
+
The model is right; the value was one step short. neutral-3 (#262626) is
|
|
687
|
+
DARKER than the track composited over a panel (#282828), so the selected
|
|
688
|
+
segment read as a dent — 1.028:1, inverted. neutral-5 clears the track on
|
|
689
|
+
both grounds a segmented control actually sits on: 1.46:1 over a panel,
|
|
690
|
+
1.69:1 over the page. */
|
|
672
691
|
--color-segment-track: color-mix(in oklch, oklch(1 0 0) 7%, transparent);
|
|
673
|
-
--color-segment-thumb: var(--neutral-
|
|
692
|
+
--color-segment-thumb: var(--neutral-5);
|
|
674
693
|
|
|
675
694
|
/* Overlay */
|
|
676
695
|
--color-overlay: oklch(0 0 0 / 0.70);
|
|
@@ -691,6 +710,18 @@
|
|
|
691
710
|
--shadow-strength: 2.5;
|
|
692
711
|
/* Light ring on dark — a dark-tinted ring would vanish against dark surfaces. */
|
|
693
712
|
--shadow-edge-ring: 0 0 0 1px oklch(1 0 0 / 0.12);
|
|
713
|
+
/* Same correction for the raised tier. Without this, `shadow-raised` /
|
|
714
|
+
`-raised-hover` keep a near-black ring that measures ~1.00:1 on a dark
|
|
715
|
+
ground, so anything using them as its only edge has none. 0.07 keeps a
|
|
716
|
+
raised card quieter than a floating overlay while still reading. */
|
|
717
|
+
--shadow-edge-ring-subtle: 0 0 0 1px oklch(1 0 0 / 0.07);
|
|
718
|
+
--shadow-kbd: inset 0 1px 0 oklch(1 0 0 / 0.10);
|
|
719
|
+
/* The segmented thumb is documented as deliberately ring-less, the shadow
|
|
720
|
+
being its whole edge. That works in light and cannot work in dark, where
|
|
721
|
+
the shadow is near-black on a near-black ground — so the fallback failed in
|
|
722
|
+
the same theme as the fill it was meant to rescue. In dark it takes a ring
|
|
723
|
+
like everything else. */
|
|
724
|
+
--shadow-segment: 0 0 0 1px oklch(1 0 0 / 0.07), 0 1px 2px -0.5px oklch(0 0 0 / 0.40), 0 3px 8px -2px oklch(0 0 0 / 0.30);
|
|
694
725
|
--shadow-brand: 0 2px 8px color-mix(in oklch, var(--color-accent-9) 30%, transparent), 0 6px 20px color-mix(in oklch, var(--color-accent-9) 20%, transparent);
|
|
695
726
|
--shadow-glow: 0 0 0 1.5px color-mix(in oklch, var(--color-accent-9) 35%, transparent), 0 0 10px color-mix(in oklch, var(--color-accent-9) 20%, transparent);
|
|
696
727
|
--shadow-ring: 0 0 0 2px color-mix(in oklch, var(--color-accent-9) 45%, transparent);
|
|
@@ -722,6 +753,39 @@
|
|
|
722
753
|
--color-text-shadow: oklch(0 0 0 / 0.40);
|
|
723
754
|
}
|
|
724
755
|
|
|
756
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
757
|
+
HIGH CONTRAST — opt-in, via `data-contrast="high"` on any ancestor
|
|
758
|
+
|
|
759
|
+
WCAG 2.2 SC 1.4.11 asks for 3:1 between a control's edge and its
|
|
760
|
+
background. Our default control edge is 2.00:1 in light and 2.08:1 in
|
|
761
|
+
dark — deliberately softer, and deliberately under the bar. That gap is
|
|
762
|
+
recorded in docs/deviations.md as PALETTE-CONTROL-EDGE-BELOW-AA.
|
|
763
|
+
|
|
764
|
+
This block is the opt-out from that decision. Set the attribute on
|
|
765
|
+
<html> (or any wrapper) and every control edge in the subtree moves to
|
|
766
|
+
3.64:1 light / 3.93:1 dark.
|
|
767
|
+
|
|
768
|
+
<html data-contrast="high"> <!-- whole app -->
|
|
769
|
+
<div data-contrast="high"> … </div> <!-- one region -->
|
|
770
|
+
|
|
771
|
+
It is an ATTRIBUTE rather than a React provider on purpose: `.dark`
|
|
772
|
+
already works this way, so a consumer's existing theme switcher can set
|
|
773
|
+
both, it needs no client component, and it survives SSR.
|
|
774
|
+
|
|
775
|
+
The steps are named, not literal, so a consumer who rebrands the neutral
|
|
776
|
+
ramp gets a correctly-scaled high-contrast mode for free. `--neutral-*`
|
|
777
|
+
is itself theme-aware, so ONE rule serves light and dark.
|
|
778
|
+
|
|
779
|
+
Placed after `.dark` deliberately: `[data-contrast='high']` and `.dark`
|
|
780
|
+
have the same specificity (0,1,0), so when both sit on <html> source
|
|
781
|
+
order is what decides. Moving this block above `.dark` silently breaks
|
|
782
|
+
high contrast in dark mode only.
|
|
783
|
+
═══════════════════════════════════════════════════════════════════ */
|
|
784
|
+
[data-contrast='high'] {
|
|
785
|
+
--color-surface-border-interactive: var(--neutral-8);
|
|
786
|
+
--color-surface-border-interactive-strong: var(--neutral-9);
|
|
787
|
+
}
|
|
788
|
+
|
|
725
789
|
/* ═══════════════════════════════════════════════════════════════════
|
|
726
790
|
Ensure Radix popper content (Select, Combobox, Popover, etc.)
|
|
727
791
|
renders above Sheet/Dialog overlays.
|