@loworbitstudio/visor 1.10.0 → 1.11.1
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/dist/CHANGELOG.json +13 -1
- package/dist/registry.json +57 -7
- package/dist/visor-manifest.json +185 -4
- package/package.json +1 -1
package/dist/CHANGELOG.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.4.0",
|
|
3
|
-
"generated_at": "2026-06-
|
|
3
|
+
"generated_at": "2026-06-15T22:40:15.943Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"accessibility-specimen": {
|
|
6
6
|
"changeType": "current",
|
|
@@ -230,6 +230,12 @@
|
|
|
230
230
|
"breakingChange": false,
|
|
231
231
|
"migrationNote": null
|
|
232
232
|
},
|
|
233
|
+
"error-placard": {
|
|
234
|
+
"changeType": "current",
|
|
235
|
+
"files": [],
|
|
236
|
+
"breakingChange": false,
|
|
237
|
+
"migrationNote": null
|
|
238
|
+
},
|
|
233
239
|
"field": {
|
|
234
240
|
"changeType": "current",
|
|
235
241
|
"files": [],
|
|
@@ -674,6 +680,12 @@
|
|
|
674
680
|
"breakingChange": false,
|
|
675
681
|
"migrationNote": null
|
|
676
682
|
},
|
|
683
|
+
"toast-card": {
|
|
684
|
+
"changeType": "current",
|
|
685
|
+
"files": [],
|
|
686
|
+
"breakingChange": false,
|
|
687
|
+
"migrationNote": null
|
|
688
|
+
},
|
|
677
689
|
"toggle-group": {
|
|
678
690
|
"changeType": "current",
|
|
679
691
|
"files": [],
|
package/dist/registry.json
CHANGED
|
@@ -66,12 +66,12 @@
|
|
|
66
66
|
{
|
|
67
67
|
"path": "components/ui/stepper/stepper.tsx",
|
|
68
68
|
"type": "registry:ui",
|
|
69
|
-
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Lock } from \"@phosphor-icons/react\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./stepper.module.css\"\n\n/* ─── Context ──────────────────────────────────────────────────────────────── */\n\ninterface StepperContextValue {\n activeStep: number\n orientation: \"horizontal\" | \"vertical\"\n}\n\nconst StepperContext = React.createContext<StepperContextValue>({\n activeStep: 0,\n orientation: \"horizontal\",\n})\n\nfunction useStepperContext() {\n return React.useContext(StepperContext)\n}\n\n/* ─── Stepper ──────────────────────────────────────────────────────────────── */\n\nexport interface StepperProps extends React.HTMLAttributes<HTMLDivElement> {\n activeStep?: number\n orientation?: \"horizontal\" | \"vertical\"\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n ({ className, activeStep = 0, orientation = \"horizontal\", children, ...props }, ref) => {\n const contextValue = React.useMemo(\n () => ({ activeStep, orientation }),\n [activeStep, orientation]\n )\n\n return (\n <StepperContext.Provider value={contextValue}>\n <div\n ref={ref}\n role=\"group\"\n aria-label=\"Progress\"\n data-slot=\"stepper\"\n data-orientation={orientation}\n className={cn(\n styles.stepper,\n orientation === \"vertical\" && styles.stepperVertical,\n className\n )}\n {...props}\n >\n {children}\n </div>\n </StepperContext.Provider>\n )\n }\n)\nStepper.displayName = \"Stepper\"\n\n/* ─── StepperItem ──────────────────────────────────────────────────────────── */\n\nexport interface StepperItemProps extends React.HTMLAttributes<HTMLDivElement> {\n step: number\n status?: \"complete\" | \"active\" | \"upcoming\" | \"locked\"\n}\n\nconst StepperItem = React.forwardRef<HTMLDivElement, StepperItemProps>(\n ({ className, step, status, children, ...props }, ref) => {\n const { activeStep, orientation } = useStepperContext()\n\n const resolvedStatus =\n status ?? (step < activeStep ? \"complete\" : step === activeStep ? \"active\" : \"upcoming\")\n\n return (\n <div\n ref={ref}\n data-slot=\"stepper-item\"\n data-step={step}\n data-status={resolvedStatus}\n data-orientation={orientation}\n aria-current={resolvedStatus === \"active\" ? \"step\" : undefined}\n className={cn(\n styles.item,\n orientation === \"vertical\" && styles.itemVertical,\n className\n )}\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nStepperItem.displayName = \"StepperItem\"\n\n/* ─── StepperTrigger ───────────────────────────────────────────────────────── */\n\nexport interface StepperTriggerProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n step: number\n status?: \"complete\" | \"active\" | \"upcoming\" | \"locked\"\n}\n\nconst StepperTrigger = React.forwardRef<HTMLButtonElement, StepperTriggerProps>(\n ({ className, step, status, children, onClick, ...props }, ref) => {\n const { activeStep } = useStepperContext()\n\n const resolvedStatus =\n status ?? (step < activeStep ? \"complete\" : step === activeStep ? \"active\" : \"upcoming\")\n\n const isLocked = resolvedStatus === \"locked\"\n\n const handleClick = isLocked\n ? undefined\n : onClick\n\n return (\n <button\n ref={ref}\n type=\"button\"\n data-slot=\"stepper-trigger\"\n data-status={resolvedStatus}\n aria-disabled={isLocked || undefined}\n tabIndex={isLocked ? -1 : undefined}\n className={cn(\n styles.trigger,\n styles[`trigger--${resolvedStatus}`],\n className\n )}\n onClick={handleClick}\n {...props}\n >\n {resolvedStatus === \"complete\" ? (\n <Check size={14} weight=\"bold\" aria-hidden=\"true\" />\n ) : resolvedStatus === \"locked\" ? (\n <Lock size={12} weight=\"bold\" aria-hidden=\"true\" />\n ) : (\n children ?? step + 1\n )}\n <span className={styles.srOnly}>\n {resolvedStatus === \"complete\"\n ? \"Completed\"\n : resolvedStatus === \"locked\"\n ? `Step ${step + 1}, locked`\n : `Step ${step + 1}`}\n </span>\n </button>\n )\n }\n)\nStepperTrigger.displayName = \"StepperTrigger\"\n\n/* ─── StepperTitle ─────────────────────────────────────────────────────────── */\n\nconst StepperTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-slot=\"stepper-title\"\n className={cn(styles.title, className)}\n {...props}\n />\n )\n }\n)\nStepperTitle.displayName = \"StepperTitle\"\n\n/* ─── StepperDescription ───────────────────────────────────────────────────── */\n\nconst StepperDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n return (\n <p\n ref={ref}\n data-slot=\"stepper-description\"\n className={cn(styles.description, className)}\n {...props}\n />\n )\n})\nStepperDescription.displayName = \"StepperDescription\"\n\n/* ─── StepperSeparator ─────────────────────────────────────────────────────── */\n\nexport interface StepperSeparatorProps extends React.HTMLAttributes<HTMLDivElement> {\n complete?: boolean\n}\n\nconst StepperSeparator = React.forwardRef<HTMLDivElement, StepperSeparatorProps>(\n ({ className, complete = false, ...props }, ref) => {\n const { orientation } = useStepperContext()\n\n return (\n <div\n ref={ref}\n role=\"separator\"\n data-slot=\"stepper-separator\"\n data-orientation={orientation}\n data-complete={complete || undefined}\n className={cn(\n styles.separator,\n orientation === \"vertical\" && styles.separatorVertical,\n complete && styles.separatorComplete,\n className\n )}\n {...props}\n />\n )\n }\n)\nStepperSeparator.displayName = \"StepperSeparator\"\n\nexport {\n Stepper,\n StepperItem,\n StepperTrigger,\n StepperTitle,\n StepperDescription,\n StepperSeparator,\n}\n"
|
|
69
|
+
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Lock } from \"@phosphor-icons/react\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./stepper.module.css\"\n\n/* ─── Context ──────────────────────────────────────────────────────────────── */\n\ninterface StepperContextValue {\n activeStep: number\n orientation: \"horizontal\" | \"vertical\"\n variant: \"default\" | \"prominent\"\n}\n\nconst StepperContext = React.createContext<StepperContextValue>({\n activeStep: 0,\n orientation: \"horizontal\",\n variant: \"default\",\n})\n\nfunction useStepperContext() {\n return React.useContext(StepperContext)\n}\n\n/* ─── Stepper ──────────────────────────────────────────────────────────────── */\n\nexport interface StepperProps extends React.HTMLAttributes<HTMLDivElement> {\n activeStep?: number\n orientation?: \"horizontal\" | \"vertical\"\n /**\n * Visual treatment variant.\n * - `\"default\"` — standard step indicator, suitable for multi-step forms and\n * linear wizards. Use anywhere a stepper supports secondary navigation.\n * - `\"prominent\"` — enhanced treatment for vertical derivation spines or\n * primary-navigation surfaces: active row gets a primary-soft tint, the\n * active bullet renders a concentric halo + filled pulse dot (instead of\n * a step number), and complete-to-next rails render in a primary-line tint.\n * Best used vertically. Fully theme-agnostic — tokens resolve against the\n * active theme, so monochromatic themes stay restrained while colorful ones\n * get the intended pop.\n */\n variant?: \"default\" | \"prominent\"\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n ({ className, activeStep = 0, orientation = \"horizontal\", variant = \"default\", children, ...props }, ref) => {\n const contextValue = React.useMemo(\n () => ({ activeStep, orientation, variant }),\n [activeStep, orientation, variant]\n )\n\n return (\n <StepperContext.Provider value={contextValue}>\n <div\n ref={ref}\n role=\"group\"\n aria-label=\"Progress\"\n data-slot=\"stepper\"\n data-orientation={orientation}\n data-variant={variant}\n className={cn(\n styles.stepper,\n orientation === \"vertical\" && styles.stepperVertical,\n className\n )}\n {...props}\n >\n {children}\n </div>\n </StepperContext.Provider>\n )\n }\n)\nStepper.displayName = \"Stepper\"\n\n/* ─── StepperItem ──────────────────────────────────────────────────────────── */\n\nexport interface StepperItemProps extends React.HTMLAttributes<HTMLDivElement> {\n step: number\n status?: \"complete\" | \"active\" | \"upcoming\" | \"locked\"\n}\n\nconst StepperItem = React.forwardRef<HTMLDivElement, StepperItemProps>(\n ({ className, step, status, children, ...props }, ref) => {\n const { activeStep, orientation, variant } = useStepperContext()\n\n const resolvedStatus =\n status ?? (step < activeStep ? \"complete\" : step === activeStep ? \"active\" : \"upcoming\")\n\n return (\n <div\n ref={ref}\n data-slot=\"stepper-item\"\n data-step={step}\n data-status={resolvedStatus}\n data-orientation={orientation}\n data-variant={variant}\n aria-current={resolvedStatus === \"active\" ? \"step\" : undefined}\n className={cn(\n styles.item,\n orientation === \"vertical\" && styles.itemVertical,\n variant === \"prominent\" && styles.itemProminent,\n variant === \"prominent\" && resolvedStatus === \"active\" && styles.itemProminentActive,\n className\n )}\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nStepperItem.displayName = \"StepperItem\"\n\n/* ─── StepperTrigger ───────────────────────────────────────────────────────── */\n\nexport interface StepperTriggerProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n step: number\n status?: \"complete\" | \"active\" | \"upcoming\" | \"locked\"\n}\n\nconst StepperTrigger = React.forwardRef<HTMLButtonElement, StepperTriggerProps>(\n ({ className, step, status, children, onClick, ...props }, ref) => {\n const { activeStep, variant } = useStepperContext()\n\n const resolvedStatus =\n status ?? (step < activeStep ? \"complete\" : step === activeStep ? \"active\" : \"upcoming\")\n\n const isLocked = resolvedStatus === \"locked\"\n const isProminentActive = variant === \"prominent\" && resolvedStatus === \"active\"\n\n const handleClick = isLocked\n ? undefined\n : onClick\n\n return (\n <button\n ref={ref}\n type=\"button\"\n data-slot=\"stepper-trigger\"\n data-status={resolvedStatus}\n data-variant={variant}\n aria-disabled={isLocked || undefined}\n tabIndex={isLocked ? -1 : undefined}\n className={cn(\n styles.trigger,\n styles[`trigger--${resolvedStatus}`],\n variant === \"prominent\" && styles[`triggerProminent--${resolvedStatus}`],\n className\n )}\n onClick={handleClick}\n {...props}\n >\n {resolvedStatus === \"complete\" ? (\n <Check size={14} weight=\"bold\" aria-hidden=\"true\" />\n ) : resolvedStatus === \"locked\" ? (\n <Lock size={12} weight=\"bold\" aria-hidden=\"true\" />\n ) : isProminentActive ? (\n /* Pulse dot replaces the step number in prominent active state */\n <span className={styles.pulseDot} aria-hidden=\"true\" />\n ) : (\n children ?? step + 1\n )}\n <span className={styles.srOnly}>\n {resolvedStatus === \"complete\"\n ? \"Completed\"\n : resolvedStatus === \"locked\"\n ? `Step ${step + 1}, locked`\n : `Step ${step + 1}`}\n </span>\n </button>\n )\n }\n)\nStepperTrigger.displayName = \"StepperTrigger\"\n\n/* ─── StepperTitle ─────────────────────────────────────────────────────────── */\n\nconst StepperTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-slot=\"stepper-title\"\n className={cn(styles.title, className)}\n {...props}\n />\n )\n }\n)\nStepperTitle.displayName = \"StepperTitle\"\n\n/* ─── StepperDescription ───────────────────────────────────────────────────── */\n\nconst StepperDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n return (\n <p\n ref={ref}\n data-slot=\"stepper-description\"\n className={cn(styles.description, className)}\n {...props}\n />\n )\n})\nStepperDescription.displayName = \"StepperDescription\"\n\n/* ─── StepperSeparator ─────────────────────────────────────────────────────── */\n\nexport interface StepperSeparatorProps extends React.HTMLAttributes<HTMLDivElement> {\n complete?: boolean\n}\n\nconst StepperSeparator = React.forwardRef<HTMLDivElement, StepperSeparatorProps>(\n ({ className, complete = false, ...props }, ref) => {\n const { orientation, variant } = useStepperContext()\n\n return (\n <div\n ref={ref}\n role=\"separator\"\n data-slot=\"stepper-separator\"\n data-orientation={orientation}\n data-complete={complete || undefined}\n data-variant={variant}\n className={cn(\n styles.separator,\n orientation === \"vertical\" && styles.separatorVertical,\n complete && styles.separatorComplete,\n variant === \"prominent\" && complete && styles.separatorProminentComplete,\n className\n )}\n {...props}\n />\n )\n }\n)\nStepperSeparator.displayName = \"StepperSeparator\"\n\nexport {\n Stepper,\n StepperItem,\n StepperTrigger,\n StepperTitle,\n StepperDescription,\n StepperSeparator,\n}\n"
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"path": "components/ui/stepper/stepper.module.css",
|
|
73
73
|
"type": "registry:ui",
|
|
74
|
-
"content": "/* Stepper container */\n.stepper {\n display: flex;\n align-items: flex-start;\n gap: 0;\n width: 100%;\n}\n\n.stepperVertical {\n flex-direction: column;\n}\n\n/* Stepper item */\n.item {\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n flex: 1;\n min-width: 0;\n}\n\n.itemVertical {\n flex-direction: row;\n align-items: flex-start;\n}\n\n/* A bare single-line title centers against the 2rem trigger; wrapped\n * title+description blocks keep the flex-start top alignment. */\n.itemVertical > .title {\n line-height: 2rem;\n}\n\n/* Trigger (step indicator circle) */\n.trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 2rem;\n height: 2rem;\n border-radius: var(--radius-full, 9999px);\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n border: 2px solid transparent;\n cursor: pointer;\n transition: background-color var(--motion-duration-200, 200ms) var(--motion-easing-default, ease-in-out),\n border-color var(--motion-duration-200, 200ms) var(--motion-easing-default, ease-in-out),\n color var(--motion-duration-200, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n.trigger:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Trigger status variants */\n.trigger--complete {\n background-color: var(--interactive-primary-bg, var(--primary, #111827));\n border-color: var(--interactive-primary-bg, var(--primary, #111827));\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n.trigger--active {\n background-color: transparent;\n border-color: var(--interactive-primary-bg, var(--primary, #111827));\n color: var(--interactive-primary-bg, var(--primary, #111827));\n}\n\n.trigger--upcoming {\n background-color: transparent;\n border-color: var(--border-default, #e5e7eb);\n color: var(--text-tertiary, #9ca3af);\n}\n\n.trigger--locked {\n background-color: transparent;\n border-color: var(--border-muted, #e5e7eb);\n color: var(--text-tertiary, #9ca3af);\n cursor: not-allowed;\n opacity: var(--opacity-60, 0.6);\n}\n\n/* Locked item — muted label */\n.item[data-status=\"locked\"] .title {\n color: var(--text-secondary, #6b7280);\n font-weight: var(--font-weight-normal, 400);\n}\n\n/* Title */\n.title {\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-primary, #111827);\n line-height: var(--line-height-snug, 1.375);\n white-space: nowrap;\n}\n\n/* Description */\n.description {\n font-size: var(--font-size-xs, 0.75rem);\n color: var(--text-secondary, #6b7280);\n line-height: var(--line-height-normal, 1.5);\n margin: 0;\n}\n\n/* Separator */\n.separator {\n flex: 1;\n height: 2px;\n min-width: var(--spacing-4, 1rem);\n background-color: var(--border-muted, #e5e7eb);\n align-self: center;\n margin: 0 var(--spacing-2, 0.5rem);\n transition: background-color var(--motion-duration-200, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n.separatorVertical {\n width: 2px;\n height: var(--spacing-6, 1.5rem);\n min-width: auto;\n margin: var(--spacing-1, 0.25rem) 0 var(--spacing-1, 0.25rem) calc(1rem - 1px);\n align-self: flex-start;\n flex: none;\n}\n\n.separatorComplete {\n background-color: var(--interactive-primary-bg, var(--primary, #111827));\n}\n\n/* Screen reader only */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n"
|
|
74
|
+
"content": "/* Stepper container */\n.stepper {\n display: flex;\n align-items: flex-start;\n gap: 0;\n width: 100%;\n}\n\n.stepperVertical {\n flex-direction: column;\n}\n\n/* Stepper item */\n.item {\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n flex: 1;\n min-width: 0;\n}\n\n.itemVertical {\n flex-direction: row;\n align-items: flex-start;\n}\n\n/* A bare single-line title centers against the 2rem trigger; wrapped\n * title+description blocks keep the flex-start top alignment. */\n.itemVertical > .title {\n line-height: 2rem;\n}\n\n/* Trigger (step indicator circle) */\n.trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 2rem;\n height: 2rem;\n border-radius: var(--radius-full, 9999px);\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n border: 2px solid transparent;\n cursor: pointer;\n transition: background-color var(--motion-duration-200, 200ms) var(--motion-easing-default, ease-in-out),\n border-color var(--motion-duration-200, 200ms) var(--motion-easing-default, ease-in-out),\n color var(--motion-duration-200, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n.trigger:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Trigger status variants */\n.trigger--complete {\n background-color: var(--interactive-primary-bg, var(--primary, #111827));\n border-color: var(--interactive-primary-bg, var(--primary, #111827));\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n.trigger--active {\n background-color: transparent;\n border-color: var(--interactive-primary-bg, var(--primary, #111827));\n color: var(--interactive-primary-bg, var(--primary, #111827));\n}\n\n.trigger--upcoming {\n background-color: transparent;\n border-color: var(--border-default, #e5e7eb);\n color: var(--text-tertiary, #9ca3af);\n}\n\n.trigger--locked {\n background-color: transparent;\n border-color: var(--border-muted, #e5e7eb);\n color: var(--text-tertiary, #9ca3af);\n cursor: not-allowed;\n opacity: var(--opacity-60, 0.6);\n}\n\n/* Locked item — muted label */\n.item[data-status=\"locked\"] .title {\n color: var(--text-secondary, #6b7280);\n font-weight: var(--font-weight-normal, 400);\n}\n\n/* Title */\n.title {\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-primary, #111827);\n line-height: var(--line-height-snug, 1.375);\n white-space: nowrap;\n}\n\n/* Description */\n.description {\n font-size: var(--font-size-xs, 0.75rem);\n color: var(--text-secondary, #6b7280);\n line-height: var(--line-height-normal, 1.5);\n margin: 0;\n}\n\n/* Separator */\n.separator {\n flex: 1;\n height: 2px;\n min-width: var(--spacing-4, 1rem);\n background-color: var(--border-muted, #e5e7eb);\n align-self: center;\n margin: 0 var(--spacing-2, 0.5rem);\n transition: background-color var(--motion-duration-200, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n.separatorVertical {\n width: 2px;\n height: var(--spacing-6, 1.5rem);\n min-width: auto;\n margin: var(--spacing-1, 0.25rem) 0 var(--spacing-1, 0.25rem) calc(1rem - 1px);\n align-self: flex-start;\n flex: none;\n}\n\n.separatorComplete {\n background-color: var(--interactive-primary-bg, var(--primary, #111827));\n}\n\n/* Screen reader only */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n\n/* ─── Prominent variant ────────────────────────────────────────────────────── */\n\n/*\n * Prominent variant tokens (all via the active theme; no hardcoded colors):\n * --interactive-primary-bg — solid primary fill (complete bullet, active border)\n * --interactive-primary-text — contrast text on primary bg (complete bullet icon)\n * --interactive-primary-soft — ~12% primary alpha overlay (active row tint, halo)\n * --interactive-primary-glow — ~32% primary alpha overlay (stronger halo ring if needed)\n *\n * No --primary-line token is emitted; the tinted rail uses color-mix at ~34%.\n */\n\n/* Active row tint */\n.itemProminent {\n border-radius: var(--radius-lg, 8px);\n padding: var(--spacing-2, 0.5rem) var(--spacing-2, 0.5rem);\n margin: 0 calc(var(--spacing-2, 0.5rem) * -1);\n}\n\n.itemProminentActive {\n background-color: var(--interactive-primary-soft, color-mix(in srgb, var(--interactive-primary-bg, #111827) 12%, transparent));\n}\n\n/* Active row label color */\n.itemProminentActive .title {\n color: var(--interactive-primary-bg, #111827);\n}\n\n/* Prominent trigger — complete state: identical to default (already solid primary) */\n.triggerProminent--complete {\n /* no extra styles needed; default trigger--complete already matches */\n}\n\n/* Prominent trigger — active state: primary border + halo */\n.triggerProminent--active {\n border-color: var(--interactive-primary-bg, #111827);\n box-shadow: 0 0 0 var(--spacing-1, 0.25rem) var(--interactive-primary-soft, color-mix(in srgb, var(--interactive-primary-bg, #111827) 12%, transparent));\n}\n\n/* Prominent trigger — upcoming/locked: unchanged (no extra styles) */\n.triggerProminent--upcoming {}\n.triggerProminent--locked {}\n\n/* Pulse dot — primary-filled inner dot for active prominent trigger */\n.pulseDot {\n display: block;\n width: var(--spacing-2, 0.5rem);\n height: var(--spacing-2, 0.5rem);\n border-radius: var(--radius-full, 9999px);\n background-color: var(--interactive-primary-bg, #111827);\n flex-shrink: 0;\n}\n\n/* Prominent separator — complete rail tinted primary-line (~34% primary) */\n.separatorProminentComplete {\n background-color: color-mix(in srgb, var(--interactive-primary-bg, #111827) 34%, transparent);\n}\n"
|
|
75
75
|
}
|
|
76
76
|
]
|
|
77
77
|
},
|
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
{
|
|
250
250
|
"path": "components/ui/checkbox/checkbox.module.css",
|
|
251
251
|
"type": "registry:ui",
|
|
252
|
-
"content": "/* Checkbox root styles\n Default rendering uses canonical values directly. Under data-density=\"editorial\"\n (set on any ancestor), the editorial density axis bakes in the blessed admin\n values: 1.125rem box, 4px radius, var(--surface-subtle) background, and\n var(--hairline-strong) border. */\n.root {\n position: relative;\n display: flex;\n width: 1rem;\n height: 1rem;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n border: 1px solid var(--border-default, #e5e7eb);\n background-color: transparent;\n transition: border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), box-shadow var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n outline: none;\n cursor: pointer;\n}\n\n:global([data-density=\"editorial\"]) .root {\n width: 1.125rem;\n height: 1.125rem;\n border-radius: 4px;\n border-color: var(--hairline-strong, #e5e7eb);\n background-color: var(--surface-subtle);\n}\n\n@media (hover: hover) {\n .root:hover:not(:focus-visible):not(:disabled):not([aria-invalid=\"true\"]) {\n border-color: var(--border-strong, #d1d5db);\n background-color: transparent;\n }\n}\n\n@media (hover: hover) {\n :global([data-density=\"editorial\"]) .root:hover:not(:focus-visible):not(:disabled):not([aria-invalid=\"true\"]) {\n background-color: var(--surface-subtle);\n }\n}\n\n.root:focus-visible {\n border-color: var(--border-focus, #111827);\n box-shadow: 0 0 0 var(--focus-ring-width, 2px) color-mix(in srgb, var(--border-focus, #111827) 15%, transparent);\n}\n\n.root:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.root[aria-invalid=\"true\"] {\n border-color: var(--border-error, #ef4444);\n box-shadow: 0 0 0 var(--focus-ring-width, 2px) color-mix(in srgb, var(--border-error, #ef4444) 15%, transparent);\n}\n\n.root[data-state=\"checked\"] {\n border-color: var(--checkbox-border-checked, var(--interactive-primary-bg, var(--primary, #111827)));\n background-color: var(--checkbox-bg-checked, var(--interactive-primary-bg, var(--primary, #111827)));\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n.root[data-state=\"indeterminate\"] {\n border-color: var(--checkbox-border-checked, var(--interactive-primary-bg, var(--primary, #111827)));\n background-color: var(--checkbox-bg-checked, var(--interactive-primary-bg, var(--primary, #111827)));\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n/* Indicator */\n.indicator {\n display: grid;\n place-content: center;\n color: currentColor;\n transition: none;\n}\n\n.icon {\n width: 0.875rem;\n height: 0.875rem;\n}\n"
|
|
252
|
+
"content": "/* Checkbox root styles\n Default rendering uses canonical values directly. Under data-density=\"editorial\"\n (set on any ancestor), the editorial density axis bakes in the blessed admin\n values: 1.125rem box, 4px radius, var(--surface-subtle) background, and\n var(--hairline-strong) border. */\n.root {\n position: relative;\n display: flex;\n width: 1rem;\n height: 1rem;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n border: 1px solid var(--border-default, #e5e7eb);\n background-color: transparent;\n transition: border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), box-shadow var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n outline: none;\n cursor: pointer;\n}\n\n:global([data-density=\"editorial\"]) .root {\n width: 1.125rem;\n height: 1.125rem;\n border-radius: 4px;\n border-color: var(--hairline-strong, #e5e7eb);\n background-color: var(--surface-subtle);\n}\n\n@media (hover: hover) {\n .root:hover:not(:focus-visible):not(:disabled):not([aria-invalid=\"true\"]):not([data-state=\"checked\"]):not([data-state=\"indeterminate\"]) {\n border-color: var(--border-strong, #d1d5db);\n background-color: transparent;\n }\n}\n\n@media (hover: hover) {\n :global([data-density=\"editorial\"]) .root:hover:not(:focus-visible):not(:disabled):not([aria-invalid=\"true\"]):not([data-state=\"checked\"]):not([data-state=\"indeterminate\"]) {\n background-color: var(--surface-subtle);\n }\n}\n\n.root:focus-visible {\n border-color: var(--border-focus, #111827);\n box-shadow: 0 0 0 var(--focus-ring-width, 2px) color-mix(in srgb, var(--border-focus, #111827) 15%, transparent);\n}\n\n.root:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.root[aria-invalid=\"true\"] {\n border-color: var(--border-error, #ef4444);\n box-shadow: 0 0 0 var(--focus-ring-width, 2px) color-mix(in srgb, var(--border-error, #ef4444) 15%, transparent);\n}\n\n.root[data-state=\"checked\"] {\n border-color: var(--checkbox-border-checked, var(--interactive-primary-bg, var(--primary, #111827)));\n background-color: var(--checkbox-bg-checked, var(--interactive-primary-bg, var(--primary, #111827)));\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n.root[data-state=\"indeterminate\"] {\n border-color: var(--checkbox-border-checked, var(--interactive-primary-bg, var(--primary, #111827)));\n background-color: var(--checkbox-bg-checked, var(--interactive-primary-bg, var(--primary, #111827)));\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n/* Indicator */\n.indicator {\n display: grid;\n place-content: center;\n color: currentColor;\n transition: none;\n}\n\n.icon {\n width: 0.875rem;\n height: 0.875rem;\n}\n"
|
|
253
253
|
}
|
|
254
254
|
]
|
|
255
255
|
},
|
|
@@ -443,12 +443,12 @@
|
|
|
443
443
|
{
|
|
444
444
|
"path": "components/ui/chip/chip.tsx",
|
|
445
445
|
"type": "registry:ui",
|
|
446
|
-
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { XIcon } from \"@phosphor-icons/react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./chip.module.css\"\n\n/* ─── Chip (base) ────────────────────────────────────────────────────── */\n\nconst chipVariants = cva(styles.base, {\n variants: {\n variant: {\n default: styles.variantDefault,\n outlined: styles.variantOutlined,\n },\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n },\n})\n\nexport interface ChipProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof chipVariants> {\n /** Optional avatar/icon rendered before the label. */\n avatar?: React.ReactNode\n /** Optional leading icon rendered before the label (after avatar). */\n leadingIcon?: React.ReactNode\n /** Label content. Renders as children if omitted. */\n label?: React.ReactNode\n /** Custom delete icon. Defaults to XIcon. */\n deleteIcon?: React.ReactNode\n /** Called when the delete button is activated. When provided the delete button is shown. */\n onDeleted?: () => void\n /** Aria label for the delete button. Defaults to \"Remove\". */\n deleteLabel?: string\n}\n\nconst Chip = React.forwardRef<HTMLDivElement, ChipProps>(\n (\n {\n className,\n variant,\n size,\n avatar,\n leadingIcon,\n label,\n children,\n deleteIcon,\n onDeleted,\n deleteLabel = \"Remove\",\n ...props\n },\n ref,\n ) => {\n const content = label ?? children\n\n return (\n <div\n ref={ref}\n data-slot=\"chip\"\n data-variant={variant ?? \"default\"}\n data-size={size ?? \"md\"}\n className={cn(chipVariants({ variant, size }), className)}\n {...props}\n >\n {avatar ? (\n <span className={styles.avatar} aria-hidden=\"true\">\n {avatar}\n </span>\n ) : null}\n {leadingIcon ? (\n <span className={styles.leadingIcon} aria-hidden=\"true\">\n {leadingIcon}\n </span>\n ) : null}\n <span className={styles.label}>{content}</span>\n {onDeleted ? (\n <button\n type=\"button\"\n aria-label={deleteLabel}\n className={styles.deleteButton}\n onClick={(e) => {\n e.stopPropagation()\n onDeleted()\n }}\n tabIndex={0}\n >\n {deleteIcon ?? (\n <XIcon\n size={12}\n weight=\"bold\"\n aria-hidden=\"true\"\n className={styles.deleteIcon}\n />\n )}\n </button>\n ) : null}\n </div>\n )\n },\n)\nChip.displayName = \"Chip\"\n\n/* ─── ChoiceChip (radio-style single-select) ─────────────────────────── */\n\nexport interface ChoiceChipProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onClick\">,\n VariantProps<typeof chipVariants> {\n /** Whether this chip is currently selected. */\n selected?: boolean\n /** Optional avatar/icon rendered before the label. */\n avatar?: React.ReactNode\n /** Optional leading icon rendered before the label. */\n leadingIcon?: React.ReactNode\n /** Label text or content. */\n label?: React.ReactNode\n /** Called when the chip is pressed. */\n onPressed?: () => void\n /** Value used when in a ChipGroup. */\n value?: string\n}\n\nconst ChoiceChip = React.forwardRef<HTMLButtonElement, ChoiceChipProps>(\n (\n {\n className,\n variant,\n size,\n selected = false,\n avatar,\n leadingIcon,\n label,\n children,\n onPressed,\n value,\n disabled,\n ...props\n },\n ref,\n ) => {\n const content = label ?? children\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n data-slot=\"choice-chip\"\n data-variant={variant ?? \"default\"}\n data-size={size ?? \"md\"}\n data-selected={selected ? \"true\" : \"false\"}\n data-value={value}\n disabled={disabled}\n className={cn(\n chipVariants({ variant, size }),\n styles.interactive,\n selected && styles.selected,\n className,\n )}\n onClick={onPressed}\n {...props}\n >\n {avatar ? (\n <span className={styles.avatar} aria-hidden=\"true\">\n {avatar}\n </span>\n ) : null}\n {leadingIcon ? (\n <span className={styles.leadingIcon} aria-hidden=\"true\">\n {leadingIcon}\n </span>\n ) : null}\n <span className={styles.label}>{content}</span>\n </button>\n )\n },\n)\nChoiceChip.displayName = \"ChoiceChip\"\n\n/* ─── FilterChip (multi-select toggle-style) ─────────────────────────── */\n\nexport interface FilterChipProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onClick\">,\n VariantProps<typeof chipVariants> {\n /** Whether this chip is currently active/selected. */\n selected?: boolean\n /** Optional leading icon. */\n leadingIcon?: React.ReactNode\n /** Label text or content. */\n label?: React.ReactNode\n /** Called when the chip is toggled. */\n onPressed?: () => void\n /** Value used when in a ChipGroup. */\n value?: string\n /** Optional count pill rendered after the label (e.g. \"47\", 132). */\n count?: React.ReactNode\n /** Tone for the count pill. \"neutral\" uses muted surface; \"primary\" uses accent surface. Defaults to \"neutral\". */\n countTone?: \"primary\" | \"neutral\"\n /**\n * Selected-state visual treatment.\n * - `\"accent\"` (default) — accent-tinted background + accent border + link text colour.\n * - `\"neutral\"` — neutral-elevated surface (--surface-card) + neutral border + primary text;\n * the count pill renders as a solid mint pill (--surface-success-default) when selected.\n * Use for editorial / admin contexts where accent bleed across shared tokens is undesirable.\n */\n selectedTreatment?: \"accent\" | \"neutral\"\n /** Optional trailing icon rendered after the count (e.g. a dropdown caret) for \"filter-as-dropdown\" chips. Muted by default. */\n trailingIcon?: React.ReactNode\n}\n\nconst FilterChip = React.forwardRef<HTMLButtonElement, FilterChipProps>(\n (\n {\n className,\n variant,\n size,\n selected = false,\n leadingIcon,\n label,\n children,\n onPressed,\n value,\n disabled,\n count,\n countTone = \"neutral\",\n selectedTreatment = \"accent\",\n trailingIcon,\n ...props\n },\n ref,\n ) => {\n const content = label ?? children\n const isNeutral = selectedTreatment === \"neutral\"\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"checkbox\"\n aria-checked={selected}\n data-slot=\"filter-chip\"\n data-variant={variant ?? \"default\"}\n data-size={size ?? \"md\"}\n data-selected={selected ? \"true\" : \"false\"}\n data-selected-treatment={selectedTreatment}\n data-value={value}\n disabled={disabled}\n className={cn(\n chipVariants({ variant, size }),\n styles.interactive,\n styles.filterChip,\n selected && (isNeutral ? styles.selectedNeutral : styles.selected),\n className,\n )}\n onClick={onPressed}\n {...props}\n >\n {leadingIcon ? (\n <span className={styles.leadingIcon} aria-hidden=\"true\">\n {leadingIcon}\n </span>\n ) : null}\n <span className={styles.label}>{content}</span>\n {count != null ? (\n <span\n data-slot=\"filter-chip-count\"\n data-tone={countTone}\n aria-hidden=\"false\"\n className={cn(\n styles.count,\n isNeutral && selected\n ? styles.countSolid\n : countTone === \"primary\"\n ? styles.countPrimary\n : styles.countNeutral,\n )}\n >\n {count}\n </span>\n ) : null}\n {trailingIcon ? (\n <span className={styles.trailing} aria-hidden=\"true\">\n {trailingIcon}\n </span>\n ) : null}\n </button>\n )\n },\n)\nFilterChip.displayName = \"FilterChip\"\n\nexport { Chip, chipVariants, ChoiceChip, FilterChip }\n"
|
|
446
|
+
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { XIcon } from \"@phosphor-icons/react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./chip.module.css\"\n\n/* ─── Chip (base) ────────────────────────────────────────────────────── */\n\nconst chipVariants = cva(styles.base, {\n variants: {\n variant: {\n default: styles.variantDefault,\n outlined: styles.variantOutlined,\n \"filled-primary\": styles.variantFilledPrimary,\n },\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n },\n})\n\nexport interface ChipProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof chipVariants> {\n /** Optional avatar/icon rendered before the label. */\n avatar?: React.ReactNode\n /** Optional leading icon rendered before the label (after avatar). */\n leadingIcon?: React.ReactNode\n /** Label content. Renders as children if omitted. */\n label?: React.ReactNode\n /** Custom delete icon. Defaults to XIcon. */\n deleteIcon?: React.ReactNode\n /** Called when the delete button is activated. When provided the delete button is shown. */\n onDeleted?: () => void\n /** Aria label for the delete button. Defaults to \"Remove\". */\n deleteLabel?: string\n}\n\nconst Chip = React.forwardRef<HTMLDivElement, ChipProps>(\n (\n {\n className,\n variant,\n size,\n avatar,\n leadingIcon,\n label,\n children,\n deleteIcon,\n onDeleted,\n deleteLabel = \"Remove\",\n ...props\n },\n ref,\n ) => {\n const content = label ?? children\n\n return (\n <div\n ref={ref}\n data-slot=\"chip\"\n data-variant={variant ?? \"default\"}\n data-size={size ?? \"md\"}\n className={cn(chipVariants({ variant, size }), className)}\n {...props}\n >\n {avatar ? (\n <span className={styles.avatar} aria-hidden=\"true\">\n {avatar}\n </span>\n ) : null}\n {leadingIcon ? (\n <span className={styles.leadingIcon} aria-hidden=\"true\">\n {leadingIcon}\n </span>\n ) : null}\n <span className={styles.label}>{content}</span>\n {onDeleted ? (\n <button\n type=\"button\"\n aria-label={deleteLabel}\n className={styles.deleteButton}\n onClick={(e) => {\n e.stopPropagation()\n onDeleted()\n }}\n tabIndex={0}\n >\n {deleteIcon ?? (\n <XIcon\n size={12}\n weight=\"bold\"\n aria-hidden=\"true\"\n className={styles.deleteIcon}\n />\n )}\n </button>\n ) : null}\n </div>\n )\n },\n)\nChip.displayName = \"Chip\"\n\n/* ─── ChoiceChip (radio-style single-select) ─────────────────────────── */\n\nexport interface ChoiceChipProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onClick\">,\n VariantProps<typeof chipVariants> {\n /** Whether this chip is currently selected. */\n selected?: boolean\n /** Optional avatar/icon rendered before the label. */\n avatar?: React.ReactNode\n /** Optional leading icon rendered before the label. */\n leadingIcon?: React.ReactNode\n /** Label text or content. */\n label?: React.ReactNode\n /** Called when the chip is pressed. */\n onPressed?: () => void\n /** Value used when in a ChipGroup. */\n value?: string\n}\n\nconst ChoiceChip = React.forwardRef<HTMLButtonElement, ChoiceChipProps>(\n (\n {\n className,\n variant,\n size,\n selected = false,\n avatar,\n leadingIcon,\n label,\n children,\n onPressed,\n value,\n disabled,\n ...props\n },\n ref,\n ) => {\n const content = label ?? children\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n data-slot=\"choice-chip\"\n data-variant={variant ?? \"default\"}\n data-size={size ?? \"md\"}\n data-selected={selected ? \"true\" : \"false\"}\n data-value={value}\n disabled={disabled}\n className={cn(\n chipVariants({ variant, size }),\n styles.interactive,\n selected && styles.selected,\n className,\n )}\n onClick={onPressed}\n {...props}\n >\n {avatar ? (\n <span className={styles.avatar} aria-hidden=\"true\">\n {avatar}\n </span>\n ) : null}\n {leadingIcon ? (\n <span className={styles.leadingIcon} aria-hidden=\"true\">\n {leadingIcon}\n </span>\n ) : null}\n <span className={styles.label}>{content}</span>\n </button>\n )\n },\n)\nChoiceChip.displayName = \"ChoiceChip\"\n\n/* ─── FilterChip (multi-select toggle-style) ─────────────────────────── */\n\nexport interface FilterChipProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onClick\">,\n VariantProps<typeof chipVariants> {\n /** Whether this chip is currently active/selected. */\n selected?: boolean\n /** Optional leading icon. */\n leadingIcon?: React.ReactNode\n /** Label text or content. */\n label?: React.ReactNode\n /** Called when the chip is toggled. */\n onPressed?: () => void\n /** Value used when in a ChipGroup. */\n value?: string\n /** Optional count pill rendered after the label (e.g. \"47\", 132). */\n count?: React.ReactNode\n /** Tone for the count pill. \"neutral\" uses muted surface; \"primary\" uses accent surface. Defaults to \"neutral\". */\n countTone?: \"primary\" | \"neutral\"\n /**\n * Selected-state visual treatment.\n * - `\"accent\"` (default) — accent-tinted background + accent border + link text colour.\n * - `\"neutral\"` — neutral-elevated surface (--surface-card) + neutral border + primary text;\n * the count pill renders as a solid mint pill (--surface-success-default) when selected.\n * Use for editorial / admin contexts where accent bleed across shared tokens is undesirable.\n */\n selectedTreatment?: \"accent\" | \"neutral\"\n /** Optional trailing icon rendered after the count (e.g. a dropdown caret) for \"filter-as-dropdown\" chips. Muted by default. */\n trailingIcon?: React.ReactNode\n}\n\nconst FilterChip = React.forwardRef<HTMLButtonElement, FilterChipProps>(\n (\n {\n className,\n variant,\n size,\n selected = false,\n leadingIcon,\n label,\n children,\n onPressed,\n value,\n disabled,\n count,\n countTone = \"neutral\",\n selectedTreatment = \"accent\",\n trailingIcon,\n ...props\n },\n ref,\n ) => {\n const content = label ?? children\n const isNeutral = selectedTreatment === \"neutral\"\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"checkbox\"\n aria-checked={selected}\n data-slot=\"filter-chip\"\n data-variant={variant ?? \"default\"}\n data-size={size ?? \"md\"}\n data-selected={selected ? \"true\" : \"false\"}\n data-selected-treatment={selectedTreatment}\n data-value={value}\n disabled={disabled}\n className={cn(\n chipVariants({ variant, size }),\n styles.interactive,\n styles.filterChip,\n selected && (isNeutral ? styles.selectedNeutral : styles.selected),\n className,\n )}\n onClick={onPressed}\n {...props}\n >\n {leadingIcon ? (\n <span className={styles.leadingIcon} aria-hidden=\"true\">\n {leadingIcon}\n </span>\n ) : null}\n <span className={styles.label}>{content}</span>\n {count != null ? (\n <span\n data-slot=\"filter-chip-count\"\n data-tone={countTone}\n aria-hidden=\"false\"\n className={cn(\n styles.count,\n isNeutral && selected\n ? styles.countSolid\n : countTone === \"primary\"\n ? styles.countPrimary\n : styles.countNeutral,\n )}\n >\n {count}\n </span>\n ) : null}\n {trailingIcon ? (\n <span className={styles.trailing} aria-hidden=\"true\">\n {trailingIcon}\n </span>\n ) : null}\n </button>\n )\n },\n)\nFilterChip.displayName = \"FilterChip\"\n\nexport { Chip, chipVariants, ChoiceChip, FilterChip }\n"
|
|
447
447
|
},
|
|
448
448
|
{
|
|
449
449
|
"path": "components/ui/chip/chip.module.css",
|
|
450
450
|
"type": "registry:ui",
|
|
451
|
-
"content": "/* Chip base */\n.base {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-1, 0.25rem);\n width: fit-content;\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb);\n background-color: var(--surface-card, #ffffff);\n color: var(--text-primary, #111827);\n font-family: var(--font-body, inherit);\n font-weight: var(--font-weight-medium, 500);\n white-space: nowrap;\n user-select: none;\n transition:\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Variant: default (filled/tonal surface) */\n.variantDefault {\n background-color: var(--surface-muted, #f3f4f6);\n border-color: transparent;\n color: var(--text-primary, #111827);\n}\n\n/* Variant: outlined */\n.variantOutlined {\n background-color: transparent;\n border-color: var(--border-default, #e5e7eb);\n color: var(--text-primary, #111827);\n}\n\n/* Sizes */\n.sizeSm {\n height: 1.5rem;\n padding: 0 var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n gap: var(--spacing-1, 0.25rem);\n}\n\n.sizeMd {\n height: 2rem;\n padding: 0 var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n gap: var(--spacing-1, 0.25rem);\n}\n\n/* Editorial density: tighter font + gap for md chips. */\n:global([data-density=\"editorial\"]) .sizeMd {\n font-size: var(--font-size-xs);\n gap: var(--spacing-2);\n}\n\n.sizeLg {\n height: 2.5rem;\n padding: 0 var(--spacing-4, 1rem);\n font-size: var(--font-size-base, 1rem);\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Sub-parts */\n.avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.leadingIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.deleteButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n background: transparent;\n border: none;\n padding: 0;\n cursor: pointer;\n color: var(--text-secondary, #6b7280);\n border-radius: var(--radius-full, 9999px);\n transition: color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n width: 1rem;\n height: 1rem;\n}\n\n@media (hover: hover) {\n .deleteButton:hover {\n color: var(--text-primary, #111827);\n background-color: color-mix(in srgb, var(--surface-interactive-hover, #f3f4f6) 60%, transparent);\n }\n}\n\n.deleteButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, #6b7280);\n outline-offset: var(--focus-ring-offset, 1px);\n}\n\n.deleteIcon {\n display: block;\n}\n\n/* Count pill (FilterChip only). Under data-density=\"editorial\", it renders\n as a fixed circular semibold pill. */\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n min-width: auto;\n height: auto;\n border-radius: var(--radius-full, 9999px);\n padding: 0 var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n font-variant-numeric: tabular-nums;\n line-height: 1;\n align-self: baseline;\n}\n\n:global([data-density=\"editorial\"]) .count {\n min-width: 1.25rem;\n height: 1.25rem;\n padding-inline: var(--spacing-1);\n font-weight: var(--font-weight-semibold);\n align-self: center;\n}\n\n.countNeutral {\n background-color: var(--surface-muted, #f3f4f6);\n color: var(--text-primary, #111827);\n}\n\n.countPrimary {\n background-color: var(--surface-accent-subtle, #eff6ff);\n color: var(--text-link, #2563eb);\n}\n\n/* Accent selected state overrides count pill (default behaviour). */\n.selected .count {\n background-color: var(--surface-card, #ffffff);\n color: var(--text-link, #2563eb);\n}\n\n/* Editorial density: selected count uses solid primary pill. */\n:global([data-density=\"editorial\"]) .selected .count {\n background-color: var(--primary);\n color: var(--primary-text);\n}\n\n/* Solid mint count pill — editorial/neutral selected treatment */\n.countSolid {\n background-color: var(--surface-success-default, #22c55e);\n color: var(--text-inverse, #ffffff);\n}\n\n/* Trailing icon (FilterChip dropdown caret) — muted, after the count. */\n.trailing {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: var(--text-tertiary, #9ca3af);\n font-size: 0.875rem;\n line-height: 1;\n}\n\n/* Interactive chips (ChoiceChip, FilterChip) */\n.interactive {\n cursor: pointer;\n border: var(--stroke-width-thin, 1px) solid transparent;\n}\n\nbutton.base,\n.interactive {\n /* Inherit base layout; add button-specific resets */\n text-align: left;\n appearance: none;\n -webkit-appearance: none;\n}\n\n.interactive:disabled {\n cursor: not-allowed;\n opacity: var(--opacity-40, 0.4);\n pointer-events: none;\n}\n\n.interactive:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, #6b7280);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n@media (hover: hover) {\n .interactive:hover:not(:disabled):not([data-selected=\"true\"]) {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n border-color: var(--border-default, #e5e7eb);\n }\n}\n\n.interactive:active:not(:disabled) {\n transform: translateY(1px);\n transition: none;\n}\n\n/* Selected state. Under data-density=\"editorial\", switches to neutral-elevated\n treatment (surface-elev bg + transparent border + primary text). */\n.selected {\n background-color: var(--surface-accent-subtle, #eff6ff);\n border-color: var(--surface-accent-default, #3b82f6);\n color: var(--text-link, #2563eb);\n}\n\n/* Editorial density: neutral-elevated selected treatment.\n --surface-elev is a theme role; the color-mix fallback matches how the\n retired overlay derived it. */\n:global([data-density=\"editorial\"]) .selected {\n background: var(--surface-elev, color-mix(in srgb, var(--surface-card), var(--surface-muted, var(--surface-card))));\n border-color: transparent;\n color: var(--text-primary);\n}\n\n@media (hover: hover) {\n .interactive.selected:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--surface-accent-subtle, #eff6ff) 80%, var(--surface-interactive-hover, #f3f4f6) 20%);\n }\n}\n\n/* Editorial / neutral-elevated selected treatment\n * Uses --surface-card bg + neutral border instead of accent tint.\n * Compose with .selected to override the accent colours. */\n.selectedNeutral {\n background-color: var(--surface-card, #ffffff);\n border-color: var(--border-strong, #d1d5db);\n color: var(--text-primary, #111827);\n}\n\n/* Re-assert selectedNeutral under editorial density: the density .selected rule\n * is (0,2,0) and would otherwise override .selectedNeutral at (0,1,0). */\n:global([data-density=\"editorial\"]) .selectedNeutral {\n background-color: var(--surface-card, #ffffff);\n border-color: var(--border-strong, #d1d5db);\n color: var(--text-primary, #111827);\n}\n\n.selectedNeutral .count {\n background-color: var(--surface-success-default, #22c55e);\n color: var(--text-inverse, #ffffff);\n}\n\n/* Re-assert selectedNeutral count under editorial density: the density\n * .selected .count rule at (0,3,0) would otherwise override at (0,2,0). */\n:global([data-density=\"editorial\"]) .selectedNeutral .count {\n background-color: var(--surface-success-default, #22c55e);\n color: var(--text-inverse, #ffffff);\n}\n\n@media (hover: hover) {\n .interactive.selectedNeutral:hover:not(:disabled) {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n }\n}\n\n/* FilterChip resting (unselected) surface. Under editorial density, uses\n --surface-elev so the filter row reads as one consistent elevated set. */\n.filterChip[data-selected=\"false\"] {\n background-color: var(--surface-muted, #f3f4f6);\n}\n\n:global([data-density=\"editorial\"]) .filterChip[data-selected=\"false\"] {\n background-color: var(--surface-elev, color-mix(in srgb, var(--surface-card), var(--surface-muted, var(--surface-card))));\n}\n"
|
|
451
|
+
"content": "/* Chip base */\n.base {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-1, 0.25rem);\n width: fit-content;\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb);\n background-color: var(--surface-card, #ffffff);\n color: var(--text-primary, #111827);\n font-family: var(--font-body, inherit);\n font-weight: var(--font-weight-medium, 500);\n white-space: nowrap;\n user-select: none;\n transition:\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Variant: default (filled/tonal surface) */\n.variantDefault {\n background-color: var(--surface-muted, #f3f4f6);\n border-color: transparent;\n color: var(--text-primary, #111827);\n}\n\n/* Variant: outlined */\n.variantOutlined {\n background-color: transparent;\n border-color: var(--border-default, #e5e7eb);\n color: var(--text-primary, #111827);\n}\n\n/* Variant: filled-primary — saturated brand-essence chip treatment.\n * Primary background + primary-text foreground + subtle shadow.\n * 700 weight + slight negative tracking assert \"this IS a brand value\".\n * Use when the chip must carry primary brand prominence — essence chips\n * on the Brand Workbench Elicit canvas, strategy surfaces, brand decks. */\n.variantFilledPrimary {\n background-color: var(--primary, #111827);\n border-color: transparent;\n color: var(--primary-text, #f9fafb);\n font-weight: 700;\n letter-spacing: -0.01em;\n box-shadow: var(--shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.05));\n}\n\n/* Sizes */\n.sizeSm {\n height: 1.5rem;\n padding: 0 var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n gap: var(--spacing-1, 0.25rem);\n}\n\n.sizeMd {\n height: 2rem;\n padding: 0 var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n gap: var(--spacing-1, 0.25rem);\n}\n\n/* Editorial density: tighter font + gap for md chips. */\n:global([data-density=\"editorial\"]) .sizeMd {\n font-size: var(--font-size-xs);\n gap: var(--spacing-2);\n}\n\n.sizeLg {\n height: 2.5rem;\n padding: 0 var(--spacing-4, 1rem);\n font-size: var(--font-size-base, 1rem);\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Sub-parts */\n.avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.leadingIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.deleteButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n background: transparent;\n border: none;\n padding: 0;\n cursor: pointer;\n color: var(--text-secondary, #6b7280);\n border-radius: var(--radius-full, 9999px);\n transition: color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n width: 1rem;\n height: 1rem;\n}\n\n@media (hover: hover) {\n .deleteButton:hover {\n color: var(--text-primary, #111827);\n background-color: color-mix(in srgb, var(--surface-interactive-hover, #f3f4f6) 60%, transparent);\n }\n}\n\n.deleteButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, #6b7280);\n outline-offset: var(--focus-ring-offset, 1px);\n}\n\n.deleteIcon {\n display: block;\n}\n\n/* Count pill (FilterChip only). Under data-density=\"editorial\", it renders\n as a fixed circular semibold pill. */\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n min-width: auto;\n height: auto;\n border-radius: var(--radius-full, 9999px);\n padding: 0 var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n font-variant-numeric: tabular-nums;\n line-height: 1;\n align-self: baseline;\n}\n\n:global([data-density=\"editorial\"]) .count {\n min-width: 1.25rem;\n height: 1.25rem;\n padding-inline: var(--spacing-1);\n font-weight: var(--font-weight-semibold);\n align-self: center;\n}\n\n.countNeutral {\n background-color: var(--surface-muted, #f3f4f6);\n color: var(--text-primary, #111827);\n}\n\n.countPrimary {\n background-color: var(--surface-accent-subtle, #eff6ff);\n color: var(--text-link, #2563eb);\n}\n\n/* Accent selected state overrides count pill (default behaviour). */\n.selected .count {\n background-color: var(--surface-card, #ffffff);\n color: var(--text-link, #2563eb);\n}\n\n/* Editorial density: selected count uses solid primary pill. */\n:global([data-density=\"editorial\"]) .selected .count {\n background-color: var(--primary);\n color: var(--primary-text);\n}\n\n/* Solid mint count pill — editorial/neutral selected treatment */\n.countSolid {\n background-color: var(--surface-success-default, #22c55e);\n color: var(--text-inverse, #ffffff);\n}\n\n/* Trailing icon (FilterChip dropdown caret) — muted, after the count. */\n.trailing {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: var(--text-tertiary, #9ca3af);\n font-size: 0.875rem;\n line-height: 1;\n}\n\n/* Interactive chips (ChoiceChip, FilterChip) */\n.interactive {\n cursor: pointer;\n border: var(--stroke-width-thin, 1px) solid transparent;\n}\n\nbutton.base,\n.interactive {\n /* Inherit base layout; add button-specific resets */\n text-align: left;\n appearance: none;\n -webkit-appearance: none;\n}\n\n.interactive:disabled {\n cursor: not-allowed;\n opacity: var(--opacity-40, 0.4);\n pointer-events: none;\n}\n\n.interactive:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, #6b7280);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n@media (hover: hover) {\n .interactive:hover:not(:disabled):not([data-selected=\"true\"]) {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n border-color: var(--border-default, #e5e7eb);\n }\n}\n\n.interactive:active:not(:disabled) {\n transform: translateY(1px);\n transition: none;\n}\n\n/* Selected state. Under data-density=\"editorial\", switches to neutral-elevated\n treatment (surface-elev bg + transparent border + primary text). */\n.selected {\n background-color: var(--surface-accent-subtle, #eff6ff);\n border-color: var(--surface-accent-default, #3b82f6);\n color: var(--text-link, #2563eb);\n}\n\n/* Editorial density: neutral-elevated selected treatment.\n --surface-elev is a theme role; the color-mix fallback matches how the\n retired overlay derived it. */\n:global([data-density=\"editorial\"]) .selected {\n background: var(--surface-elev, color-mix(in srgb, var(--surface-card), var(--surface-muted, var(--surface-card))));\n border-color: transparent;\n color: var(--text-primary);\n}\n\n@media (hover: hover) {\n .interactive.selected:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--surface-accent-subtle, #eff6ff) 80%, var(--surface-interactive-hover, #f3f4f6) 20%);\n }\n}\n\n/* Editorial / neutral-elevated selected treatment\n * Uses --surface-card bg + neutral border instead of accent tint.\n * Compose with .selected to override the accent colours. */\n.selectedNeutral {\n background-color: var(--surface-card, #ffffff);\n border-color: var(--border-strong, #d1d5db);\n color: var(--text-primary, #111827);\n}\n\n/* Re-assert selectedNeutral under editorial density: the density .selected rule\n * is (0,2,0) and would otherwise override .selectedNeutral at (0,1,0). */\n:global([data-density=\"editorial\"]) .selectedNeutral {\n background-color: var(--surface-card, #ffffff);\n border-color: var(--border-strong, #d1d5db);\n color: var(--text-primary, #111827);\n}\n\n.selectedNeutral .count {\n background-color: var(--surface-success-default, #22c55e);\n color: var(--text-inverse, #ffffff);\n}\n\n/* Re-assert selectedNeutral count under editorial density: the density\n * .selected .count rule at (0,3,0) would otherwise override at (0,2,0). */\n:global([data-density=\"editorial\"]) .selectedNeutral .count {\n background-color: var(--surface-success-default, #22c55e);\n color: var(--text-inverse, #ffffff);\n}\n\n@media (hover: hover) {\n .interactive.selectedNeutral:hover:not(:disabled) {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n }\n}\n\n/* FilterChip resting (unselected) surface. Under editorial density, uses\n --surface-elev so the filter row reads as one consistent elevated set. */\n.filterChip[data-selected=\"false\"] {\n background-color: var(--surface-muted, #f3f4f6);\n}\n\n:global([data-density=\"editorial\"]) .filterChip[data-selected=\"false\"] {\n background-color: var(--surface-elev, color-mix(in srgb, var(--surface-card), var(--surface-muted, var(--surface-card))));\n}\n"
|
|
452
452
|
}
|
|
453
453
|
]
|
|
454
454
|
},
|
|
@@ -468,7 +468,7 @@
|
|
|
468
468
|
{
|
|
469
469
|
"path": "components/ui/avatar/avatar.tsx",
|
|
470
470
|
"type": "registry:ui",
|
|
471
|
-
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./avatar.module.css\"\nimport stackStyles from \"./avatar-stack.module.css\"\n\nexport interface AvatarProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> {\n size?: \"sm\" | \"default\" | \"lg\"\n}\n\nconst Avatar = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Root>,\n AvatarProps\n>(({ className, size = \"default\", ...props }, ref) => {\n return (\n <AvatarPrimitive.Root\n ref={ref}\n data-slot=\"avatar\"\n data-size={size}\n className={cn(\n styles.avatar,\n size === \"sm\" && styles.avatarSm,\n size === \"lg\" && styles.avatarLg,\n className\n )}\n {...props}\n />\n )\n})\nAvatar.displayName = \"Avatar\"\n\nconst AvatarImage = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Image>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => {\n return (\n <AvatarPrimitive.Image\n ref={ref}\n data-slot=\"avatar-image\"\n className={cn(styles.avatarImage, className)}\n {...props}\n />\n )\n})\nAvatarImage.displayName = \"AvatarImage\"\n\nconst AvatarFallback = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Fallback>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => {\n return (\n <AvatarPrimitive.Fallback\n ref={ref}\n data-slot=\"avatar-fallback\"\n className={cn(styles.avatarFallback, className)}\n {...props}\n />\n )\n})\nAvatarFallback.displayName = \"AvatarFallback\"\n\nexport interface AvatarStackProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"role\" | \"aria-label\"> {\n /**\n *
|
|
471
|
+
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./avatar.module.css\"\nimport stackStyles from \"./avatar-stack.module.css\"\n\n// ─── AvatarStack item type ───────────────────────────────────────────────────\n\n/**\n * Rich item form for AvatarStack. A plain string or `undefined` is also\n * accepted (backward-compatible shorthand for an image-src-only item).\n */\nexport interface AvatarStackItem {\n /** Initials rendered as fallback (and primary) content — e.g. \"AR\". */\n initials?: React.ReactNode\n /** Optional image source. When present, the image covers the disc. */\n src?: string\n /** Accessible label for the disc — e.g. the member name. */\n alt?: string\n /**\n * Per-avatar style escape hatch — carries the gradient `background` + text\n * `color` for the editorial gradient discs (see `getMemberAvatarStyle`).\n */\n style?: React.CSSProperties\n}\n\nexport interface AvatarProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> {\n size?: \"sm\" | \"default\" | \"lg\"\n}\n\nconst Avatar = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Root>,\n AvatarProps\n>(({ className, size = \"default\", ...props }, ref) => {\n return (\n <AvatarPrimitive.Root\n ref={ref}\n data-slot=\"avatar\"\n data-size={size}\n className={cn(\n styles.avatar,\n size === \"sm\" && styles.avatarSm,\n size === \"lg\" && styles.avatarLg,\n className\n )}\n {...props}\n />\n )\n})\nAvatar.displayName = \"Avatar\"\n\nconst AvatarImage = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Image>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => {\n return (\n <AvatarPrimitive.Image\n ref={ref}\n data-slot=\"avatar-image\"\n className={cn(styles.avatarImage, className)}\n {...props}\n />\n )\n})\nAvatarImage.displayName = \"AvatarImage\"\n\nconst AvatarFallback = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Fallback>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => {\n return (\n <AvatarPrimitive.Fallback\n ref={ref}\n data-slot=\"avatar-fallback\"\n className={cn(styles.avatarFallback, className)}\n {...props}\n />\n )\n})\nAvatarFallback.displayName = \"AvatarFallback\"\n\nexport interface AvatarStackProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"role\" | \"aria-label\"> {\n /**\n * Avatars to render, in display order. Each entry is either:\n * - A plain image URL string (backward-compatible shorthand)\n * - `undefined` — renders the `·` fallback disc, useful for server-truncated lists\n * - An `AvatarStackItem` object with `initials`, `src`, `alt`, and/or `style`\n */\n avatars: (string | undefined | AvatarStackItem)[]\n /**\n * Total member count. May exceed `avatars.length` when the caller has\n * server-truncated the avatar URLs and only knows the count. The overflow\n * indicator is computed against this value.\n */\n total: number\n /**\n * Maximum number of avatar slots rendered before the `+N` overflow\n * indicator. Defaults to `6`.\n */\n max?: number\n /**\n * Explicit \"+N\" override. When provided, this value is used verbatim\n * instead of the value derived from `total - visible.length`. Useful\n * when the caller has a pre-computed overflow count.\n */\n overflowCount?: number\n /** Avatar size. Defaults to `\"sm\"`. */\n size?: \"sm\" | \"default\" | \"lg\"\n /**\n * Accessible label override. Defaults to ``${total} members``.\n */\n label?: string\n}\n\n/** Normalize a raw avatar entry to an `AvatarStackItem`. */\nfunction toItem(entry: string | undefined | AvatarStackItem): AvatarStackItem {\n if (entry === undefined || entry === null) return {}\n if (typeof entry === \"string\") return { src: entry, alt: \"\" }\n return entry\n}\n\nconst AvatarStack = React.forwardRef<HTMLDivElement, AvatarStackProps>(\n function AvatarStack(\n {\n avatars,\n total,\n max = 6,\n overflowCount,\n size = \"sm\",\n label,\n className,\n ...rest\n },\n ref,\n ) {\n const visible = avatars.slice(0, max)\n const derivedOverflow = Math.max(0, total - visible.length)\n const overflow = overflowCount ?? derivedOverflow\n const ariaLabel = label ?? `${total} members`\n\n return (\n <div\n ref={ref}\n role=\"img\"\n aria-label={ariaLabel}\n data-slot=\"avatar-stack\"\n data-size={size}\n className={cn(stackStyles.root, className)}\n {...rest}\n >\n {visible.map((entry, index) => {\n const item = toItem(entry)\n return (\n <Avatar\n key={index}\n size={size}\n className={stackStyles.avatar}\n style={item.style}\n data-stack-item=\"\"\n >\n {item.src ? (\n <AvatarImage src={item.src} alt={item.alt ?? \"\"} />\n ) : item.initials != null ? (\n <AvatarFallback className={stackStyles.initialsDisc}>\n {item.initials}\n </AvatarFallback>\n ) : (\n <AvatarFallback>·</AvatarFallback>\n )}\n </Avatar>\n )\n })}\n {overflow > 0 ? (\n <Avatar\n size={size}\n className={stackStyles.avatar}\n data-stack-overflow=\"\"\n >\n <AvatarFallback>+{overflow}</AvatarFallback>\n </Avatar>\n ) : null}\n </div>\n )\n },\n)\n\nAvatarStack.displayName = \"AvatarStack\"\n\nexport { Avatar, AvatarImage, AvatarFallback, AvatarStack }\n"
|
|
472
472
|
},
|
|
473
473
|
{
|
|
474
474
|
"path": "components/ui/avatar/avatar.module.css",
|
|
@@ -478,7 +478,7 @@
|
|
|
478
478
|
{
|
|
479
479
|
"path": "components/ui/avatar/avatar-stack.module.css",
|
|
480
480
|
"type": "registry:ui",
|
|
481
|
-
"content": "/* AvatarStack root */\n.root {\n display: inline-flex;\n flex-direction: row;\n align-items: center;\n isolation: isolate;\n}\n\n/* Each avatar gets a ring matching the parent surface so adjacent avatars\n read as separate circles. Avatar's own `overflow: hidden` clips inner\n shadows, so the ring is projected outward via box-shadow. */\n.avatar {\n box-shadow: 0 0 0 var(--stroke-width-medium, 2px) var(--surface-default, #ffffff);\n}\n\n/* Overlap every avatar except the first. Later siblings stack on top of\n earlier ones because `isolation: isolate` establishes a single stacking\n context and DOM order wins inside it. */\n.avatar:not(:first-child) {\n margin-inline-start: calc(-1 * var(--spacing-2, 0.5rem));\n}\n"
|
|
481
|
+
"content": "/* AvatarStack root */\n.root {\n display: inline-flex;\n flex-direction: row;\n align-items: center;\n isolation: isolate;\n}\n\n/* Each avatar gets a ring matching the parent surface so adjacent avatars\n read as separate circles. Avatar's own `overflow: hidden` clips inner\n shadows, so the ring is projected outward via box-shadow. */\n.avatar {\n box-shadow: 0 0 0 var(--stroke-width-medium, 2px) var(--surface-default, #ffffff);\n}\n\n/* Overlap every avatar except the first. Later siblings stack on top of\n earlier ones because `isolation: isolate` establishes a single stacking\n context and DOM order wins inside it. */\n.avatar:not(:first-child) {\n margin-inline-start: calc(-1 * var(--spacing-2, 0.5rem));\n}\n\n/* When an AvatarStackItem carries a per-item `style` with gradient\n background + color, the fallback must be transparent so the gradient\n on the parent Avatar shows through. Only applied to the initials\n fallback inside a stack disc — plain Avatar usage is unaffected. */\n.initialsDisc {\n background-color: transparent;\n color: inherit;\n}\n"
|
|
482
482
|
}
|
|
483
483
|
]
|
|
484
484
|
},
|
|
@@ -1024,6 +1024,32 @@
|
|
|
1024
1024
|
}
|
|
1025
1025
|
]
|
|
1026
1026
|
},
|
|
1027
|
+
{
|
|
1028
|
+
"name": "toast-card",
|
|
1029
|
+
"type": "registry:ui",
|
|
1030
|
+
"description": "A static, server-renderable notification card for editorial display of toast anatomy. Use in state galleries — not for imperative portal notifications.",
|
|
1031
|
+
"category": "feedback",
|
|
1032
|
+
"dependencies": [
|
|
1033
|
+
"class-variance-authority",
|
|
1034
|
+
"@phosphor-icons/react",
|
|
1035
|
+
"@loworbitstudio/visor-core"
|
|
1036
|
+
],
|
|
1037
|
+
"registryDependencies": [
|
|
1038
|
+
"utils"
|
|
1039
|
+
],
|
|
1040
|
+
"files": [
|
|
1041
|
+
{
|
|
1042
|
+
"path": "components/ui/toast-card/toast-card.tsx",
|
|
1043
|
+
"type": "registry:ui",
|
|
1044
|
+
"content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport {\n CheckCircle,\n WarningCircle,\n Info,\n Warning,\n X,\n} from \"@phosphor-icons/react/dist/ssr\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./toast-card.module.css\"\n\n/* Variant → filled icon-badge color. A circular FILLED badge whose\n background is the semantic token and whose glyph reads in --primary-text.\n The whole card is declarative + server-renderable (no Sonner), so a fixed\n stack of these can be statically composed (see ToastCardStack). */\nconst toastCardVariants = cva(styles.base, {\n variants: {\n variant: {\n success: styles.variantSuccess,\n error: styles.variantError,\n info: styles.variantInfo,\n warning: styles.variantWarning,\n },\n },\n defaultVariants: {\n variant: \"info\",\n },\n})\n\ntype ToastCardVariant = NonNullable<VariantProps<typeof toastCardVariants>[\"variant\"]>\n\n/* Default glyph per variant — matches the golden success/error/info stack.\n Overridable via the `icon` prop for bespoke notifications. */\nconst variantIcon: Record<ToastCardVariant, React.ElementType> = {\n success: CheckCircle,\n error: WarningCircle,\n info: Info,\n warning: Warning,\n}\n\nexport interface ToastCardProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\">,\n VariantProps<typeof toastCardVariants> {\n /** Bold one-line headline. Required. */\n title: React.ReactNode\n /** Muted supporting line beneath the title. */\n body?: React.ReactNode\n /** Optional primary-colored action affordance (link/button text). */\n action?: React.ReactNode\n /** Handler for the action affordance. When omitted with `action`, the\n * action renders as static text (server-safe). */\n onAction?: React.MouseEventHandler<HTMLButtonElement>\n /** Dismiss handler. When provided, renders the muted X control. */\n onDismiss?: React.MouseEventHandler<HTMLButtonElement>\n /** Override the leading glyph. Defaults to the per-variant icon. */\n icon?: React.ReactNode\n}\n\nconst ToastCard = React.forwardRef<HTMLDivElement, ToastCardProps>(\n (\n {\n className,\n variant,\n title,\n body,\n action,\n onAction,\n onDismiss,\n icon,\n ...props\n },\n ref\n ) => {\n const resolvedVariant: ToastCardVariant = variant ?? \"info\"\n const Glyph = variantIcon[resolvedVariant]\n\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-live=\"polite\"\n data-slot=\"toast-card\"\n data-variant={resolvedVariant}\n className={cn(toastCardVariants({ variant }), className)}\n {...props}\n >\n <span\n data-slot=\"toast-card-icon\"\n className={styles.icon}\n aria-hidden=\"true\"\n >\n {icon ?? <Glyph weight=\"fill\" />}\n </span>\n <div data-slot=\"toast-card-content\" className={styles.content}>\n <div data-slot=\"toast-card-title\" className={styles.title}>\n {title}\n </div>\n {body ? (\n <div data-slot=\"toast-card-body\" className={styles.body}>\n {body}\n </div>\n ) : null}\n {action ? (\n <button\n type=\"button\"\n data-slot=\"toast-card-action\"\n className={styles.action}\n onClick={onAction}\n >\n {action}\n </button>\n ) : null}\n </div>\n {onDismiss ? (\n <button\n type=\"button\"\n data-slot=\"toast-card-close\"\n className={styles.close}\n aria-label=\"Dismiss notification\"\n onClick={onDismiss}\n >\n <X weight=\"bold\" />\n </button>\n ) : null}\n </div>\n )\n }\n)\nToastCard.displayName = \"ToastCard\"\n\nexport interface ToastCardStackProps\n extends React.HTMLAttributes<HTMLDivElement> {\n /** Distance from the top of the viewport. Defaults to --spacing-6. */\n top?: string\n /** Vertical gap between stacked cards. Defaults to --spacing-3. */\n gap?: string\n}\n\n/* Fixed top-right column for a vertical stack of ToastCards. Declarative +\n server-renderable: the Feedback screen pins three of these without any\n client toast runtime. Offsets are CSS-var driven so consumers can retune\n without forking the module. */\nconst ToastCardStack = React.forwardRef<HTMLDivElement, ToastCardStackProps>(\n ({ className, top, gap, style, children, ...props }, ref) => {\n const stackStyle = {\n ...(top ? { [\"--toast-card-stack-top\" as string]: top } : {}),\n ...(gap ? { [\"--toast-card-stack-gap\" as string]: gap } : {}),\n ...style,\n } as React.CSSProperties\n\n return (\n <div\n ref={ref}\n data-slot=\"toast-card-stack\"\n className={cn(styles.stack, className)}\n style={stackStyle}\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nToastCardStack.displayName = \"ToastCardStack\"\n\nexport { ToastCard, ToastCardStack, toastCardVariants }\n"
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
"path": "components/ui/toast-card/toast-card.module.css",
|
|
1048
|
+
"type": "registry:ui",
|
|
1049
|
+
"content": "/* ToastCard — a declarative (non-Sonner) notification card that can be\n * server-rendered in a fixed stack. Theme-agnostic: every value is a\n * design-language token with a safe fallback, so the card looks right on\n * all themes without any hardcoded brand color.\n *\n * Note: use ToastCard for static editorial rendering of notification anatomy\n * (state galleries, docs). For imperative portal notifications use Toast. */\n\n/* Card surface — elevated card on a soft shadow, leading icon + content +\n optional close, aligned to the top so multi-line bodies read cleanly. */\n.base {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-3, 0.75rem);\n min-width: 320px;\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n background-color: var(--surface-elev, var(--surface-card, #ffffff));\n border-radius: var(--radius-md, 0.5rem);\n box-shadow: var(--shadow-lg);\n color: var(--text-primary, #111827);\n}\n\n/* Circular FILLED icon badge — fixed square collapses to a perfect circle.\n Background is set per-variant; the glyph inherits currentColor. */\n.icon {\n flex-shrink: 0;\n width: var(--toast-card-icon-size, 1.5rem);\n height: var(--toast-card-icon-size, 1.5rem);\n margin-top: calc(var(--spacing-1, 0.25rem) / 4);\n border-radius: var(--radius-full, 9999px);\n display: grid;\n place-items: center;\n}\n\n.icon svg {\n width: var(--toast-card-icon-glyph, 0.875rem);\n height: var(--toast-card-icon-glyph, 0.875rem);\n}\n\n/* Variants — filled badge color + glyph color. */\n.variantSuccess .icon {\n background-color: var(--success, #22c55e);\n color: var(--primary-text, var(--text-inverse, #ffffff));\n}\n\n.variantError .icon {\n background-color: var(--destructive, #ef4444);\n color: var(--color-white, #ffffff);\n}\n\n.variantInfo .icon {\n background-color: var(--accent, #0ea5e9);\n color: var(--primary-text, var(--text-inverse, #ffffff));\n}\n\n.variantWarning .icon {\n background-color: var(--warning, #f59e0b);\n color: var(--primary-text, var(--text-inverse, #ffffff));\n}\n\n/* Content column — title, optional muted body, optional action. */\n.content {\n flex: 1;\n min-width: 0;\n}\n\n.title {\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-semibold, 600);\n line-height: 1.3;\n color: var(--text-primary, #111827);\n}\n\n.body {\n margin-top: calc(var(--spacing-1, 0.25rem) / 2);\n font-size: var(--toast-card-body-font-size, 0.8125rem);\n line-height: 1.4;\n color: var(--text-tertiary, #6b7280);\n}\n\n/* Primary-colored action affordance — borderless text button that reads as a\n link. Server-safe: renders as static styled text when no handler is wired. */\n.action {\n display: inline-block;\n margin-top: calc(var(--spacing-1, 0.25rem) * 1.5);\n padding: 0;\n border: none;\n background: transparent;\n font-size: var(--toast-card-body-font-size, 0.8125rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--primary, currentColor);\n cursor: pointer;\n}\n\n.action:hover {\n text-decoration: underline;\n}\n\n.action:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n/* Muted dismiss control — transparent square, darkens on hover. */\n.close {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: calc(var(--spacing-1, 0.25rem) / 2);\n border: none;\n background: transparent;\n color: var(--text-muted, var(--text-tertiary, #9ca3af));\n cursor: pointer;\n transition: color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.close:hover {\n color: var(--text-primary, #111827);\n}\n\n.close:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n.close svg {\n width: 1rem;\n height: 1rem;\n}\n\n/* Fixed top-right column for a vertical stack of cards. Top offset + gap are\n CSS-var driven (set via ToastCardStack props) so consumers retune without\n forking. Defaults match the overlay toast-region pattern. */\n.stack {\n position: fixed;\n top: var(--toast-card-stack-top, var(--spacing-6, 1.5rem));\n right: var(--toast-card-stack-right, var(--spacing-6, 1.5rem));\n z-index: var(--toast-card-stack-z, 60);\n display: flex;\n flex-direction: column;\n gap: var(--toast-card-stack-gap, var(--spacing-3, 0.75rem));\n max-width: 380px;\n}\n"
|
|
1050
|
+
}
|
|
1051
|
+
]
|
|
1052
|
+
},
|
|
1027
1053
|
{
|
|
1028
1054
|
"name": "table",
|
|
1029
1055
|
"type": "registry:ui",
|
|
@@ -2393,6 +2419,30 @@
|
|
|
2393
2419
|
}
|
|
2394
2420
|
]
|
|
2395
2421
|
},
|
|
2422
|
+
{
|
|
2423
|
+
"name": "error-placard",
|
|
2424
|
+
"type": "registry:ui",
|
|
2425
|
+
"description": "Inline failed-load placard with a destructive-tinted icon chip, title, body, and optional right-aligned recovery actions. Distinct from Alert (passive semantic callout) and Banner (full-width page bar).",
|
|
2426
|
+
"category": "feedback",
|
|
2427
|
+
"dependencies": [
|
|
2428
|
+
"@loworbitstudio/visor-core"
|
|
2429
|
+
],
|
|
2430
|
+
"registryDependencies": [
|
|
2431
|
+
"utils"
|
|
2432
|
+
],
|
|
2433
|
+
"files": [
|
|
2434
|
+
{
|
|
2435
|
+
"path": "components/ui/error-placard/error-placard.tsx",
|
|
2436
|
+
"type": "registry:ui",
|
|
2437
|
+
"content": "import * as React from \"react\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./error-placard.module.css\"\n\nexport interface ErrorPlacardProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Leading visual — typically a Phosphor icon. Rendered in a destructive-tinted circular chip. */\n icon: React.ReactNode\n /** Short direct statement of the failure. Required. */\n title: React.ReactNode\n /** 1-2 sentence explanation or recovery guidance. Required. */\n body: React.ReactNode\n /** Optional right-aligned action cluster — typically one or more Buttons. */\n actions?: React.ReactNode\n}\n\n/**\n * ErrorPlacard — an inline failed-load placard.\n *\n * A destructive-tinted horizontal card: a circular destructive icon chip on the\n * left, title + body in the middle, and an optional right-aligned action cluster.\n * The surface is a destructive color-mix on the card with an inset destructive\n * ring. Theme-agnostic — reads entirely from design-language tokens.\n */\nconst ErrorPlacard = React.forwardRef<HTMLDivElement, ErrorPlacardProps>(\n ({ className, icon, title, body, actions, ...props }, ref) => {\n return (\n <div\n ref={ref}\n role=\"alert\"\n data-slot=\"error-placard\"\n className={cn(styles.base, className)}\n {...props}\n >\n <div\n data-slot=\"error-placard-icon\"\n className={styles.icon}\n aria-hidden=\"true\"\n >\n {icon}\n </div>\n\n <div data-slot=\"error-placard-body\" className={styles.content}>\n <p data-slot=\"error-placard-title\" className={styles.title}>\n {title}\n </p>\n <p data-slot=\"error-placard-message\" className={styles.message}>\n {body}\n </p>\n </div>\n\n {actions ? (\n <div\n data-slot=\"error-placard-actions\"\n className={styles.actions}\n >\n {actions}\n </div>\n ) : null}\n </div>\n )\n }\n)\nErrorPlacard.displayName = \"ErrorPlacard\"\n\nexport { ErrorPlacard }\n"
|
|
2438
|
+
},
|
|
2439
|
+
{
|
|
2440
|
+
"path": "components/ui/error-placard/error-placard.module.css",
|
|
2441
|
+
"type": "registry:ui",
|
|
2442
|
+
"content": "/* ErrorPlacard — inline failed-load placard.\n Destructive-tinted horizontal card: circular icon chip, title + body, optional\n right-aligned action cluster. Theme-agnostic — all values from design tokens. */\n.base {\n display: flex;\n align-items: center;\n gap: var(--spacing-5);\n padding: var(--spacing-8);\n border-radius: var(--radius-lg);\n /* Destructive color-mix on the card surface */\n background: color-mix(in srgb, var(--destructive) 8%, var(--surface-card));\n /* Inset destructive ring */\n box-shadow: inset 0 0 0 1px\n color-mix(in srgb, var(--destructive) 30%, transparent);\n}\n\n/* Circular destructive icon chip on the left */\n.icon {\n flex-shrink: 0;\n display: grid;\n place-items: center;\n width: 44px;\n height: 44px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--destructive) 20%, transparent);\n color: var(--destructive);\n font-size: 22px;\n line-height: 1;\n}\n\n/* Title + body column in the middle */\n.content {\n min-width: 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--font-size-base);\n font-weight: 600;\n}\n\n.message {\n margin: calc(var(--spacing-1, 0.25rem) / 2) 0 0;\n font-size: 13px;\n color: var(--text-tertiary);\n}\n\n/* Right-aligned action cluster slot */\n.actions {\n margin-left: auto;\n display: flex;\n gap: var(--spacing-2);\n}\n"
|
|
2443
|
+
}
|
|
2444
|
+
]
|
|
2445
|
+
},
|
|
2396
2446
|
{
|
|
2397
2447
|
"name": "filter-bar",
|
|
2398
2448
|
"type": "registry:ui",
|
package/dist/visor-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.4.0",
|
|
3
|
-
"generated_at": "2026-06-
|
|
3
|
+
"generated_at": "2026-06-15T22:40:15.938Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"accessibility-specimen": {
|
|
6
6
|
"category": "specimen",
|
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
},
|
|
312
312
|
{
|
|
313
313
|
"name": "AvatarStack",
|
|
314
|
-
"description": "Compound primitive for overlapping avatar groups with a `+N` overflow indicator.
|
|
314
|
+
"description": "Compound primitive for overlapping avatar groups with a `+N` overflow indicator. Each entry in `avatars` is a plain image-URL string, `undefined` (renders a placeholder disc), or an `AvatarStackItem` object with `initials`, `src`, `alt`, and an optional `style` escape hatch for per-disc gradient backgrounds. Accepts a `total` count (supports server-truncated lists), a `max` cap (default 6), an explicit `overflowCount` override, a `size` pass-through (`\"sm\" | \"default\" | \"lg\"`, default `\"sm\"`), and an optional accessible `label` override. Renders `role=\"img\"` with `data-slot=\"avatar-stack\"`.\n"
|
|
315
315
|
}
|
|
316
316
|
],
|
|
317
317
|
"dependencies": [
|
|
@@ -1551,7 +1551,8 @@
|
|
|
1551
1551
|
"variants": {
|
|
1552
1552
|
"variant": [
|
|
1553
1553
|
"default",
|
|
1554
|
-
"outlined"
|
|
1554
|
+
"outlined",
|
|
1555
|
+
"filled-primary"
|
|
1555
1556
|
],
|
|
1556
1557
|
"size": [
|
|
1557
1558
|
"sm",
|
|
@@ -1562,7 +1563,7 @@
|
|
|
1562
1563
|
"props": [
|
|
1563
1564
|
{
|
|
1564
1565
|
"name": "variant",
|
|
1565
|
-
"type": "\"default\" | \"outlined\"",
|
|
1566
|
+
"type": "\"default\" | \"outlined\" | \"filled-primary\"",
|
|
1566
1567
|
"default": "\"default\""
|
|
1567
1568
|
},
|
|
1568
1569
|
{
|
|
@@ -1693,6 +1694,7 @@
|
|
|
1693
1694
|
"--primary",
|
|
1694
1695
|
"--primary-text",
|
|
1695
1696
|
"--radius-full",
|
|
1697
|
+
"--shadow-sm",
|
|
1696
1698
|
"--spacing-1",
|
|
1697
1699
|
"--spacing-2",
|
|
1698
1700
|
"--spacing-3",
|
|
@@ -3525,6 +3527,62 @@
|
|
|
3525
3527
|
],
|
|
3526
3528
|
"example": "<EmptyState\n icon={<FolderOpen />}\n heading=\"No profiles yet\"\n description=\"Create your first profile to get started.\"\n action={<Button>New profile</Button>}\n secondaryAction={<Button variant=\"outline\">Learn more</Button>}\n/>\n"
|
|
3527
3529
|
},
|
|
3530
|
+
"error-placard": {
|
|
3531
|
+
"category": "feedback",
|
|
3532
|
+
"description": "Inline failed-load placard with a destructive icon chip, title, body, and optional recovery actions.",
|
|
3533
|
+
"when_to_use": [
|
|
3534
|
+
"A specific section or data panel failed to load and requires user action to recover",
|
|
3535
|
+
"Displaying a named error with both context and a retry or dismiss option in-place",
|
|
3536
|
+
"Inline error states within cards, list items, or content panels (not page-level)"
|
|
3537
|
+
],
|
|
3538
|
+
"when_not_to_use": [
|
|
3539
|
+
"Passive informational notices or warnings (use Alert)",
|
|
3540
|
+
"Site-wide or page-level announcements (use Banner)",
|
|
3541
|
+
"Empty states where no data exists yet (use EmptyState)",
|
|
3542
|
+
"Ephemeral transient notifications (use Toast)"
|
|
3543
|
+
],
|
|
3544
|
+
"props": [
|
|
3545
|
+
{
|
|
3546
|
+
"name": "icon",
|
|
3547
|
+
"type": "ReactNode",
|
|
3548
|
+
"required": true,
|
|
3549
|
+
"description": "Leading visual rendered in a destructive-tinted circular chip. Typically a Phosphor icon at 22px."
|
|
3550
|
+
},
|
|
3551
|
+
{
|
|
3552
|
+
"name": "title",
|
|
3553
|
+
"type": "ReactNode",
|
|
3554
|
+
"required": true,
|
|
3555
|
+
"description": "Short direct statement of the failure."
|
|
3556
|
+
},
|
|
3557
|
+
{
|
|
3558
|
+
"name": "body",
|
|
3559
|
+
"type": "ReactNode",
|
|
3560
|
+
"required": true,
|
|
3561
|
+
"description": "One or two sentences of explanation or recovery guidance."
|
|
3562
|
+
},
|
|
3563
|
+
{
|
|
3564
|
+
"name": "actions",
|
|
3565
|
+
"type": "ReactNode",
|
|
3566
|
+
"required": false,
|
|
3567
|
+
"description": "Optional right-aligned action cluster — typically one or more Buttons (retry, dismiss, contact support)."
|
|
3568
|
+
}
|
|
3569
|
+
],
|
|
3570
|
+
"dependencies": [
|
|
3571
|
+
"@loworbitstudio/visor-core"
|
|
3572
|
+
],
|
|
3573
|
+
"tokens_used": [
|
|
3574
|
+
"--destructive",
|
|
3575
|
+
"--font-size-base",
|
|
3576
|
+
"--radius-lg",
|
|
3577
|
+
"--spacing-1",
|
|
3578
|
+
"--spacing-2",
|
|
3579
|
+
"--spacing-5",
|
|
3580
|
+
"--spacing-8",
|
|
3581
|
+
"--surface-card",
|
|
3582
|
+
"--text-tertiary"
|
|
3583
|
+
],
|
|
3584
|
+
"example": "import { WarningCircle } from '@phosphor-icons/react';\n\n<ErrorPlacard\n icon={<WarningCircle weight=\"fill\" />}\n title=\"Could not load members\"\n body=\"The request timed out. Check your connection and try again.\"\n actions={\n <>\n <Button variant=\"ghost\" size=\"sm\">Dismiss</Button>\n <Button variant=\"outline\" size=\"sm\">Retry</Button>\n </>\n }\n/>\n"
|
|
3585
|
+
},
|
|
3528
3586
|
"field": {
|
|
3529
3587
|
"category": "form",
|
|
3530
3588
|
"description": "A form field wrapper composing label, description, and error components.",
|
|
@@ -7819,6 +7877,14 @@
|
|
|
7819
7877
|
"Pagination through pages of content (use Pagination)"
|
|
7820
7878
|
],
|
|
7821
7879
|
"variants": [
|
|
7880
|
+
{
|
|
7881
|
+
"name": "variant",
|
|
7882
|
+
"values": [
|
|
7883
|
+
"default",
|
|
7884
|
+
"prominent"
|
|
7885
|
+
],
|
|
7886
|
+
"description": "Visual treatment for the entire Stepper. \"default\" is the standard step indicator for multi-step forms and linear wizards. \"prominent\" is designed for vertical derivation spines and primary-navigation surfaces: the active row gets a primary-soft tint, the active bullet renders a concentric halo and filled pulse dot (replacing the step number), and complete-to-next rails render in a primary-line tint. Token surface: --interactive-primary-bg (border + fill + pulse dot), --interactive-primary-soft (row tint + halo), color-mix(in srgb, --interactive-primary-bg 34%, transparent) (rail tint). All values are fully theme-agnostic.\n"
|
|
7887
|
+
},
|
|
7822
7888
|
{
|
|
7823
7889
|
"name": "status",
|
|
7824
7890
|
"values": [
|
|
@@ -7883,6 +7949,7 @@
|
|
|
7883
7949
|
"--font-weight-medium",
|
|
7884
7950
|
"--font-weight-normal",
|
|
7885
7951
|
"--interactive-primary-bg",
|
|
7952
|
+
"--interactive-primary-soft",
|
|
7886
7953
|
"--interactive-primary-text",
|
|
7887
7954
|
"--line-height-normal",
|
|
7888
7955
|
"--line-height-snug",
|
|
@@ -7891,6 +7958,7 @@
|
|
|
7891
7958
|
"--opacity-60",
|
|
7892
7959
|
"--primary",
|
|
7893
7960
|
"--radius-full",
|
|
7961
|
+
"--radius-lg",
|
|
7894
7962
|
"--spacing-1",
|
|
7895
7963
|
"--spacing-2",
|
|
7896
7964
|
"--spacing-4",
|
|
@@ -8778,6 +8846,117 @@
|
|
|
8778
8846
|
],
|
|
8779
8847
|
"example": "import { Toaster, toast } from \"@/components/ui/toast\"\n\n{/* Place once in your layout */}\n<Toaster />\n\n{/* Call imperatively */}\ntoast.success(\"Changes saved\")\ntoast.error(\"Something went wrong\")\n"
|
|
8780
8848
|
},
|
|
8849
|
+
"toast-card": {
|
|
8850
|
+
"category": "feedback",
|
|
8851
|
+
"description": "A static, server-renderable notification card for editorial display of toast anatomy. Use in state galleries and design documentation — not for imperative portal notifications.",
|
|
8852
|
+
"when_to_use": [
|
|
8853
|
+
"State gallery screens showing notification anatomy in-flow",
|
|
8854
|
+
"Design documentation illustrating toast variants",
|
|
8855
|
+
"Static editorial rendering of notification cards without portal or timer machinery",
|
|
8856
|
+
"Layouts that need declarative, server-rendered notification cards"
|
|
8857
|
+
],
|
|
8858
|
+
"when_not_to_use": [
|
|
8859
|
+
"Imperative portal notifications that auto-dismiss (use Toast with Sonner instead)",
|
|
8860
|
+
"Notifications triggered by user actions at runtime (use Toast)",
|
|
8861
|
+
"Full-width announcement bars (use Banner)",
|
|
8862
|
+
"Inline contextual callouts within content (use Alert)"
|
|
8863
|
+
],
|
|
8864
|
+
"variants": {
|
|
8865
|
+
"variant": [
|
|
8866
|
+
"success",
|
|
8867
|
+
"error",
|
|
8868
|
+
"info",
|
|
8869
|
+
"warning"
|
|
8870
|
+
]
|
|
8871
|
+
},
|
|
8872
|
+
"props": [
|
|
8873
|
+
{
|
|
8874
|
+
"name": "variant",
|
|
8875
|
+
"type": "\"success\" | \"error\" | \"info\" | \"warning\"",
|
|
8876
|
+
"default": "\"info\"",
|
|
8877
|
+
"description": "Intent variant controlling the icon badge color and default glyph"
|
|
8878
|
+
},
|
|
8879
|
+
{
|
|
8880
|
+
"name": "title",
|
|
8881
|
+
"type": "React.ReactNode",
|
|
8882
|
+
"description": "Bold one-line headline. Required."
|
|
8883
|
+
},
|
|
8884
|
+
{
|
|
8885
|
+
"name": "body",
|
|
8886
|
+
"type": "React.ReactNode",
|
|
8887
|
+
"description": "Muted supporting line beneath the title. Optional."
|
|
8888
|
+
},
|
|
8889
|
+
{
|
|
8890
|
+
"name": "action",
|
|
8891
|
+
"type": "React.ReactNode",
|
|
8892
|
+
"description": "Primary-colored action affordance text. Optional. Renders as a button when onAction is provided, otherwise static text."
|
|
8893
|
+
},
|
|
8894
|
+
{
|
|
8895
|
+
"name": "onAction",
|
|
8896
|
+
"type": "React.MouseEventHandler<HTMLButtonElement>",
|
|
8897
|
+
"description": "Handler for the action affordance. When omitted with action, renders action as static text."
|
|
8898
|
+
},
|
|
8899
|
+
{
|
|
8900
|
+
"name": "onDismiss",
|
|
8901
|
+
"type": "React.MouseEventHandler<HTMLButtonElement>",
|
|
8902
|
+
"description": "Dismiss handler. When provided, renders a muted X dismiss control."
|
|
8903
|
+
},
|
|
8904
|
+
{
|
|
8905
|
+
"name": "icon",
|
|
8906
|
+
"type": "React.ReactNode",
|
|
8907
|
+
"description": "Override the leading glyph. Defaults to the per-variant Phosphor icon (CheckCircle / WarningCircle / Info / Warning)."
|
|
8908
|
+
}
|
|
8909
|
+
],
|
|
8910
|
+
"sub_components": [
|
|
8911
|
+
{
|
|
8912
|
+
"name": "ToastCardStack",
|
|
8913
|
+
"description": "Fixed top-right stacking container for multiple ToastCards. Declarative and server-renderable. Accepts `top` and `gap` string props that map to CSS custom properties for consumer retuning without forking. Renders `data-slot=\"toast-card-stack\"`.\n"
|
|
8914
|
+
}
|
|
8915
|
+
],
|
|
8916
|
+
"dependencies": [
|
|
8917
|
+
"class-variance-authority",
|
|
8918
|
+
"@phosphor-icons/react"
|
|
8919
|
+
],
|
|
8920
|
+
"tokens_used": [
|
|
8921
|
+
"--accent",
|
|
8922
|
+
"--border-focus",
|
|
8923
|
+
"--color-white",
|
|
8924
|
+
"--destructive",
|
|
8925
|
+
"--focus-ring-offset",
|
|
8926
|
+
"--focus-ring-width",
|
|
8927
|
+
"--font-size-sm",
|
|
8928
|
+
"--font-weight-medium",
|
|
8929
|
+
"--font-weight-semibold",
|
|
8930
|
+
"--motion-duration-150",
|
|
8931
|
+
"--motion-easing-default",
|
|
8932
|
+
"--primary",
|
|
8933
|
+
"--primary-text",
|
|
8934
|
+
"--radius-full",
|
|
8935
|
+
"--radius-md",
|
|
8936
|
+
"--radius-sm",
|
|
8937
|
+
"--shadow-lg",
|
|
8938
|
+
"--spacing-1",
|
|
8939
|
+
"--spacing-3",
|
|
8940
|
+
"--spacing-4",
|
|
8941
|
+
"--spacing-6",
|
|
8942
|
+
"--success",
|
|
8943
|
+
"--surface-card",
|
|
8944
|
+
"--surface-elev",
|
|
8945
|
+
"--text-inverse",
|
|
8946
|
+
"--text-muted",
|
|
8947
|
+
"--text-primary",
|
|
8948
|
+
"--text-tertiary",
|
|
8949
|
+
"--toast-card-body-font-size",
|
|
8950
|
+
"--toast-card-icon-glyph",
|
|
8951
|
+
"--toast-card-icon-size",
|
|
8952
|
+
"--toast-card-stack-gap",
|
|
8953
|
+
"--toast-card-stack-right",
|
|
8954
|
+
"--toast-card-stack-top",
|
|
8955
|
+
"--toast-card-stack-z",
|
|
8956
|
+
"--warning"
|
|
8957
|
+
],
|
|
8958
|
+
"example": "<ToastCard variant=\"success\" title=\"Changes saved\" body=\"Your profile has been updated.\" />\n\n<ToastCard\n variant=\"error\"\n title=\"Upload failed\"\n body=\"The file exceeds the 10MB limit.\"\n action=\"Try again\"\n onAction={() => {}}\n onDismiss={() => {}}\n/>\n"
|
|
8959
|
+
},
|
|
8781
8960
|
"toggle-group": {
|
|
8782
8961
|
"category": "form",
|
|
8783
8962
|
"description": "A toggle group component with single-select and multi-select modes using CVA variants.",
|
|
@@ -11223,10 +11402,12 @@
|
|
|
11223
11402
|
"alert",
|
|
11224
11403
|
"banner",
|
|
11225
11404
|
"challenge-card",
|
|
11405
|
+
"error-placard",
|
|
11226
11406
|
"popover",
|
|
11227
11407
|
"progress",
|
|
11228
11408
|
"skeleton",
|
|
11229
11409
|
"toast",
|
|
11410
|
+
"toast-card",
|
|
11230
11411
|
"tooltip"
|
|
11231
11412
|
],
|
|
11232
11413
|
"layout": [
|