@adea-ai/ui 0.82.0 → 0.82.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/components/composites/appearance-editor/appearance-choices.d.ts.map +1 -1
  2. package/dist/components/composites/appearance-editor/appearance-choices.js +3 -3
  3. package/dist/components/composites/appearance-editor/appearance-choices.js.map +1 -1
  4. package/dist/components/composites/appearance-editor/appearance-rows.d.ts.map +1 -1
  5. package/dist/components/composites/appearance-editor/appearance-rows.js +13 -2
  6. package/dist/components/composites/appearance-editor/appearance-rows.js.map +1 -1
  7. package/dist/components/layout/side-rail/side-rail.d.ts +1 -0
  8. package/dist/components/layout/side-rail/side-rail.d.ts.map +1 -1
  9. package/dist/components/layout/side-rail/side-rail.js +6 -2
  10. package/dist/components/layout/side-rail/side-rail.js.map +1 -1
  11. package/dist/components/ui/command/command.d.ts +5 -6
  12. package/dist/components/ui/command/command.d.ts.map +1 -1
  13. package/dist/components/ui/command/command.js +73 -15
  14. package/dist/components/ui/command/command.js.map +1 -1
  15. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts +3 -0
  16. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  17. package/dist/components/ui/dropdown-menu/dropdown-menu.js +7 -1
  18. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  19. package/dist/components/ui/status-chip/status-chip.d.ts +2 -0
  20. package/dist/components/ui/status-chip/status-chip.d.ts.map +1 -1
  21. package/dist/components/ui/status-chip/status-chip.js +26 -17
  22. package/dist/components/ui/status-chip/status-chip.js.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/lib/overlay.js +1 -1
  25. package/dist/lib/overlay.js.map +1 -1
  26. package/dist/lint/index.d.ts +2 -0
  27. package/dist/lint/index.d.ts.map +1 -1
  28. package/dist/lint.js +82 -0
  29. package/dist/lint.js.map +1 -1
  30. package/dist/r/command.json +2 -0
  31. package/dist/r/registry.json +2 -0
  32. package/dist/r/src/components/composites/appearance-editor/appearance-choices.tsx +3 -5
  33. package/dist/r/src/components/composites/appearance-editor/appearance-rows.tsx +9 -3
  34. package/dist/r/src/components/layout/side-rail/side-rail.tsx +8 -3
  35. package/dist/r/src/components/ui/command/command.tsx +81 -29
  36. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +5 -1
  37. package/dist/r/src/components/ui/status-chip/status-chip.tsx +11 -2
  38. package/dist/r/src/lib/overlay.ts +1 -1
  39. package/package.json +1 -1
  40. package/registry.json +2 -0
  41. package/src/components/composites/appearance-editor/appearance-choices.tsx +3 -5
  42. package/src/components/composites/appearance-editor/appearance-rows.tsx +9 -3
  43. package/src/components/layout/side-rail/side-rail.tsx +8 -3
  44. package/src/components/ui/command/command.tsx +81 -29
  45. package/src/components/ui/dropdown-menu/dropdown-menu.tsx +5 -1
  46. package/src/components/ui/status-chip/status-chip.tsx +11 -2
  47. package/src/lib/overlay.ts +1 -1
  48. package/src/lint/index.ts +95 -0
package/dist/lint.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return\n emit(node, element)\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@corvu',\n 'cmdk-solid',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n '@base-ui',\n])\n\n/**\n * The interactive ARIA roles an application can dress a generic element in, and\n * the primitive that owns each role. `link` is deliberately absent: an `<a>` is\n * natively interactive and Button itself supports `as=\"a\"` for the link-styled\n * case.\n */\nconst ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },\n radio: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },\n switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },\n tab: { primitive: 'Tabs', path: '@adea-ai/ui/components/ui/tabs' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n menuitem: { primitive: 'DropdownMenu', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n })\n\n/**\n * The elements whose only job is layout or text. A click handler or a tabindex on\n * one of these is a control being hand-built out of a non-control.\n */\nconst GENERIC_ELEMENTS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'ul',\n 'ol',\n 'td',\n 'th',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'main',\n 'img',\n 'svg',\n])\n\nconst CLICK_HANDLERS: ReadonlySet<string> = new Set([\n 'onClick',\n 'onDoubleClick',\n 'onPointerDown',\n 'onPointerUp',\n 'onMouseDown',\n 'onMouseUp',\n 'on:click',\n 'on:dblclick',\n 'on:pointerdown',\n 'on:pointerup',\n 'on:mousedown',\n 'on:mouseup',\n])\n\ninterface InteractiveWrapperOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\n/** Only statically knowable branches; unresolved roles remain a review boundary. */\nconst staticStrings = (node: any): string[] => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]\n if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression)\n if (node?.type === 'ConditionalExpression')\n return [...staticStrings(node.consequent), ...staticStrings(node.alternate)]\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]\n return []\n}\n\nconst roleOf = (node: any): string | null => {\n for (const attribute of node.attributes ?? []) {\n if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue\n return (\n staticStrings(attribute.value)\n .flatMap((role) => role.split(/\\s+/))\n .find((role) => role in ROLE_PRIMITIVES) ?? null\n )\n }\n return null\n}\n\n/** Whether the element carries the attribute, by name. */\nconst has = (node: any, name: string): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) =>\n attribute.type === 'JSXAttribute' &&\n (attribute.name?.type === 'JSXNamespacedName'\n ? `${attribute.name.namespace.name}:${attribute.name.name.name}`\n : attribute.name?.name) === name\n )\n\nconst hasTabStop = (node: any): boolean =>\n (node.attributes ?? []).some((attribute: any) => {\n if (\n attribute.type !== 'JSXAttribute' ||\n !['tabIndex', 'tabindex'].includes(attribute.name?.name)\n )\n return false\n const value =\n attribute.value?.type === 'JSXExpressionContainer'\n ? attribute.value.expression\n : attribute.value\n if (value?.type === 'Literal') return !(Number(value.value) < 0)\n if (\n value?.type === 'UnaryExpression' &&\n value.operator === '-' &&\n value.argument?.type === 'Literal'\n )\n return !(Number(value.argument.value) > 0)\n return true\n })\n\nconst noInteractiveWrappers: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n roleWrapper:\n 'A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.',\n clickWrapper:\n 'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',\n tabindexWrapper:\n 'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions\n const allowed = new Set(options.allow ?? [])\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return\n // A capitalized element is a component — often a design-system primitive\n // composing a role deliberately (a Button carrying role=\"option\" inside a\n // custom listbox, for example). That is composition, not impersonation.\n if (element[0] === element[0]?.toUpperCase()) return\n\n const role = roleOf(node)\n if (role && role in ROLE_PRIMITIVES) {\n const target = ROLE_PRIMITIVES[role]!\n context.report({\n node,\n messageId: 'roleWrapper',\n data: { element, role, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n\n if (GENERIC_ELEMENTS.has(element)) {\n const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))\n if (click) {\n const target = ROLE_PRIMITIVES.button!\n context.report({\n node,\n messageId: 'clickWrapper',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n if (hasTabStop(node)) {\n context.report({\n node,\n messageId: 'tabindexWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n }\n },\n }\n },\n}\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n ExportNamedDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ExportAllDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n CallExpression(node: any) {\n if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return\n const source = node.arguments?.[0]?.value\n if (typeof source === 'string' && offender(source)) report(node, source)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n 'no-interactive-wrappers': noInteractiveWrappers,\n },\n}\n\nexport default plugin\n"],"mappings":"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"}
1
+ {"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\n/** Reads a static object-property key without evaluating a computed expression. */\nconst staticPropertyKey = (node: any, computed: boolean): string | null => {\n if (!computed && node?.type === 'Identifier') return node.name\n if (node?.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw\n return null\n}\n\n/** Detects named props in a literal JSX spread, including nested literal spreads. */\nconst objectHasStaticProperty = (node: any, name: string, seen = new Set<unknown>()): boolean => {\n if (!node || seen.has(node)) return false\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return objectHasStaticProperty(node.expression, name, seen)\n if (node.type !== 'ObjectExpression') return false\n return node.properties.some((property: any) => {\n if (property.type === 'Property')\n return staticPropertyKey(property.key, property.computed === true) === name\n if (property.type === 'SpreadElement')\n return objectHasStaticProperty(property.argument, name, seen)\n return false\n })\n}\n\nconst noInlineStyles: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Consumer components cannot set appearance or layout with JSX style props; use shared variants and token-backed classes.',\n },\n schema: [],\n messages: {\n inlineStyle:\n 'JSX style attributes are not permitted, including layout values and CSS custom properties. Use shared variants and token-backed classes.',\n inlineStyleElement:\n 'A JSX style element bypasses the shared design system. Use shared variants and token-backed classes.',\n inlineStyleSpread:\n 'A JSX spread contains a style prop. Pass named shared component props instead of hiding inline styles in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyle' })\n },\n JSXOpeningElement(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyleElement' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'style'))\n context.report({ node, messageId: 'inlineStyleSpread' })\n },\n }\n },\n}\n\nconst noClassList: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Solid classList props hide conditional classes from static design-system lint; use cn() with its object-key form.',\n },\n schema: [],\n messages: {\n classList:\n 'classList hides classes from design-system lint. Use cn() with its object-key form and statically readable class names.',\n classListSpread:\n 'A JSX spread contains classList. Use cn() with its object-key form instead of hiding classes in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'classList')\n context.report({ node, messageId: 'classList' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'classList'))\n context.report({ node, messageId: 'classListSpread' })\n },\n }\n },\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return\n emit(node, element)\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@corvu',\n 'cmdk-solid',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n '@base-ui',\n])\n\n/**\n * The interactive ARIA roles an application can dress a generic element in, and\n * the primitive that owns each role. `link` is deliberately absent: an `<a>` is\n * natively interactive and Button itself supports `as=\"a\"` for the link-styled\n * case.\n */\nconst ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },\n radio: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },\n switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },\n tab: { primitive: 'Tabs', path: '@adea-ai/ui/components/ui/tabs' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n menuitem: { primitive: 'DropdownMenu', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n })\n\n/**\n * The elements whose only job is layout or text. A click handler or a tabindex on\n * one of these is a control being hand-built out of a non-control.\n */\nconst GENERIC_ELEMENTS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'ul',\n 'ol',\n 'td',\n 'th',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'main',\n 'img',\n 'svg',\n])\n\nconst CLICK_HANDLERS: ReadonlySet<string> = new Set([\n 'onClick',\n 'onDoubleClick',\n 'onPointerDown',\n 'onPointerUp',\n 'onMouseDown',\n 'onMouseUp',\n 'on:click',\n 'on:dblclick',\n 'on:pointerdown',\n 'on:pointerup',\n 'on:mousedown',\n 'on:mouseup',\n])\n\ninterface InteractiveWrapperOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\n/** Only statically knowable branches; unresolved roles remain a review boundary. */\nconst staticStrings = (node: any): string[] => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]\n if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression)\n if (node?.type === 'ConditionalExpression')\n return [...staticStrings(node.consequent), ...staticStrings(node.alternate)]\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]\n return []\n}\n\nconst roleOf = (node: any): string | null => {\n for (const attribute of node.attributes ?? []) {\n if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue\n return (\n staticStrings(attribute.value)\n .flatMap((role) => role.split(/\\s+/))\n .find((role) => role in ROLE_PRIMITIVES) ?? null\n )\n }\n return null\n}\n\n/** Whether the element carries the attribute, by name. */\nconst has = (node: any, name: string): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) =>\n attribute.type === 'JSXAttribute' &&\n (attribute.name?.type === 'JSXNamespacedName'\n ? `${attribute.name.namespace.name}:${attribute.name.name.name}`\n : attribute.name?.name) === name\n )\n\nconst hasTabStop = (node: any): boolean =>\n (node.attributes ?? []).some((attribute: any) => {\n if (\n attribute.type !== 'JSXAttribute' ||\n !['tabIndex', 'tabindex'].includes(attribute.name?.name)\n )\n return false\n const value =\n attribute.value?.type === 'JSXExpressionContainer'\n ? attribute.value.expression\n : attribute.value\n if (value?.type === 'Literal') return !(Number(value.value) < 0)\n if (\n value?.type === 'UnaryExpression' &&\n value.operator === '-' &&\n value.argument?.type === 'Literal'\n )\n return !(Number(value.argument.value) > 0)\n return true\n })\n\nconst noInteractiveWrappers: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n roleWrapper:\n 'A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.',\n clickWrapper:\n 'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',\n tabindexWrapper:\n 'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions\n const allowed = new Set(options.allow ?? [])\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return\n // A capitalized element is a component — often a design-system primitive\n // composing a role deliberately (a Button carrying role=\"option\" inside a\n // custom listbox, for example). That is composition, not impersonation.\n if (element[0] === element[0]?.toUpperCase()) return\n\n const role = roleOf(node)\n if (role && role in ROLE_PRIMITIVES) {\n const target = ROLE_PRIMITIVES[role]!\n context.report({\n node,\n messageId: 'roleWrapper',\n data: { element, role, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n\n if (GENERIC_ELEMENTS.has(element)) {\n const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))\n if (click) {\n const target = ROLE_PRIMITIVES.button!\n context.report({\n node,\n messageId: 'clickWrapper',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n if (hasTabStop(node)) {\n context.report({\n node,\n messageId: 'tabindexWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n }\n },\n }\n },\n}\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n ExportNamedDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ExportAllDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n CallExpression(node: any) {\n if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return\n const source = node.arguments?.[0]?.value\n if (typeof source === 'string' && offender(source)) report(node, source)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-inline-styles': noInlineStyles,\n 'no-class-list': noClassList,\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n 'no-interactive-wrappers': noInteractiveWrappers,\n },\n}\n\nexport default plugin\n"],"mappings":"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"}
@@ -4,10 +4,12 @@
4
4
  "title": "Command",
5
5
  "description": "",
6
6
  "dependencies": [
7
+ "@kobalte/core@^0.13.14",
7
8
  "cmdk-solid@^1.2.0",
8
9
  "lucide-solid@^1.48.0"
9
10
  ],
10
11
  "registryDependencies": [
12
+ "@adea-ai/ui/dialog",
11
13
  "lib"
12
14
  ],
13
15
  "files": [
@@ -442,10 +442,12 @@
442
442
  "title": "Command",
443
443
  "description": "",
444
444
  "dependencies": [
445
+ "@kobalte/core@^0.13.14",
445
446
  "cmdk-solid@^1.2.0",
446
447
  "lucide-solid@^1.48.0"
447
448
  ],
448
449
  "registryDependencies": [
450
+ "@adea-ai/ui/dialog",
449
451
  "lib"
450
452
  ],
451
453
  "files": [
@@ -93,9 +93,7 @@ export function AccentChoices(props: AppearanceEditorProps) {
93
93
  disabled={props.saving}
94
94
  orientation="horizontal"
95
95
  aria-label="Accent"
96
- /* Narrow hosts wrap the swatches into a three-column grid so the row
97
- control stays beside its label; wide hosts keep one inline row. */
98
- class="grid grid-cols-3 items-center gap-2 sm:flex sm:flex-wrap sm:justify-end"
96
+ class="flex flex-wrap items-center gap-2"
99
97
  onChange={(accent) =>
100
98
  props.onChange({
101
99
  accent: accent === 'custom' ? (props.customAccentValue ?? '') : accent,
@@ -104,11 +102,11 @@ export function AccentChoices(props: AppearanceEditorProps) {
104
102
  >
105
103
  <Radio.Item value="theme">
106
104
  <Radio.ItemInput />
107
- <Radio.ItemLabel class="flex h-7 cursor-pointer items-center rounded-md border px-2 text-xs data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
105
+ <Radio.ItemLabel class="flex h-7 cursor-pointer items-center rounded-md border px-2 text-xs whitespace-nowrap data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
108
106
  Theme default
109
107
  </Radio.ItemLabel>
110
108
  </Radio.Item>
111
- <div class="col-span-3 grid grid-cols-3 gap-2 sm:col-span-1 sm:flex sm:gap-2">
109
+ <div class="flex max-w-full flex-wrap gap-2">
112
110
  <For each={props.accentOptions}>
113
111
  {(option) => (
114
112
  <Radio.Item
@@ -60,9 +60,15 @@ export function SettingsRow(props: {
60
60
  }) {
61
61
  return (
62
62
  <section
63
- class={cn('flex flex-col gap-3 px-4 py-4', { 'sm:flex-row sm:items-center': !props.stacked })}
63
+ class={cn('flex gap-3 px-4 py-4', {
64
+ 'flex-col': props.stacked,
65
+ 'flex-wrap items-center': !props.stacked,
66
+ })}
64
67
  >
65
- <div class="flex min-w-0 flex-1 items-center gap-3">
68
+ {/* Reserve readable copy before choosing a side-by-side control row.
69
+ Wrapping follows the actual host width and enlarged text, rather
70
+ than a viewport breakpoint that also matches a narrow popover. */}
71
+ <div class={cn('flex min-w-0 flex-1 items-center gap-3', { 'basis-48': !props.stacked })}>
66
72
  <span
67
73
  class="flex size-9 shrink-0 items-center justify-center rounded-lg border bg-foreground/5 text-muted-foreground [&_svg]:size-4"
68
74
  aria-hidden="true"
@@ -76,7 +82,7 @@ export function SettingsRow(props: {
76
82
  </Show>
77
83
  </div>
78
84
  </div>
79
- <div class="min-w-0 shrink-0">{props.children}</div>
85
+ <div class="min-w-0 max-w-full shrink-0">{props.children}</div>
80
86
  </section>
81
87
  )
82
88
  }
@@ -329,9 +329,13 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
329
329
 
330
330
  /** A non-navigating rail control: a collapse toggle, a "new" action. */
331
331
  export function SideRailButton(
332
- props: Omit<ComponentProps<'button'>, 'type'> & { label: string; shortcut?: string }
332
+ props: Omit<ComponentProps<'button'>, 'type'> & {
333
+ label: string
334
+ shortcut?: string
335
+ active?: boolean
336
+ }
333
337
  ) {
334
- const [local, rest] = splitProps(props, ['class', 'label', 'shortcut', 'children'])
338
+ const [local, rest] = splitProps(props, ['class', 'label', 'shortcut', 'children', 'active'])
335
339
  const [row, setRow] = createSignal<HTMLElement>()
336
340
  const [tip, setTip] = createSignal<{ top: number; left: number; height: number } | null>(null)
337
341
 
@@ -351,13 +355,14 @@ export function SideRailButton(
351
355
  type="button"
352
356
  ref={setRow}
353
357
  aria-label={local.label}
358
+ data-active={local.active ? 'true' : undefined}
354
359
  onPointerEnter={showTip}
355
360
  onPointerLeave={() => setTip(null)}
356
361
  onFocus={showTip}
357
362
  onBlur={() => setTip(null)}
358
363
  class={cn(
359
364
  sideRailItemClass,
360
- sideRailItemStateClass.idle,
365
+ local.active ? sideRailItemStateClass.active : sideRailItemStateClass.idle,
361
366
  'disabled:pointer-events-none disabled:opacity-50',
362
367
  local.class
363
368
  )}
@@ -10,17 +10,20 @@ import {
10
10
  CommandSeparator as CmdkSeparator,
11
11
  useCommandState,
12
12
  } from 'cmdk-solid'
13
+ import { Dialog as KobalteDialog } from '@kobalte/core/dialog'
13
14
  import { Search } from 'lucide-solid'
14
15
  import type { ComponentProps } from 'solid-js'
15
16
  import { createEffect, createSignal, splitProps } from 'solid-js'
17
+ import { createDialogFocusRestoration } from '../dialog/dialog'
16
18
  import { menuItem } from '../../../lib/overlay'
17
19
  import { cn } from '../../../lib/utils'
18
20
 
19
- // cmdk emits data-disabled="false" on enabled items; Kobalte omits it.
20
- // Presence selectors would dim and disable every command in an app palette.
21
+ // cmdk emits data-disabled="false" on enabled items, unlike the presence-only
22
+ // attributes Kobalte menu rows use. Apply those shared disabled styles only when
23
+ // cmdk's value is true.
21
24
  const commandItem = menuItem
22
25
  .replace('data-[disabled]:pointer-events-none', '')
23
- .replace('data-[disabled]:opacity-50', '')
26
+ .replace('data-[disabled]:text-muted-foreground', '')
24
27
 
25
28
  /**
26
29
  * Command.
@@ -52,26 +55,76 @@ export function Command(props: ComponentProps<typeof CmdkRoot>) {
52
55
  )
53
56
  }
54
57
 
55
- export function CommandDialog(props: ComponentProps<typeof CmdkDialog>) {
56
- // cmdk's Dialog splits `contentClassName` onto the role="dialog" content
57
- // element and routes every other prop (including `class`) to the inner
58
- // Command. The surface must live on the content: a `class` here styles the
59
- // Command (fixed, so visible) while the dialog element itself renders as an
60
- // unstyled zero-height wrapper at the end of body — invisible to role
61
- // queries and axe.
62
- const [local, rest] = splitProps(props, ['class'])
58
+ type CommandDialogProps = ComponentProps<typeof CmdkDialog> &
59
+ Pick<ComponentProps<typeof KobalteDialog.Content>, 'onCloseAutoFocus' | 'onOpenAutoFocus'>
60
+
61
+ export function CommandDialog(props: CommandDialogProps) {
62
+ // cmdk-solid's Dialog does not forward Content autofocus props: unknown
63
+ // props fall through to its Command root. Compose Kobalte's dialog surface
64
+ // here so Escape and other close paths can restore focus to the real opener.
65
+ // Preserve the shared wrapper's `class` contract on the dialog surface;
66
+ // `contentClassName` adds to it, while the nested Command keeps its own API.
67
+ const [local, dialogProps, commandProps] = splitProps(
68
+ props,
69
+ [
70
+ 'class',
71
+ 'contentClassName',
72
+ 'overlayClassName',
73
+ 'container',
74
+ 'onCloseAutoFocus',
75
+ 'onOpenAutoFocus',
76
+ ],
77
+ [
78
+ 'open',
79
+ 'defaultOpen',
80
+ 'onOpenChange',
81
+ 'id',
82
+ 'modal',
83
+ 'preventScroll',
84
+ 'forceMount',
85
+ 'translations',
86
+ ]
87
+ )
88
+ const [isOpen, setIsOpen] = createSignal(dialogProps.open ?? dialogProps.defaultOpen ?? false)
89
+
90
+ createEffect(() => {
91
+ if (dialogProps.open !== undefined) setIsOpen(dialogProps.open)
92
+ })
93
+
94
+ const focus = createDialogFocusRestoration({
95
+ open: isOpen,
96
+ onCloseAutoFocus: local.onCloseAutoFocus,
97
+ onOpenAutoFocus: local.onOpenAutoFocus,
98
+ })
63
99
 
64
100
  return (
65
- <CmdkDialog
66
- contentClassName={cn(
67
- 'bg-popover text-popover-foreground fixed top-1/3 left-1/2 z-(--z-dialog) w-full max-w-lg -translate-x-1/2 -translate-y-1/2',
68
- 'overflow-hidden rounded-xl border border-border shadow-xl',
69
- 'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95',
70
- 'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
71
- local.class
72
- )}
73
- {...rest}
74
- />
101
+ <KobalteDialog
102
+ {...dialogProps}
103
+ onOpenChange={(open) => {
104
+ setIsOpen(open)
105
+ dialogProps.onOpenChange?.(open)
106
+ }}
107
+ >
108
+ <KobalteDialog.Portal mount={local.container}>
109
+ <KobalteDialog.Overlay cmdk-overlay="" class={local.overlayClassName} />
110
+ <KobalteDialog.Content
111
+ aria-label={commandProps.label}
112
+ cmdk-dialog=""
113
+ class={cn(
114
+ 'bg-popover text-popover-foreground fixed top-1/3 left-1/2 z-(--z-dialog) w-full max-w-lg -translate-x-1/2 -translate-y-1/2',
115
+ 'overflow-hidden rounded-xl border border-border shadow-xl',
116
+ 'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95',
117
+ 'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
118
+ local.class,
119
+ local.contentClassName
120
+ )}
121
+ onOpenAutoFocus={focus.onOpenAutoFocus}
122
+ onCloseAutoFocus={focus.onCloseAutoFocus}
123
+ >
124
+ <Command {...commandProps} />
125
+ </KobalteDialog.Content>
126
+ </KobalteDialog.Portal>
127
+ </KobalteDialog>
75
128
  )
76
129
  }
77
130
 
@@ -188,8 +241,8 @@ export function CommandItem(props: ComponentProps<typeof CmdkItem>) {
188
241
  <CmdkItem
189
242
  class={cn(
190
243
  commandItem,
191
- 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
192
- 'text-popover-foreground data-[selected=true]:bg-surface-hover data-[selected=true]:text-popover-foreground',
244
+ 'data-[disabled=true]:pointer-events-none data-[disabled=true]:text-muted-foreground',
245
+ 'group/command-item text-popover-foreground data-[selected=true]:bg-card data-[selected=true]:text-popover-foreground',
193
246
  local.class
194
247
  )}
195
248
  {...rest}
@@ -212,18 +265,17 @@ export function CommandLoading(props: ComponentProps<typeof CmdkLoading>) {
212
265
  )
213
266
  }
214
267
 
215
- /**
216
- * The keyboard hint a palette item usually carries.
217
- * Muted canvas text can fail contrast on a raised or selected command row;
218
- * hierarchy comes from its size and monospace face, with the row's readable ink.
219
- */
268
+ /** The keyboard hint a palette item usually carries. */
220
269
  export function CommandShortcut(props: {
221
270
  class?: string
222
271
  children?: ComponentProps<'span'>['children']
223
272
  }) {
224
273
  return (
225
274
  <span
226
- class={cn('text-popover-foreground ms-auto font-mono text-2xs tracking-widest', props.class)}
275
+ class={cn(
276
+ 'text-muted-foreground ms-auto font-mono text-2xs tracking-widest group-data-[selected=true]/command-item:text-foreground',
277
+ props.class
278
+ )}
227
279
  >
228
280
  {props.children}
229
281
  </span>
@@ -25,13 +25,17 @@ import { cn } from '../../../lib/utils'
25
25
  * Kobalte supplies the full menu contract — arrow keys, typeahead, the roving
26
26
  * highlight, Escape to close and return focus — which is exactly what a
27
27
  * hand-rolled `div` with click handlers gets wrong.
28
+ * Menus are nonmodal by default so their generated focus guards do not become
29
+ * invalid direct children of the ARIA menu. Set `modal` explicitly when the
30
+ * surrounding interaction needs modal containment.
28
31
  *
29
32
  * The `destructive` variant marks the row with a status-coloured edge and
30
33
  * highlighted tint while keeping its label on the body foreground. Pairing it
31
34
  * with an AlertDialog is the caller's job.
32
35
  */
33
36
  export function DropdownMenu(props: ComponentProps<typeof KobalteDropdownMenu>) {
34
- return <KobalteDropdownMenu {...props} />
37
+ const [local, rest] = splitProps(props, ['modal'])
38
+ return <KobalteDropdownMenu modal={local.modal ?? false} {...rest} />
35
39
  }
36
40
 
37
41
  export function DropdownMenuTrigger(props: ComponentProps<typeof KobalteDropdownMenu.Trigger>) {
@@ -59,6 +59,8 @@ export type StatusTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info' |
59
59
  export type StatusChipProps = Omit<ComponentProps<'span'>, 'children'> & {
60
60
  tone?: StatusTone
61
61
  label: string
62
+ /** Visually hide the text while preserving it for assistive technology. */
63
+ compact?: boolean
62
64
  /** Why the status is what it is. Shown in a tooltip; omit for a self-evident label. */
63
65
  detail?: string
64
66
  /** A trailing slot: a timestamp, a retry affordance, a count. */
@@ -66,7 +68,14 @@ export type StatusChipProps = Omit<ComponentProps<'span'>, 'children'> & {
66
68
  }
67
69
 
68
70
  export function StatusChip(props: StatusChipProps) {
69
- const [local, rest] = splitProps(props, ['tone', 'label', 'detail', 'trailing', 'class'])
71
+ const [local, rest] = splitProps(props, [
72
+ 'tone',
73
+ 'label',
74
+ 'compact',
75
+ 'detail',
76
+ 'trailing',
77
+ 'class',
78
+ ])
70
79
 
71
80
  const chip = (
72
81
  <span
@@ -78,7 +87,7 @@ export function StatusChip(props: StatusChipProps) {
78
87
  {...rest}
79
88
  >
80
89
  <span class={statusDotVariants({ tone: local.tone })} aria-hidden="true" />
81
- {local.label}
90
+ <span class={cn(local.compact && 'sr-only')}>{local.label}</span>
82
91
  {local.trailing}
83
92
  </span>
84
93
  )
@@ -54,7 +54,7 @@ export const menuItem = [
54
54
  'relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none',
55
55
  'transition-colors ease-out',
56
56
  'data-[highlighted]:bg-surface-hover data-[highlighted]:text-foreground',
57
- 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
57
+ 'data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground',
58
58
  '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:size-4',
59
59
  '[&_svg:not([class*=text-])]:text-muted-foreground',
60
60
  ].join(' ')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.82.0",
3
+ "version": "0.82.2",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
package/registry.json CHANGED
@@ -442,10 +442,12 @@
442
442
  "title": "Command",
443
443
  "description": "",
444
444
  "dependencies": [
445
+ "@kobalte/core@^0.13.14",
445
446
  "cmdk-solid@^1.2.0",
446
447
  "lucide-solid@^1.48.0"
447
448
  ],
448
449
  "registryDependencies": [
450
+ "@adea-ai/ui/dialog",
449
451
  "lib"
450
452
  ],
451
453
  "files": [
@@ -93,9 +93,7 @@ export function AccentChoices(props: AppearanceEditorProps) {
93
93
  disabled={props.saving}
94
94
  orientation="horizontal"
95
95
  aria-label="Accent"
96
- /* Narrow hosts wrap the swatches into a three-column grid so the row
97
- control stays beside its label; wide hosts keep one inline row. */
98
- class="grid grid-cols-3 items-center gap-2 sm:flex sm:flex-wrap sm:justify-end"
96
+ class="flex flex-wrap items-center gap-2"
99
97
  onChange={(accent) =>
100
98
  props.onChange({
101
99
  accent: accent === 'custom' ? (props.customAccentValue ?? '') : accent,
@@ -104,11 +102,11 @@ export function AccentChoices(props: AppearanceEditorProps) {
104
102
  >
105
103
  <Radio.Item value="theme">
106
104
  <Radio.ItemInput />
107
- <Radio.ItemLabel class="flex h-7 cursor-pointer items-center rounded-md border px-2 text-xs data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
105
+ <Radio.ItemLabel class="flex h-7 cursor-pointer items-center rounded-md border px-2 text-xs whitespace-nowrap data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
108
106
  Theme default
109
107
  </Radio.ItemLabel>
110
108
  </Radio.Item>
111
- <div class="col-span-3 grid grid-cols-3 gap-2 sm:col-span-1 sm:flex sm:gap-2">
109
+ <div class="flex max-w-full flex-wrap gap-2">
112
110
  <For each={props.accentOptions}>
113
111
  {(option) => (
114
112
  <Radio.Item
@@ -60,9 +60,15 @@ export function SettingsRow(props: {
60
60
  }) {
61
61
  return (
62
62
  <section
63
- class={cn('flex flex-col gap-3 px-4 py-4', { 'sm:flex-row sm:items-center': !props.stacked })}
63
+ class={cn('flex gap-3 px-4 py-4', {
64
+ 'flex-col': props.stacked,
65
+ 'flex-wrap items-center': !props.stacked,
66
+ })}
64
67
  >
65
- <div class="flex min-w-0 flex-1 items-center gap-3">
68
+ {/* Reserve readable copy before choosing a side-by-side control row.
69
+ Wrapping follows the actual host width and enlarged text, rather
70
+ than a viewport breakpoint that also matches a narrow popover. */}
71
+ <div class={cn('flex min-w-0 flex-1 items-center gap-3', { 'basis-48': !props.stacked })}>
66
72
  <span
67
73
  class="flex size-9 shrink-0 items-center justify-center rounded-lg border bg-foreground/5 text-muted-foreground [&_svg]:size-4"
68
74
  aria-hidden="true"
@@ -76,7 +82,7 @@ export function SettingsRow(props: {
76
82
  </Show>
77
83
  </div>
78
84
  </div>
79
- <div class="min-w-0 shrink-0">{props.children}</div>
85
+ <div class="min-w-0 max-w-full shrink-0">{props.children}</div>
80
86
  </section>
81
87
  )
82
88
  }
@@ -329,9 +329,13 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
329
329
 
330
330
  /** A non-navigating rail control: a collapse toggle, a "new" action. */
331
331
  export function SideRailButton(
332
- props: Omit<ComponentProps<'button'>, 'type'> & { label: string; shortcut?: string }
332
+ props: Omit<ComponentProps<'button'>, 'type'> & {
333
+ label: string
334
+ shortcut?: string
335
+ active?: boolean
336
+ }
333
337
  ) {
334
- const [local, rest] = splitProps(props, ['class', 'label', 'shortcut', 'children'])
338
+ const [local, rest] = splitProps(props, ['class', 'label', 'shortcut', 'children', 'active'])
335
339
  const [row, setRow] = createSignal<HTMLElement>()
336
340
  const [tip, setTip] = createSignal<{ top: number; left: number; height: number } | null>(null)
337
341
 
@@ -351,13 +355,14 @@ export function SideRailButton(
351
355
  type="button"
352
356
  ref={setRow}
353
357
  aria-label={local.label}
358
+ data-active={local.active ? 'true' : undefined}
354
359
  onPointerEnter={showTip}
355
360
  onPointerLeave={() => setTip(null)}
356
361
  onFocus={showTip}
357
362
  onBlur={() => setTip(null)}
358
363
  class={cn(
359
364
  sideRailItemClass,
360
- sideRailItemStateClass.idle,
365
+ local.active ? sideRailItemStateClass.active : sideRailItemStateClass.idle,
361
366
  'disabled:pointer-events-none disabled:opacity-50',
362
367
  local.class
363
368
  )}