@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.
- package/dist/components/composites/appearance-editor/appearance-choices.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-choices.js +3 -3
- package/dist/components/composites/appearance-editor/appearance-choices.js.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-rows.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-rows.js +13 -2
- package/dist/components/composites/appearance-editor/appearance-rows.js.map +1 -1
- package/dist/components/layout/side-rail/side-rail.d.ts +1 -0
- package/dist/components/layout/side-rail/side-rail.d.ts.map +1 -1
- package/dist/components/layout/side-rail/side-rail.js +6 -2
- package/dist/components/layout/side-rail/side-rail.js.map +1 -1
- package/dist/components/ui/command/command.d.ts +5 -6
- package/dist/components/ui/command/command.d.ts.map +1 -1
- package/dist/components/ui/command/command.js +73 -15
- package/dist/components/ui/command/command.js.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.js +7 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/ui/status-chip/status-chip.d.ts +2 -0
- package/dist/components/ui/status-chip/status-chip.d.ts.map +1 -1
- package/dist/components/ui/status-chip/status-chip.js +26 -17
- package/dist/components/ui/status-chip/status-chip.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/overlay.js +1 -1
- package/dist/lib/overlay.js.map +1 -1
- package/dist/lint/index.d.ts +2 -0
- package/dist/lint/index.d.ts.map +1 -1
- package/dist/lint.js +82 -0
- package/dist/lint.js.map +1 -1
- package/dist/r/command.json +2 -0
- package/dist/r/registry.json +2 -0
- package/dist/r/src/components/composites/appearance-editor/appearance-choices.tsx +3 -5
- package/dist/r/src/components/composites/appearance-editor/appearance-rows.tsx +9 -3
- package/dist/r/src/components/layout/side-rail/side-rail.tsx +8 -3
- package/dist/r/src/components/ui/command/command.tsx +81 -29
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +5 -1
- package/dist/r/src/components/ui/status-chip/status-chip.tsx +11 -2
- package/dist/r/src/lib/overlay.ts +1 -1
- package/package.json +1 -1
- package/registry.json +2 -0
- package/src/components/composites/appearance-editor/appearance-choices.tsx +3 -5
- package/src/components/composites/appearance-editor/appearance-rows.tsx +9 -3
- package/src/components/layout/side-rail/side-rail.tsx +8 -3
- package/src/components/ui/command/command.tsx +81 -29
- package/src/components/ui/dropdown-menu/dropdown-menu.tsx +5 -1
- package/src/components/ui/status-chip/status-chip.tsx +11 -2
- package/src/lib/overlay.ts +1 -1
- 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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"}
|
package/dist/r/command.json
CHANGED
package/dist/r/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
|
-
|
|
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="
|
|
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
|
|
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
|
-
|
|
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'> & {
|
|
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
|
|
20
|
-
//
|
|
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]:
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
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
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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]:
|
|
192
|
-
'text-popover-foreground data-[selected=true]:bg-
|
|
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(
|
|
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
|
-
|
|
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, [
|
|
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]:
|
|
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
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
|
-
|
|
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="
|
|
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
|
|
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
|
-
|
|
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'> & {
|
|
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
|
)}
|