@devalok/shilp-sutra 0.55.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BREAKING.json +13 -0
- package/MIGRATION.md +56 -0
- package/README.md +16 -1
- package/dist/_chunks/badge-group.js +45 -45
- package/dist/_chunks/emoji-suggestion.js +32 -30
- package/dist/_chunks/emoji-suggestion.js.map +1 -1
- package/dist/_chunks/mention-suggestion.js +35 -34
- package/dist/_chunks/mention-suggestion.js.map +1 -1
- package/dist/_chunks/primitives-internal.js +437 -537
- package/dist/_chunks/primitives-internal.js.map +1 -1
- package/dist/composed/content-card.js +18 -18
- package/dist/composed/extensions/emoji-node.js +8 -7
- package/dist/composed/extensions/emoji-node.js.map +1 -1
- package/dist/composed/rich-chat-input.js +535 -526
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.js +201 -193
- package/dist/composed/rich-text-editor.js.map +1 -1
- package/dist/tokens/primitives.css +6 -1
- package/dist/tokens/semantic.css +1 -1
- package/dist/ui/alert.js +29 -29
- package/dist/ui/avatar.js +42 -42
- package/dist/ui/banner.js +25 -25
- package/dist/ui/button.js +31 -31
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/card.js +39 -39
- package/dist/ui/checkbox.d.ts.map +1 -1
- package/dist/ui/checkbox.js +1 -0
- package/dist/ui/checkbox.js.map +1 -1
- package/dist/ui/combobox.js +135 -135
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/dot.js +13 -13
- package/dist/ui/input.js +25 -25
- package/dist/ui/lib/utils.js +2 -1
- package/dist/ui/lib/utils.js.map +1 -1
- package/dist/ui/number-input.js +36 -36
- package/dist/ui/progress.js +55 -55
- package/dist/ui/select.js +58 -58
- package/dist/ui/sheet.js +46 -46
- package/dist/ui/sidebar.js +128 -128
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/skeleton.js +39 -39
- package/dist/ui/slider.d.ts.map +1 -1
- package/dist/ui/slider.js +18 -17
- package/dist/ui/slider.js.map +1 -1
- package/dist/ui/surface.js +9 -9
- package/dist/ui/switch.d.ts.map +1 -1
- package/dist/ui/switch.js +1 -0
- package/dist/ui/switch.js.map +1 -1
- package/dist/ui/tabs.js +47 -47
- package/dist/ui/text.js +8 -8
- package/dist/ui/textarea.js +12 -12
- package/dist/ui/toggle.js +12 -12
- package/docs/recipes/install-astro.md +2 -2
- package/docs/recipes/install-next-app-router.md +2 -2
- package/docs/recipes/install-remix.md +2 -2
- package/docs/recipes/install-tanstack-start.md +2 -2
- package/docs/recipes/install-vite.md +2 -2
- package/docs/recipes/troubleshoot.md +26 -5
- package/llms.txt +1 -1
- package/mcp-manifest.json +24 -3
- package/package.json +39 -11
- package/skill/SKILL.md +1 -1
- package/skill/references/components.md +1 -1
- package/skill/references/setup-astro.md +2 -2
- package/skill/references/setup-next-app-router.md +2 -2
- package/skill/references/setup-remix.md +2 -2
- package/skill/references/setup-tanstack-start.md +2 -2
- package/skill/references/setup-vite.md +2 -2
- package/skill/references/troubleshoot.md +26 -5
- package/dist/_chunks/tiptap.js +0 -18809
- package/dist/_chunks/tiptap.js.map +0 -1
- package/dist/_chunks/vendor-utils.js +0 -1766
- package/dist/_chunks/vendor-utils.js.map +0 -1
- package/scripts/welcome.mjs +0 -312
package/dist/ui/checkbox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.js","names":[],"sources":["../../src/ui/checkbox.tsx"],"sourcesContent":["'use client'\n\nimport * as CheckboxPrimitive from '@primitives/react-checkbox'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { type FieldState, resolveFieldState } from './lib/field-state'\nimport { durations } from './lib/motion'\nimport { cn } from './lib/utils'\n\n/** Border + background tint per validation state (checked state uses accent regardless). */\nconst stateTintClasses: Record<Exclude<FieldState, 'default'>, string> = {\n error: 'border-error-7 bg-error-3',\n warning: 'border-warning-7 bg-warning-3',\n success: 'border-success-7 bg-success-3',\n}\n\n/**\n * Props for Checkbox — a Radix-powered accessible checkbox with error state styling and\n * an indeterminate state for \"select all\" row controls.\n *\n * **Indeterminate:** Pass `indeterminate={true}` to show the \"−\" dash (overrides `checked`).\n * This is correct for \"select all\" headers when only some rows are selected.\n *\n * **Controlled:** Use `checked` + `onCheckedChange` for controlled usage.\n * `onCheckedChange` receives `true | false | 'indeterminate'` from Radix.\n *\n * @example\n * // Basic controlled checkbox:\n * <Checkbox checked={agreed} onCheckedChange={(v) => setAgreed(v === true)} />\n *\n * @example\n * // Error state when required checkbox is not checked:\n * <Checkbox state={!termsAccepted ? 'error' : 'default'} checked={termsAccepted} onCheckedChange={(v) => setTerms(v === true)} />\n *\n * @example\n * // \"Select all\" checkbox with indeterminate state:\n * <Checkbox\n * indeterminate={someSelected && !allSelected}\n * checked={allSelected}\n * onCheckedChange={(v) => toggleAll(v === true)}\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {\n /** Validation/feedback state. `'error'` also sets `aria-invalid`. Inherited from `FormField` when omitted. */\n state?: FieldState\n indeterminate?: boolean\n /** Control size — `sm` (20px), `md` (24px, default, WCAG compliant), `lg` (28px). */\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst checkboxSizeClasses = {\n sm: 'h-5 w-5',\n md: 'h-6 w-6',\n lg: 'h-7 w-7',\n} as const\n\nconst checkboxIconClasses = {\n sm: 'h-[14px] w-[14px]',\n md: 'h-[18px] w-[18px]',\n lg: 'h-5 w-5',\n} as const\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(({ className, state: stateProp, indeterminate, size = 'md', checked, onCheckedChange, defaultChecked, ...props }, ref) => {\n // Track internal state so AnimatePresence works for both controlled & uncontrolled usage\n const isControlled = checked !== undefined\n const [internalChecked, setInternalChecked] = React.useState<boolean | 'indeterminate'>(\n indeterminate ? 'indeterminate' : (defaultChecked ? true : false),\n )\n\n const fieldCtx = useFormField()\n const state = resolveFieldState(stateProp, fieldCtx.state)\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n const actualChecked = indeterminate ? 'indeterminate' : (isControlled ? checked : internalChecked)\n const isActive = actualChecked === true || actualChecked === 'indeterminate'\n\n const handleCheckedChange = React.useCallback(\n (value: boolean | 'indeterminate') => {\n if (!isControlled) setInternalChecked(value)\n onCheckedChange?.(value)\n },\n [isControlled, onCheckedChange],\n )\n\n return (\n <CheckboxPrimitive.Root\n ref={ref}\n checked={isControlled || indeterminate ? actualChecked : undefined}\n defaultChecked={!isControlled && !indeterminate ? defaultChecked : undefined}\n onCheckedChange={handleCheckedChange}\n className={cn(\n 'touch-target peer flex items-center justify-center shrink-0 rounded-control-inner',\n checkboxSizeClasses[size],\n 'border border-surface-border-strong',\n 'bg-surface-raised-hover',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2',\n 'disabled:cursor-not-allowed disabled:opacity-action-disabled',\n 'data-[state=unchecked]:hover:border-accent-7 data-[state=unchecked]:hover:bg-surface-raised-active',\n 'data-[state=checked]:bg-accent-9 data-[state=checked]:border-accent-7 data-[state=checked]:text-accent-fg',\n 'data-[state=indeterminate]:bg-accent-9 data-[state=indeterminate]:border-accent-7 data-[state=indeterminate]:text-accent-fg',\n state && stateTintClasses[state],\n className,\n )}\n aria-invalid={state === 'error' || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n {...props}\n >\n <AnimatePresence>\n {isActive && (\n <CheckboxPrimitive.Indicator forceMount asChild>\n <motion.span\n className=\"flex items-center justify-center text-current\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: durations.fast01 }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n className={checkboxIconClasses[size]}\n >\n {actualChecked === 'indeterminate' ? (\n <motion.line\n x1=\"4\"\n y1=\"8\"\n x2=\"12\"\n y2=\"8\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n initial={{ pathLength: 0 }}\n animate={{ pathLength: 1 }}\n exit={{ pathLength: 0 }}\n transition={{ duration: durations.moderate02, ease: 'easeOut' }}\n />\n ) : (\n <motion.path\n d=\"M3.5 8.5l3 3 6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n initial={{ pathLength: 0 }}\n animate={{ pathLength: 1 }}\n exit={{ pathLength: 0 }}\n transition={{ duration: durations.moderate02, ease: 'easeOut' }}\n />\n )}\n </svg>\n </motion.span>\n </CheckboxPrimitive.Indicator>\n )}\n </AnimatePresence>\n </CheckboxPrimitive.Root>\n )\n})\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n"],"mappings":";;;;;;;;;;AAYA,IAAM,IAAmE;CACvE,OAAO;CACP,SAAS;CACT,SAAS;AACX,GAqCM,IAAsB;CAC1B,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GAEM,IAAsB;CAC1B,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GAEM,IAAW,EAAM,YAGpB,EAAE,cAAW,OAAO,GAAW,kBAAe,UAAO,MAAM,YAAS,oBAAiB,mBAAgB,GAAG,KAAS,MAAQ;CAE1H,IAAM,IAAe,MAAY,KAAA,GAC3B,CAAC,GAAiB,KAAsB,EAAM,SAClD,IAAgB,kBAAmB,GACrC,GAEM,IAAW,EAAa,GACxB,IAAQ,EAAkB,GAAW,EAAS,KAAK,GACnD,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAElD,IAAgB,IAAgB,kBAAmB,IAAe,IAAU,GAC5E,IAAW,MAAkB,MAAQ,MAAkB,iBAEvD,IAAsB,EAAM,aAC/B,MAAqC;EAEpC,AADK,KAAc,EAAmB,CAAK,GAC3C,IAAkB,CAAK;CACzB,GACA,CAAC,GAAc,CAAe,CAChC;CAEA,OACE,kBAAC,GAAD;EACO;EACL,SAAS,KAAgB,IAAgB,IAAgB,KAAA;EACzD,gBAAgB,CAAC,KAAgB,CAAC,IAAgB,IAAiB,KAAA;EACnE,iBAAiB;EACjB,WAAW,EACT,qFACA,EAAoB,IACpB,uCACA,2BACA,+DACA,6GACA,gEACA,sGACA,6GACA,+HACA,KAAS,EAAiB,IAC1B,CACF;EACA,gBAAc,MAAU,WAAW,KAAA;EACnC,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,GAAI;
|
|
1
|
+
{"version":3,"file":"checkbox.js","names":[],"sources":["../../src/ui/checkbox.tsx"],"sourcesContent":["'use client'\n\nimport * as CheckboxPrimitive from '@primitives/react-checkbox'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { type FieldState, resolveFieldState } from './lib/field-state'\nimport { durations } from './lib/motion'\nimport { cn } from './lib/utils'\n\n/** Border + background tint per validation state (checked state uses accent regardless). */\nconst stateTintClasses: Record<Exclude<FieldState, 'default'>, string> = {\n error: 'border-error-7 bg-error-3',\n warning: 'border-warning-7 bg-warning-3',\n success: 'border-success-7 bg-success-3',\n}\n\n/**\n * Props for Checkbox — a Radix-powered accessible checkbox with error state styling and\n * an indeterminate state for \"select all\" row controls.\n *\n * **Indeterminate:** Pass `indeterminate={true}` to show the \"−\" dash (overrides `checked`).\n * This is correct for \"select all\" headers when only some rows are selected.\n *\n * **Controlled:** Use `checked` + `onCheckedChange` for controlled usage.\n * `onCheckedChange` receives `true | false | 'indeterminate'` from Radix.\n *\n * @example\n * // Basic controlled checkbox:\n * <Checkbox checked={agreed} onCheckedChange={(v) => setAgreed(v === true)} />\n *\n * @example\n * // Error state when required checkbox is not checked:\n * <Checkbox state={!termsAccepted ? 'error' : 'default'} checked={termsAccepted} onCheckedChange={(v) => setTerms(v === true)} />\n *\n * @example\n * // \"Select all\" checkbox with indeterminate state:\n * <Checkbox\n * indeterminate={someSelected && !allSelected}\n * checked={allSelected}\n * onCheckedChange={(v) => toggleAll(v === true)}\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {\n /** Validation/feedback state. `'error'` also sets `aria-invalid`. Inherited from `FormField` when omitted. */\n state?: FieldState\n indeterminate?: boolean\n /** Control size — `sm` (20px), `md` (24px, default, WCAG compliant), `lg` (28px). */\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst checkboxSizeClasses = {\n sm: 'h-5 w-5',\n md: 'h-6 w-6',\n lg: 'h-7 w-7',\n} as const\n\nconst checkboxIconClasses = {\n sm: 'h-[14px] w-[14px]',\n md: 'h-[18px] w-[18px]',\n lg: 'h-5 w-5',\n} as const\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(({ className, state: stateProp, indeterminate, size = 'md', checked, onCheckedChange, defaultChecked, ...props }, ref) => {\n // Track internal state so AnimatePresence works for both controlled & uncontrolled usage\n const isControlled = checked !== undefined\n const [internalChecked, setInternalChecked] = React.useState<boolean | 'indeterminate'>(\n indeterminate ? 'indeterminate' : (defaultChecked ? true : false),\n )\n\n const fieldCtx = useFormField()\n const state = resolveFieldState(stateProp, fieldCtx.state)\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n const actualChecked = indeterminate ? 'indeterminate' : (isControlled ? checked : internalChecked)\n const isActive = actualChecked === true || actualChecked === 'indeterminate'\n\n const handleCheckedChange = React.useCallback(\n (value: boolean | 'indeterminate') => {\n if (!isControlled) setInternalChecked(value)\n onCheckedChange?.(value)\n },\n [isControlled, onCheckedChange],\n )\n\n return (\n <CheckboxPrimitive.Root\n ref={ref}\n checked={isControlled || indeterminate ? actualChecked : undefined}\n defaultChecked={!isControlled && !indeterminate ? defaultChecked : undefined}\n onCheckedChange={handleCheckedChange}\n className={cn(\n 'touch-target peer flex items-center justify-center shrink-0 rounded-control-inner',\n checkboxSizeClasses[size],\n 'border border-surface-border-strong',\n 'bg-surface-raised-hover',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2',\n 'disabled:cursor-not-allowed disabled:opacity-action-disabled',\n 'data-[state=unchecked]:hover:border-accent-7 data-[state=unchecked]:hover:bg-surface-raised-active',\n 'data-[state=checked]:bg-accent-9 data-[state=checked]:border-accent-7 data-[state=checked]:text-accent-fg',\n 'data-[state=indeterminate]:bg-accent-9 data-[state=indeterminate]:border-accent-7 data-[state=indeterminate]:text-accent-fg',\n state && stateTintClasses[state],\n className,\n )}\n aria-invalid={state === 'error' || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n {...props}\n // Explicit id wins; otherwise adopt FormField's inputId so <Label htmlFor>\n // resolves. Without this the label renders, points at nothing, and the\n // control has NO accessible name — a screen reader announces an unnamed\n // checkbox even though the consumer wrote FormField + Label correctly.\n id={props.id ?? fieldCtx.inputId}\n >\n <AnimatePresence>\n {isActive && (\n <CheckboxPrimitive.Indicator forceMount asChild>\n <motion.span\n className=\"flex items-center justify-center text-current\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: durations.fast01 }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n className={checkboxIconClasses[size]}\n >\n {actualChecked === 'indeterminate' ? (\n <motion.line\n x1=\"4\"\n y1=\"8\"\n x2=\"12\"\n y2=\"8\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n initial={{ pathLength: 0 }}\n animate={{ pathLength: 1 }}\n exit={{ pathLength: 0 }}\n transition={{ duration: durations.moderate02, ease: 'easeOut' }}\n />\n ) : (\n <motion.path\n d=\"M3.5 8.5l3 3 6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n initial={{ pathLength: 0 }}\n animate={{ pathLength: 1 }}\n exit={{ pathLength: 0 }}\n transition={{ duration: durations.moderate02, ease: 'easeOut' }}\n />\n )}\n </svg>\n </motion.span>\n </CheckboxPrimitive.Indicator>\n )}\n </AnimatePresence>\n </CheckboxPrimitive.Root>\n )\n})\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n"],"mappings":";;;;;;;;;;AAYA,IAAM,IAAmE;CACvE,OAAO;CACP,SAAS;CACT,SAAS;AACX,GAqCM,IAAsB;CAC1B,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GAEM,IAAsB;CAC1B,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GAEM,IAAW,EAAM,YAGpB,EAAE,cAAW,OAAO,GAAW,kBAAe,UAAO,MAAM,YAAS,oBAAiB,mBAAgB,GAAG,KAAS,MAAQ;CAE1H,IAAM,IAAe,MAAY,KAAA,GAC3B,CAAC,GAAiB,KAAsB,EAAM,SAClD,IAAgB,kBAAmB,GACrC,GAEM,IAAW,EAAa,GACxB,IAAQ,EAAkB,GAAW,EAAS,KAAK,GACnD,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAElD,IAAgB,IAAgB,kBAAmB,IAAe,IAAU,GAC5E,IAAW,MAAkB,MAAQ,MAAkB,iBAEvD,IAAsB,EAAM,aAC/B,MAAqC;EAEpC,AADK,KAAc,EAAmB,CAAK,GAC3C,IAAkB,CAAK;CACzB,GACA,CAAC,GAAc,CAAe,CAChC;CAEA,OACE,kBAAC,GAAD;EACO;EACL,SAAS,KAAgB,IAAgB,IAAgB,KAAA;EACzD,gBAAgB,CAAC,KAAgB,CAAC,IAAgB,IAAiB,KAAA;EACnE,iBAAiB;EACjB,WAAW,EACT,qFACA,EAAoB,IACpB,uCACA,2BACA,+DACA,6GACA,gEACA,sGACA,6GACA,+HACA,KAAS,EAAiB,IAC1B,CACF;EACA,gBAAc,MAAU,WAAW,KAAA;EACnC,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,GAAI;EAKJ,IAAI,EAAM,MAAM,EAAS;YAEzB,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,GAAD;GAA6B,YAAA;GAAW,SAAA;aACtC,kBAAC,EAAO,MAAR;IACE,WAAU;IACV,SAAS,EAAE,SAAS,EAAE;IACtB,SAAS,EAAE,SAAS,EAAE;IACtB,MAAM,EAAE,SAAS,EAAE;IACnB,YAAY,EAAE,UAAU,EAAU,OAAO;cAEzC,kBAAC,OAAD;KACE,OAAM;KACN,QAAO;KACP,SAAQ;KACR,MAAK;KACL,WAAW,EAAoB;eAE9B,MAAkB,kBACjB,kBAAC,EAAO,MAAR;MACE,IAAG;MACH,IAAG;MACH,IAAG;MACH,IAAG;MACH,QAAO;MACP,aAAY;MACZ,eAAc;MACd,SAAS,EAAE,YAAY,EAAE;MACzB,SAAS,EAAE,YAAY,EAAE;MACzB,MAAM,EAAE,YAAY,EAAE;MACtB,YAAY;OAAE,UAAU,EAAU;OAAY,MAAM;MAAU;KAC/D,CAAA,IAED,kBAAC,EAAO,MAAR;MACE,GAAE;MACF,QAAO;MACP,aAAY;MACZ,eAAc;MACd,gBAAe;MACf,MAAK;MACL,SAAS,EAAE,YAAY,EAAE;MACzB,SAAS,EAAE,YAAY,EAAE;MACzB,MAAM,EAAE,YAAY,EAAE;MACtB,YAAY;OAAE,UAAU,EAAU;OAAY,MAAM;MAAU;KAC/D,CAAA;IAEA,CAAA;GACM,CAAA;EACc,CAAA,EAEhB,CAAA;CACK,CAAA;AAE5B,CAAC;AACD,EAAS,cAAA,EAAqC"}
|
package/dist/ui/combobox.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { IconProvider as e } from "./icon-context.js";
|
|
3
3
|
import { springs as t, tweens as n } from "./lib/motion.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
4
|
+
import { cn as r } from "./lib/utils.js";
|
|
5
|
+
import { Icon as i } from "./icon.js";
|
|
6
|
+
import { useFormField as a } from "./form.js";
|
|
7
|
+
import { resolveFieldState as o } from "../_chunks/field-state.js";
|
|
8
|
+
import { normalizeIcon as s } from "../_chunks/normalize-icon.js";
|
|
9
|
+
import { Content2 as c, Portal as ee, Root2 as te, Trigger as ne } from "../_chunks/primitives/react-popover.js";
|
|
10
|
+
import * as l from "react";
|
|
11
|
+
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
12
|
+
import { IconCheck as p, IconChevronDown as m, IconSearch as h, IconX as re } from "@tabler/icons-react";
|
|
13
|
+
import { motion as ie } from "framer-motion";
|
|
14
|
+
import { cva as g } from "class-variance-authority";
|
|
15
15
|
//#region src/ui/combobox.tsx
|
|
16
|
-
var
|
|
16
|
+
var ae = {
|
|
17
17
|
error: "border-error-7",
|
|
18
18
|
warning: "border-warning-7",
|
|
19
19
|
success: "border-success-7"
|
|
20
|
-
},
|
|
20
|
+
}, _ = g([
|
|
21
21
|
"flex w-full items-center justify-between whitespace-nowrap rounded-control",
|
|
22
22
|
"border border-surface-border-strong bg-surface-raised-hover",
|
|
23
23
|
"transition-colors duration-fast-01 ease-productive-standard",
|
|
@@ -31,122 +31,122 @@ var oe = {
|
|
|
31
31
|
lg: "h-ds-lg text-body-md px-ds-05"
|
|
32
32
|
} },
|
|
33
33
|
defaultVariants: { size: "md" }
|
|
34
|
-
}),
|
|
34
|
+
}), v = {
|
|
35
35
|
xs: "xs",
|
|
36
36
|
sm: "sm",
|
|
37
37
|
md: "sm",
|
|
38
38
|
lg: "md"
|
|
39
|
-
},
|
|
39
|
+
}, y = {
|
|
40
40
|
xs: "px-ds-02 py-0 text-body-xs",
|
|
41
41
|
sm: "px-ds-02 py-0 text-body-xs",
|
|
42
42
|
md: "px-ds-03 py-[1px] text-body-sm",
|
|
43
43
|
lg: "px-ds-03 py-[2px] text-body-sm"
|
|
44
|
-
},
|
|
44
|
+
}, b = {
|
|
45
45
|
xs: "text-body-xs",
|
|
46
46
|
sm: "text-body-xs",
|
|
47
47
|
md: "text-body-sm",
|
|
48
48
|
lg: "text-body-sm"
|
|
49
|
-
},
|
|
50
|
-
let
|
|
51
|
-
|
|
49
|
+
}, x = 2, S = 36, C = l.forwardRef(({ options: g, value: C, onValueChange: w, placeholder: T = "Select...", searchPlaceholder: oe = "Search...", emptyMessage: se = "No results found", multiple: E = !1, disabled: D = !1, className: ce, triggerClassName: le, maxVisible: ue = 6, renderOption: O, accessibleLabel: k, id: A, size: j = "md", state: M, ...N }, P) => {
|
|
50
|
+
let F = j ?? "md", [I, L] = l.useState(!1), [R, z] = l.useState(""), [B, V] = l.useState(-1), H = l.useRef(null), U = l.useRef(null), W = l.useId(), G = l.useId(), K = l.useMemo(() => C == null ? [] : Array.isArray(C) ? C : [C], [C]), q = l.useMemo(() => R ? g.filter((e) => e.label.toLowerCase().includes(R.toLowerCase())) : g, [g, R]), de = l.useCallback((e) => K.includes(e), [K]), J = l.useCallback((e) => {
|
|
51
|
+
E ? w(K.includes(e) ? K.filter((t) => t !== e) : [...K, e]) : (w(e), L(!1));
|
|
52
52
|
}, [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
]),
|
|
57
|
-
e.stopPropagation(), e.preventDefault(),
|
|
58
|
-
}, [
|
|
59
|
-
|
|
60
|
-
}, [
|
|
61
|
-
|
|
62
|
-
if (
|
|
53
|
+
E,
|
|
54
|
+
K,
|
|
55
|
+
w
|
|
56
|
+
]), fe = l.useCallback((e, t) => {
|
|
57
|
+
e.stopPropagation(), e.preventDefault(), w(K.filter((e) => e !== t));
|
|
58
|
+
}, [K, w]), Y = l.useCallback((e) => {
|
|
59
|
+
D || (L(e), e || (z(""), V(-1)));
|
|
60
|
+
}, [D]);
|
|
61
|
+
l.useEffect(() => {
|
|
62
|
+
if (I) {
|
|
63
63
|
let e = setTimeout(() => {
|
|
64
|
-
|
|
64
|
+
H.current?.focus();
|
|
65
65
|
}, 0);
|
|
66
66
|
return () => clearTimeout(e);
|
|
67
67
|
}
|
|
68
|
-
}, [
|
|
69
|
-
let
|
|
70
|
-
let n =
|
|
68
|
+
}, [I]);
|
|
69
|
+
let X = l.useCallback((e, t) => {
|
|
70
|
+
let n = q.length;
|
|
71
71
|
if (n === 0) return -1;
|
|
72
72
|
let r = e + t;
|
|
73
73
|
for (; r >= 0 && r < n;) {
|
|
74
|
-
if (!
|
|
74
|
+
if (!q[r].disabled) return r;
|
|
75
75
|
r += t;
|
|
76
76
|
}
|
|
77
77
|
return e;
|
|
78
|
-
}, [
|
|
78
|
+
}, [q]), pe = l.useCallback((e) => {
|
|
79
79
|
switch (e.key) {
|
|
80
80
|
case "ArrowDown": {
|
|
81
81
|
e.preventDefault();
|
|
82
|
-
let t =
|
|
83
|
-
|
|
82
|
+
let t = X(B, 1);
|
|
83
|
+
V(t);
|
|
84
84
|
break;
|
|
85
85
|
}
|
|
86
86
|
case "ArrowUp": {
|
|
87
87
|
e.preventDefault();
|
|
88
|
-
let t =
|
|
89
|
-
|
|
88
|
+
let t = X(B, -1);
|
|
89
|
+
V(t);
|
|
90
90
|
break;
|
|
91
91
|
}
|
|
92
92
|
case "Home": {
|
|
93
93
|
e.preventDefault();
|
|
94
|
-
let t =
|
|
95
|
-
|
|
94
|
+
let t = q.findIndex((e) => !e.disabled);
|
|
95
|
+
V(t);
|
|
96
96
|
break;
|
|
97
97
|
}
|
|
98
98
|
case "End": {
|
|
99
99
|
e.preventDefault();
|
|
100
100
|
let t = -1;
|
|
101
|
-
for (let e =
|
|
101
|
+
for (let e = q.length - 1; e >= 0; e--) if (!q[e].disabled) {
|
|
102
102
|
t = e;
|
|
103
103
|
break;
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
V(t);
|
|
106
106
|
break;
|
|
107
107
|
}
|
|
108
108
|
case "Enter":
|
|
109
|
-
e.preventDefault(),
|
|
109
|
+
e.preventDefault(), B >= 0 && B < q.length && !q[B].disabled && J(q[B].value);
|
|
110
110
|
break;
|
|
111
111
|
case "Escape":
|
|
112
|
-
e.preventDefault(),
|
|
112
|
+
e.preventDefault(), L(!1), z(""), V(-1);
|
|
113
113
|
break;
|
|
114
114
|
}
|
|
115
115
|
}, [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
B,
|
|
117
|
+
q,
|
|
118
|
+
X,
|
|
119
|
+
J
|
|
120
120
|
]);
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
let e =
|
|
121
|
+
l.useEffect(() => {
|
|
122
|
+
if (B >= 0 && U.current) {
|
|
123
|
+
let e = U.current.children[B];
|
|
124
124
|
e && e.scrollIntoView?.({ block: "nearest" });
|
|
125
125
|
}
|
|
126
|
-
}, [
|
|
127
|
-
let
|
|
128
|
-
if (
|
|
129
|
-
let e =
|
|
130
|
-
return /* @__PURE__ */
|
|
126
|
+
}, [B]);
|
|
127
|
+
let me = l.useCallback(() => K.length === 0 ? null : g.find((e) => e.value === K[0])?.label ?? null, [K, g]), Z = v[F], he = y[F], ge = b[F], _e = () => {
|
|
128
|
+
if (E && K.length > 0) {
|
|
129
|
+
let e = K.slice(0, x), t = K.length - x;
|
|
130
|
+
return /* @__PURE__ */ f("span", {
|
|
131
131
|
className: "flex flex-1 flex-wrap items-center gap-ds-02 overflow-hidden",
|
|
132
132
|
children: [e.map((e) => {
|
|
133
|
-
let t =
|
|
134
|
-
return t ? /* @__PURE__ */
|
|
135
|
-
className:
|
|
136
|
-
children: [t.label, /* @__PURE__ */
|
|
133
|
+
let t = g.find((t) => t.value === e);
|
|
134
|
+
return t ? /* @__PURE__ */ f("span", {
|
|
135
|
+
className: r("inline-flex items-center gap-ds-01 rounded-control-inner bg-accent-2", he),
|
|
136
|
+
children: [t.label, /* @__PURE__ */ d("button", {
|
|
137
137
|
type: "button",
|
|
138
138
|
className: "inline-flex items-center justify-center rounded-pill outline-hidden hover:bg-surface-raised-hover transition-colors duration-fast-01 ease-productive-standard",
|
|
139
|
-
onClick: (t) =>
|
|
139
|
+
onClick: (t) => fe(t, e),
|
|
140
140
|
"aria-label": `Remove ${t.label}`,
|
|
141
141
|
tabIndex: -1,
|
|
142
|
-
children: /* @__PURE__ */
|
|
143
|
-
icon:
|
|
142
|
+
children: /* @__PURE__ */ d(i, {
|
|
143
|
+
icon: re,
|
|
144
144
|
size: Z
|
|
145
145
|
})
|
|
146
146
|
})]
|
|
147
147
|
}, e) : null;
|
|
148
|
-
}), t > 0 && /* @__PURE__ */
|
|
149
|
-
className:
|
|
148
|
+
}), t > 0 && /* @__PURE__ */ f("span", {
|
|
149
|
+
className: r("text-surface-fg-muted", ge),
|
|
150
150
|
children: [
|
|
151
151
|
"+",
|
|
152
152
|
t,
|
|
@@ -155,60 +155,60 @@ var oe = {
|
|
|
155
155
|
})]
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
|
-
if (!
|
|
159
|
-
let e =
|
|
160
|
-
if (e) return /* @__PURE__ */
|
|
158
|
+
if (!E && K.length === 1) {
|
|
159
|
+
let e = me();
|
|
160
|
+
if (e) return /* @__PURE__ */ d("span", {
|
|
161
161
|
className: "min-w-0 flex-1 truncate text-left",
|
|
162
162
|
children: e
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
-
return /* @__PURE__ */
|
|
165
|
+
return /* @__PURE__ */ d("span", {
|
|
166
166
|
className: "min-w-0 flex-1 truncate text-left text-surface-fg-subtle",
|
|
167
|
-
children:
|
|
167
|
+
children: T
|
|
168
168
|
});
|
|
169
|
-
}, Q =
|
|
170
|
-
return /* @__PURE__ */
|
|
171
|
-
open:
|
|
172
|
-
onOpenChange:
|
|
173
|
-
children: /* @__PURE__ */
|
|
174
|
-
className:
|
|
175
|
-
...
|
|
176
|
-
children: [/* @__PURE__ */
|
|
169
|
+
}, Q = a(), $ = o(M, Q.state), ve = $ === "error", ye = Q.helperTextId, be = Q.required;
|
|
170
|
+
return /* @__PURE__ */ d(te, {
|
|
171
|
+
open: I,
|
|
172
|
+
onOpenChange: Y,
|
|
173
|
+
children: /* @__PURE__ */ f("div", {
|
|
174
|
+
className: r("relative", ce),
|
|
175
|
+
...N,
|
|
176
|
+
children: [/* @__PURE__ */ d(ne, {
|
|
177
177
|
asChild: !0,
|
|
178
|
-
disabled:
|
|
179
|
-
children: /* @__PURE__ */
|
|
180
|
-
ref:
|
|
178
|
+
disabled: D,
|
|
179
|
+
children: /* @__PURE__ */ f("div", {
|
|
180
|
+
ref: P,
|
|
181
181
|
role: "combobox",
|
|
182
|
-
tabIndex:
|
|
183
|
-
"data-disabled":
|
|
184
|
-
id:
|
|
185
|
-
"aria-expanded":
|
|
186
|
-
"aria-controls":
|
|
182
|
+
tabIndex: D ? -1 : 0,
|
|
183
|
+
"data-disabled": D || void 0,
|
|
184
|
+
id: A,
|
|
185
|
+
"aria-expanded": I,
|
|
186
|
+
"aria-controls": G,
|
|
187
187
|
"aria-haspopup": "listbox",
|
|
188
|
-
"aria-disabled":
|
|
189
|
-
"aria-labelledby":
|
|
190
|
-
"aria-label":
|
|
188
|
+
"aria-disabled": D || void 0,
|
|
189
|
+
"aria-labelledby": k ? void 0 : Q.labelId,
|
|
190
|
+
"aria-label": k ?? (Q.labelId ? void 0 : T),
|
|
191
191
|
"aria-invalid": ve || void 0,
|
|
192
192
|
"aria-describedby": ye,
|
|
193
193
|
"aria-required": be || void 0,
|
|
194
194
|
onKeyDown: (e) => {
|
|
195
|
-
|
|
195
|
+
D || (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") && (e.preventDefault(), Y(!0));
|
|
196
196
|
},
|
|
197
|
-
className:
|
|
198
|
-
children: [_e(), /* @__PURE__ */
|
|
199
|
-
icon:
|
|
197
|
+
className: r(_({ size: F }), "cursor-pointer", I && "border-accent-7", $ && ae[$], le),
|
|
198
|
+
children: [_e(), /* @__PURE__ */ d(i, {
|
|
199
|
+
icon: m,
|
|
200
200
|
size: Z,
|
|
201
|
-
className:
|
|
201
|
+
className: r("ml-ds-02 shrink-0 opacity-50 transition-transform duration-fast-01 ease-productive-standard", I && "rotate-180")
|
|
202
202
|
})]
|
|
203
203
|
})
|
|
204
|
-
}), /* @__PURE__ */
|
|
204
|
+
}), /* @__PURE__ */ d(ee, { children: /* @__PURE__ */ d(c, {
|
|
205
205
|
asChild: !0,
|
|
206
206
|
sideOffset: 4,
|
|
207
207
|
align: "start",
|
|
208
208
|
onOpenAutoFocus: (e) => {
|
|
209
|
-
e.preventDefault(),
|
|
209
|
+
e.preventDefault(), H.current?.focus();
|
|
210
210
|
},
|
|
211
|
-
children: /* @__PURE__ */
|
|
211
|
+
children: /* @__PURE__ */ f(ie.div, {
|
|
212
212
|
initial: {
|
|
213
213
|
opacity: 0,
|
|
214
214
|
scale: .95
|
|
@@ -221,72 +221,72 @@ var oe = {
|
|
|
221
221
|
...t.snappy,
|
|
222
222
|
opacity: n.fade
|
|
223
223
|
},
|
|
224
|
-
className:
|
|
225
|
-
children: [/* @__PURE__ */
|
|
224
|
+
className: r("z-popover w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-overlay bg-surface-overlay shadow-floating"),
|
|
225
|
+
children: [/* @__PURE__ */ f("div", {
|
|
226
226
|
className: "flex items-center gap-ds-02 border-b border-surface-border px-ds-04",
|
|
227
|
-
children: [/* @__PURE__ */
|
|
228
|
-
icon:
|
|
227
|
+
children: [/* @__PURE__ */ d(i, {
|
|
228
|
+
icon: h,
|
|
229
229
|
size: "sm",
|
|
230
230
|
className: "shrink-0 text-surface-fg-subtle"
|
|
231
|
-
}), /* @__PURE__ */
|
|
232
|
-
ref:
|
|
231
|
+
}), /* @__PURE__ */ d("input", {
|
|
232
|
+
ref: H,
|
|
233
233
|
type: "text",
|
|
234
234
|
className: "flex-1 bg-transparent py-ds-03 text-body-md outline-hidden placeholder:text-surface-fg-subtle",
|
|
235
|
-
placeholder:
|
|
236
|
-
value:
|
|
235
|
+
placeholder: oe,
|
|
236
|
+
value: R,
|
|
237
237
|
onChange: (e) => {
|
|
238
|
-
|
|
238
|
+
z(e.target.value), V(-1);
|
|
239
239
|
},
|
|
240
|
-
onKeyDown:
|
|
240
|
+
onKeyDown: pe,
|
|
241
241
|
"aria-autocomplete": "list",
|
|
242
|
-
"aria-controls":
|
|
243
|
-
"aria-activedescendant":
|
|
242
|
+
"aria-controls": G,
|
|
243
|
+
"aria-activedescendant": B >= 0 ? `${W}-option-${B}` : void 0,
|
|
244
244
|
"aria-label": "Search options"
|
|
245
245
|
})]
|
|
246
|
-
}),
|
|
246
|
+
}), q.length === 0 ? /* @__PURE__ */ d("div", {
|
|
247
247
|
className: "px-ds-04 py-ds-05 text-center text-body-md text-surface-fg-subtle",
|
|
248
|
-
children:
|
|
249
|
-
}) : /* @__PURE__ */
|
|
250
|
-
ref:
|
|
251
|
-
id:
|
|
248
|
+
children: se
|
|
249
|
+
}) : /* @__PURE__ */ d("ul", {
|
|
250
|
+
ref: U,
|
|
251
|
+
id: G,
|
|
252
252
|
role: "listbox",
|
|
253
|
-
"aria-multiselectable":
|
|
253
|
+
"aria-multiselectable": E || void 0,
|
|
254
254
|
className: "overflow-auto p-ds-02",
|
|
255
|
-
style: { maxHeight: `${
|
|
256
|
-
children:
|
|
257
|
-
let
|
|
258
|
-
return /* @__PURE__ */
|
|
259
|
-
id: `${
|
|
255
|
+
style: { maxHeight: `${ue * S}px` },
|
|
256
|
+
children: q.map((t, n) => {
|
|
257
|
+
let a = de(t.value);
|
|
258
|
+
return /* @__PURE__ */ f("li", {
|
|
259
|
+
id: `${W}-option-${n}`,
|
|
260
260
|
role: "option",
|
|
261
|
-
"aria-selected":
|
|
261
|
+
"aria-selected": a,
|
|
262
262
|
"aria-disabled": t.disabled || void 0,
|
|
263
|
-
className:
|
|
263
|
+
className: r("relative flex cursor-pointer select-none items-center gap-ds-03 rounded-control px-ds-04 py-ds-03 text-body-md outline-hidden", "transition-colors duration-fast-01 ease-productive-standard", B === n && "bg-accent-2", a && "text-accent-11", t.disabled && "pointer-events-none opacity-action-disabled"),
|
|
264
264
|
onClick: () => {
|
|
265
|
-
t.disabled ||
|
|
265
|
+
t.disabled || J(t.value);
|
|
266
266
|
},
|
|
267
267
|
onKeyDown: (e) => {
|
|
268
|
-
!t.disabled && (e.key === "Enter" || e.key === " ") && (e.preventDefault(),
|
|
268
|
+
!t.disabled && (e.key === "Enter" || e.key === " ") && (e.preventDefault(), J(t.value));
|
|
269
269
|
},
|
|
270
270
|
onMouseEnter: () => {
|
|
271
|
-
t.disabled ||
|
|
271
|
+
t.disabled || V(n);
|
|
272
272
|
},
|
|
273
273
|
children: [
|
|
274
|
-
t.icon && /* @__PURE__ */
|
|
274
|
+
t.icon && /* @__PURE__ */ d("span", {
|
|
275
275
|
className: "flex h-ico-sm w-ico-sm items-center justify-center shrink-0",
|
|
276
|
-
children: /* @__PURE__ */
|
|
276
|
+
children: /* @__PURE__ */ d(e, {
|
|
277
277
|
size: Z,
|
|
278
|
-
children:
|
|
278
|
+
children: s(t.icon)
|
|
279
279
|
})
|
|
280
280
|
}),
|
|
281
|
-
/* @__PURE__ */
|
|
281
|
+
/* @__PURE__ */ d("span", {
|
|
282
282
|
className: "flex flex-1 flex-col",
|
|
283
|
-
children:
|
|
283
|
+
children: O ? O(t, a) : /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d("span", { children: t.label }), t.description && /* @__PURE__ */ d("span", {
|
|
284
284
|
className: "text-body-sm text-surface-fg-muted",
|
|
285
285
|
children: t.description
|
|
286
286
|
})] })
|
|
287
287
|
}),
|
|
288
|
-
|
|
289
|
-
icon:
|
|
288
|
+
a && /* @__PURE__ */ d(i, {
|
|
289
|
+
icon: p,
|
|
290
290
|
size: "sm",
|
|
291
291
|
className: "shrink-0"
|
|
292
292
|
})
|
|
@@ -299,6 +299,6 @@ var oe = {
|
|
|
299
299
|
})
|
|
300
300
|
});
|
|
301
301
|
});
|
|
302
|
-
|
|
302
|
+
C.displayName = "Combobox";
|
|
303
303
|
//#endregion
|
|
304
|
-
export {
|
|
304
|
+
export { C as Combobox, _ as comboboxTriggerVariants };
|
package/dist/ui/combobox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combobox.js","names":[],"sources":["../../src/ui/combobox.tsx"],"sourcesContent":["'use client'\n\nimport * as PopoverPrimitive from '@primitives/react-popover'\nimport { IconCheck, IconChevronDown, IconSearch, IconX } from '@tabler/icons-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { Icon } from './icon'\nimport { IconProvider, type IconSize } from './icon-context'\nimport { type FieldState, resolveFieldState } from './lib/field-state'\nimport type { IconInput } from './lib/icon-input'\nimport { springs, tweens } from './lib/motion'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\n\n/** Border tint per validation state, applied to the trigger. */\nconst stateBorderClasses: Record<Exclude<FieldState, 'default'>, string> = {\n error: 'border-error-7',\n warning: 'border-warning-7',\n success: 'border-success-7',\n}\n\nexport const comboboxTriggerVariants = cva(\n [\n 'flex w-full items-center justify-between whitespace-nowrap rounded-control',\n 'border border-surface-border-strong bg-surface-raised-hover',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 focus-visible:border-accent-7',\n // Trigger is a div[role=combobox] (so chip remove-buttons in multi-select\n // aren't nested in a <button>), so disabled styling keys off data-disabled,\n // not the :disabled pseudo-class.\n 'data-[disabled]:cursor-not-allowed data-[disabled]:opacity-action-disabled',\n ],\n {\n variants: {\n size: {\n xs: 'h-ds-xs-plus text-body-sm px-ds-02',\n sm: 'h-ds-sm text-body-sm px-ds-03',\n md: 'h-ds-md text-body-md px-ds-04',\n lg: 'h-ds-lg text-body-md px-ds-05',\n },\n },\n defaultVariants: { size: 'md' },\n },\n)\n\nexport type ComboboxSize = NonNullable<VariantProps<typeof comboboxTriggerVariants>['size']>\n\n/** Maps combobox size to Icon component size for chevron / check icons */\nconst iconSizeMap: Record<NonNullable<ComboboxSize>, IconSize> = {\n xs: 'xs',\n sm: 'sm',\n md: 'sm',\n lg: 'md',\n}\n\n/** Maps combobox size to pill text + padding classes */\nconst pillSizeMap: Record<NonNullable<ComboboxSize>, string> = {\n xs: 'px-ds-02 py-0 text-body-xs',\n sm: 'px-ds-02 py-0 text-body-xs',\n md: 'px-ds-03 py-[1px] text-body-sm',\n lg: 'px-ds-03 py-[2px] text-body-sm',\n}\n\n/** Maps combobox size to overflow text classes */\nconst overflowTextMap: Record<NonNullable<ComboboxSize>, string> = {\n xs: 'text-body-xs',\n sm: 'text-body-xs',\n md: 'text-body-sm',\n lg: 'text-body-sm',\n}\n\n/**\n * Option shape for a Combobox dropdown item.\n * `value` must be unique across all options — it is the key used in selection state.\n */\nexport interface ComboboxOption {\n value: string\n label: string\n description?: string\n icon?: IconInput\n disabled?: boolean\n}\n\n/**\n * Props for Combobox — a searchable single or multi-select dropdown with built-in keyboard\n * navigation, pill overflow (\"+ N more\"), and an optional custom option renderer.\n *\n * **Single vs multi:** `multiple={false}` (default) — `value` is a `string` and `onValueChange`\n * receives a `string`. When `multiple={true}`, `value` is `string[]`, `onValueChange` receives\n * `string[]`, and selected items appear as dismissible pills in the trigger.\n *\n * The props form a **discriminated union** on `multiple` — TypeScript will narrow `value` and\n * `onValueChange` automatically, so no manual casts are needed.\n *\n * **Custom rendering:** Use `renderOption` to return custom JSX per option (e.g. avatars, badges).\n *\n * @example\n * // Single-select country picker:\n * <Combobox\n * options={[{ value: 'in', label: 'India' }, { value: 'us', label: 'United States' }]}\n * value={country}\n * onValueChange={(v) => setCountry(v)}\n * placeholder=\"Select country\"\n * />\n *\n * @example\n * // Multi-select tag picker with pill display:\n * <Combobox\n * multiple\n * options={tagOptions}\n * value={selectedTags}\n * onValueChange={(v) => setSelectedTags(v)}\n * placeholder=\"Select tags...\"\n * />\n *\n * @example\n * // Custom option renderer (user avatars in assignee picker):\n * <Combobox\n * options={users.map(u => ({ value: u.id, label: u.name }))}\n * value={assigneeId}\n * onValueChange={(v) => setAssigneeId(v)}\n * renderOption={(option, selected) => (\n * <span className=\"flex items-center gap-ds-03\">\n * <Avatar size=\"xs\"><AvatarFallback>{option.label[0]}</AvatarFallback></Avatar>\n * {option.label}\n * </span>\n * )}\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\ninterface ComboboxBaseProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Available options shown in the dropdown. */\n options: ComboboxOption[]\n /** Placeholder shown in the trigger when no value is selected. */\n placeholder?: string\n /** Placeholder for the search input inside the dropdown. */\n searchPlaceholder?: string\n /** Message shown when the search yields no results. */\n emptyMessage?: string\n disabled?: boolean\n triggerClassName?: string\n /** Max visible items in the dropdown before scroll (default 6). */\n maxVisible?: number\n /** Custom renderer for each option row. Receives the option and whether it's currently selected. */\n renderOption?: (option: ComboboxOption, selected: boolean) => React.ReactNode\n /** Accessible label for the trigger button. Falls back to `placeholder` if not provided. */\n accessibleLabel?: string\n /** Size of the trigger. Controls height, text size, padding, and pill sizing. */\n size?: ComboboxSize\n /** Validation/feedback state. `'error'` also sets `aria-invalid`. Inherited from `FormField` when omitted. */\n state?: FieldState\n}\n\ninterface ComboboxSingleProps extends ComboboxBaseProps {\n multiple?: false\n value?: string\n onValueChange: (value: string) => void\n}\n\ninterface ComboboxMultipleProps extends ComboboxBaseProps {\n multiple: true\n value?: string[]\n onValueChange: (value: string[]) => void\n}\n\nexport type ComboboxProps = ComboboxSingleProps | ComboboxMultipleProps\n\n/** Max pills shown in the trigger before \"+N more\" overflow */\nconst MAX_VISIBLE_PILLS = 2\n\n/** Approximate height of a single option item in px */\nconst ITEM_HEIGHT_PX = 36\n\nconst Combobox = React.forwardRef<HTMLDivElement, ComboboxProps>(\n (\n {\n options,\n value,\n onValueChange,\n placeholder = 'Select...',\n searchPlaceholder = 'Search...',\n emptyMessage = 'No results found',\n multiple = false,\n disabled = false,\n className,\n triggerClassName,\n maxVisible = 6,\n renderOption,\n accessibleLabel,\n id: externalId,\n size: sizeProp = 'md',\n state: stateProp,\n ...rest\n },\n ref,\n ) => {\n const size = sizeProp ?? 'md'\n const [open, setOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [highlightedIndex, setHighlightedIndex] = React.useState(-1)\n const searchInputRef = React.useRef<HTMLInputElement>(null)\n const listRef = React.useRef<HTMLUListElement>(null)\n const optionIdPrefix = React.useId()\n const listboxId = React.useId()\n\n const selectedValues = React.useMemo<string[]>(() => {\n if (value === undefined || value === null) return []\n if (Array.isArray(value)) return value\n return [value]\n }, [value])\n\n const filteredOptions = React.useMemo(\n () =>\n search\n ? options.filter((o) =>\n o.label.toLowerCase().includes(search.toLowerCase()),\n )\n : options,\n [options, search],\n )\n\n const isSelected = React.useCallback(\n (optionValue: string) => selectedValues.includes(optionValue),\n [selectedValues],\n )\n\n const handleSelect = React.useCallback(\n (optionValue: string) => {\n if (multiple) {\n const newValue = selectedValues.includes(optionValue)\n ? selectedValues.filter((v) => v !== optionValue)\n : [...selectedValues, optionValue]\n ;(onValueChange as (value: string[]) => void)(newValue)\n } else {\n ;(onValueChange as (value: string) => void)(optionValue)\n setOpen(false)\n }\n },\n [multiple, selectedValues, onValueChange],\n )\n\n const handleRemovePill = React.useCallback(\n (e: React.SyntheticEvent, optionValue: string) => {\n e.stopPropagation()\n e.preventDefault()\n const newValue = selectedValues.filter((v) => v !== optionValue)\n ;(onValueChange as (value: string[]) => void)(newValue)\n },\n [selectedValues, onValueChange],\n )\n\n const handleOpenChange = React.useCallback(\n (nextOpen: boolean) => {\n if (disabled) return\n setOpen(nextOpen)\n if (!nextOpen) {\n setSearch('')\n setHighlightedIndex(-1)\n }\n },\n [disabled],\n )\n\n // Auto-focus search input when popover opens\n React.useEffect(() => {\n if (open) {\n // Use a small timeout to allow the popover to render\n const timer = setTimeout(() => {\n searchInputRef.current?.focus()\n }, 0)\n return () => clearTimeout(timer)\n }\n }, [open])\n\n const findNextEnabledIndex = React.useCallback(\n (currentIndex: number, direction: 1 | -1): number => {\n const len = filteredOptions.length\n if (len === 0) return -1\n\n let nextIndex = currentIndex + direction\n while (nextIndex >= 0 && nextIndex < len) {\n if (!filteredOptions[nextIndex].disabled) return nextIndex\n nextIndex += direction\n }\n return currentIndex\n },\n [filteredOptions],\n )\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault()\n const nextIdx = findNextEnabledIndex(highlightedIndex, 1)\n setHighlightedIndex(nextIdx)\n break\n }\n case 'ArrowUp': {\n e.preventDefault()\n const prevIdx = findNextEnabledIndex(highlightedIndex, -1)\n setHighlightedIndex(prevIdx)\n break\n }\n case 'Home': {\n e.preventDefault()\n const firstEnabled = filteredOptions.findIndex((o) => !o.disabled)\n setHighlightedIndex(firstEnabled)\n break\n }\n case 'End': {\n e.preventDefault()\n let lastEnabled = -1\n for (let i = filteredOptions.length - 1; i >= 0; i--) {\n if (!filteredOptions[i].disabled) {\n lastEnabled = i\n break\n }\n }\n setHighlightedIndex(lastEnabled)\n break\n }\n case 'Enter': {\n e.preventDefault()\n if (\n highlightedIndex >= 0 &&\n highlightedIndex < filteredOptions.length &&\n !filteredOptions[highlightedIndex].disabled\n ) {\n handleSelect(filteredOptions[highlightedIndex].value)\n }\n break\n }\n case 'Escape': {\n e.preventDefault()\n setOpen(false)\n setSearch('')\n setHighlightedIndex(-1)\n break\n }\n }\n },\n [highlightedIndex, filteredOptions, findNextEnabledIndex, handleSelect],\n )\n\n // Scroll highlighted option into view\n React.useEffect(() => {\n if (highlightedIndex >= 0 && listRef.current) {\n const optionEl = listRef.current.children[highlightedIndex] as HTMLElement\n if (optionEl) {\n optionEl.scrollIntoView?.({ block: 'nearest' })\n }\n }\n }, [highlightedIndex])\n\n const getSelectedLabel = React.useCallback(() => {\n if (selectedValues.length === 0) return null\n const option = options.find((o) => o.value === selectedValues[0])\n return option?.label ?? null\n }, [selectedValues, options])\n\n const resolvedIconSize = iconSizeMap[size]\n const resolvedPillClasses = pillSizeMap[size]\n const resolvedOverflowText = overflowTextMap[size]\n\n const renderTriggerContent = () => {\n if (multiple && selectedValues.length > 0) {\n const visiblePills = selectedValues.slice(0, MAX_VISIBLE_PILLS)\n const remaining = selectedValues.length - MAX_VISIBLE_PILLS\n\n return (\n <span className=\"flex flex-1 flex-wrap items-center gap-ds-02 overflow-hidden\">\n {visiblePills.map((val) => {\n const option = options.find((o) => o.value === val)\n if (!option) return null\n return (\n <span\n key={val}\n className={cn('inline-flex items-center gap-ds-01 rounded-control-inner bg-accent-2', resolvedPillClasses)}\n >\n {option.label}\n <button\n type=\"button\"\n className=\"inline-flex items-center justify-center rounded-pill outline-hidden hover:bg-surface-raised-hover transition-colors duration-fast-01 ease-productive-standard\"\n onClick={(e) => handleRemovePill(e, val)}\n aria-label={`Remove ${option.label}`}\n tabIndex={-1}\n >\n <Icon icon={IconX} size={resolvedIconSize} />\n </button>\n </span>\n )\n })}\n {remaining > 0 && (\n <span className={cn('text-surface-fg-muted', resolvedOverflowText)}>\n +{remaining} more\n </span>\n )}\n </span>\n )\n }\n\n if (!multiple && selectedValues.length === 1) {\n const label = getSelectedLabel()\n if (label) {\n return <span className=\"min-w-0 flex-1 truncate text-left\">{label}</span>\n }\n }\n\n return (\n <span className=\"min-w-0 flex-1 truncate text-left text-surface-fg-subtle\">\n {placeholder}\n </span>\n )\n }\n\n const fieldCtx = useFormField()\n const state = resolveFieldState(stateProp, fieldCtx.state)\n const isError = state === 'error'\n const ariaDescribedBy = fieldCtx.helperTextId\n const ariaRequired = fieldCtx.required\n\n return (\n <PopoverPrimitive.Root open={open} onOpenChange={handleOpenChange}>\n <div className={cn('relative', className)} {...rest}>\n <PopoverPrimitive.Trigger asChild disabled={disabled}>\n {/* role=combobox on a <div>, NOT a <button> — a native button can't\n legally contain the chip remove-<button>s rendered in multi-select\n (invalid HTML + a11y break). This is the W3C select-only combobox\n pattern. Radix Trigger wires click/toggle; we add the keyboard-open\n (Enter/Space/ArrowDown) a real button would have given for free. */}\n <div\n ref={ref}\n role=\"combobox\"\n tabIndex={disabled ? -1 : 0}\n data-disabled={disabled || undefined}\n id={externalId}\n aria-expanded={open}\n aria-controls={listboxId}\n aria-haspopup=\"listbox\"\n aria-disabled={disabled || undefined}\n // The trigger is a div[role=combobox] — non-labellable, so `<Label htmlFor>`\n // can't target it. Inside a FormField, adopt the label's id via\n // aria-labelledby; explicit accessibleLabel wins; placeholder is the\n // standalone fallback.\n aria-labelledby={accessibleLabel ? undefined : fieldCtx.labelId}\n aria-label={accessibleLabel ?? (fieldCtx.labelId ? undefined : placeholder)}\n aria-invalid={isError || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n onKeyDown={(e) => {\n if (disabled) return\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault()\n handleOpenChange(true)\n }\n }}\n className={cn(\n comboboxTriggerVariants({ size }),\n 'cursor-pointer',\n open && 'border-accent-7',\n state && stateBorderClasses[state],\n triggerClassName,\n )}\n >\n {renderTriggerContent()}\n <Icon icon={IconChevronDown} size={resolvedIconSize} className={cn(\"ml-ds-02 shrink-0 opacity-50 transition-transform duration-fast-01 ease-productive-standard\", open && 'rotate-180')} />\n </div>\n </PopoverPrimitive.Trigger>\n\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n asChild\n sideOffset={4}\n align=\"start\"\n onOpenAutoFocus={(e) => {\n e.preventDefault()\n searchInputRef.current?.focus()\n }}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-overlay bg-surface-overlay shadow-floating',\n )}\n >\n {/* Search input */}\n <div className=\"flex items-center gap-ds-02 border-b border-surface-border px-ds-04\">\n <Icon icon={IconSearch} size=\"sm\" className=\"shrink-0 text-surface-fg-subtle\" />\n <input\n ref={searchInputRef}\n type=\"text\"\n className=\"flex-1 bg-transparent py-ds-03 text-body-md outline-hidden placeholder:text-surface-fg-subtle\"\n placeholder={searchPlaceholder}\n value={search}\n onChange={(e) => {\n setSearch(e.target.value)\n setHighlightedIndex(-1)\n }}\n onKeyDown={handleKeyDown}\n aria-autocomplete=\"list\"\n aria-controls={listboxId}\n aria-activedescendant={\n highlightedIndex >= 0\n ? `${optionIdPrefix}-option-${highlightedIndex}`\n : undefined\n }\n aria-label=\"Search options\"\n />\n </div>\n\n {/* Options list */}\n {filteredOptions.length === 0 ? (\n <div className=\"px-ds-04 py-ds-05 text-center text-body-md text-surface-fg-subtle\">\n {emptyMessage}\n </div>\n ) : (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-multiselectable={multiple || undefined}\n className=\"overflow-auto p-ds-02\"\n style={{ maxHeight: `${maxVisible * ITEM_HEIGHT_PX}px` }}\n >\n {filteredOptions.map((option, index) => {\n const selected = isSelected(option.value)\n return (\n <li\n key={option.value}\n id={`${optionIdPrefix}-option-${index}`}\n role=\"option\"\n aria-selected={selected}\n aria-disabled={option.disabled || undefined}\n className={cn(\n 'relative flex cursor-pointer select-none items-center gap-ds-03 rounded-control px-ds-04 py-ds-03 text-body-md outline-hidden',\n 'transition-colors duration-fast-01 ease-productive-standard',\n highlightedIndex === index &&\n 'bg-accent-2',\n selected && 'text-accent-11',\n option.disabled &&\n 'pointer-events-none opacity-action-disabled',\n )}\n onClick={() => {\n if (!option.disabled) {\n handleSelect(option.value)\n }\n }}\n onKeyDown={(e) => {\n if (!option.disabled && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault()\n handleSelect(option.value)\n }\n }}\n onMouseEnter={() => {\n if (!option.disabled) {\n setHighlightedIndex(index)\n }\n }}\n >\n {option.icon && (\n <span className=\"flex h-ico-sm w-ico-sm items-center justify-center shrink-0\">\n <IconProvider size={resolvedIconSize}>\n {normalizeIcon(option.icon)}\n </IconProvider>\n </span>\n )}\n <span className=\"flex flex-1 flex-col\">\n {renderOption ? (\n renderOption(option, selected)\n ) : (\n <>\n <span>{option.label}</span>\n {option.description && (\n <span className=\"text-body-sm text-surface-fg-muted\">\n {option.description}\n </span>\n )}\n </>\n )}\n </span>\n {selected && (\n <Icon icon={IconCheck} size=\"sm\" className=\"shrink-0\" />\n )}\n </li>\n )\n })}\n </ul>\n )}\n </motion.div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </div>\n </PopoverPrimitive.Root>\n )\n },\n)\nCombobox.displayName = 'Combobox'\n\nexport { Combobox }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"combobox.js","names":[],"sources":["../../src/ui/combobox.tsx"],"sourcesContent":["'use client'\n\nimport * as PopoverPrimitive from '@primitives/react-popover'\nimport { IconCheck, IconChevronDown, IconSearch, IconX } from '@tabler/icons-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { Icon } from './icon'\nimport { IconProvider, type IconSize } from './icon-context'\nimport { type FieldState, resolveFieldState } from './lib/field-state'\nimport type { IconInput } from './lib/icon-input'\nimport { springs, tweens } from './lib/motion'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\n\n/** Border tint per validation state, applied to the trigger. */\nconst stateBorderClasses: Record<Exclude<FieldState, 'default'>, string> = {\n error: 'border-error-7',\n warning: 'border-warning-7',\n success: 'border-success-7',\n}\n\nexport const comboboxTriggerVariants = cva(\n [\n 'flex w-full items-center justify-between whitespace-nowrap rounded-control',\n 'border border-surface-border-strong bg-surface-raised-hover',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 focus-visible:border-accent-7',\n // Trigger is a div[role=combobox] (so chip remove-buttons in multi-select\n // aren't nested in a <button>), so disabled styling keys off data-disabled,\n // not the :disabled pseudo-class.\n 'data-[disabled]:cursor-not-allowed data-[disabled]:opacity-action-disabled',\n ],\n {\n variants: {\n size: {\n xs: 'h-ds-xs-plus text-body-sm px-ds-02',\n sm: 'h-ds-sm text-body-sm px-ds-03',\n md: 'h-ds-md text-body-md px-ds-04',\n lg: 'h-ds-lg text-body-md px-ds-05',\n },\n },\n defaultVariants: { size: 'md' },\n },\n)\n\nexport type ComboboxSize = NonNullable<VariantProps<typeof comboboxTriggerVariants>['size']>\n\n/** Maps combobox size to Icon component size for chevron / check icons */\nconst iconSizeMap: Record<NonNullable<ComboboxSize>, IconSize> = {\n xs: 'xs',\n sm: 'sm',\n md: 'sm',\n lg: 'md',\n}\n\n/** Maps combobox size to pill text + padding classes */\nconst pillSizeMap: Record<NonNullable<ComboboxSize>, string> = {\n xs: 'px-ds-02 py-0 text-body-xs',\n sm: 'px-ds-02 py-0 text-body-xs',\n md: 'px-ds-03 py-[1px] text-body-sm',\n lg: 'px-ds-03 py-[2px] text-body-sm',\n}\n\n/** Maps combobox size to overflow text classes */\nconst overflowTextMap: Record<NonNullable<ComboboxSize>, string> = {\n xs: 'text-body-xs',\n sm: 'text-body-xs',\n md: 'text-body-sm',\n lg: 'text-body-sm',\n}\n\n/**\n * Option shape for a Combobox dropdown item.\n * `value` must be unique across all options — it is the key used in selection state.\n */\nexport interface ComboboxOption {\n value: string\n label: string\n description?: string\n icon?: IconInput\n disabled?: boolean\n}\n\n/**\n * Props for Combobox — a searchable single or multi-select dropdown with built-in keyboard\n * navigation, pill overflow (\"+ N more\"), and an optional custom option renderer.\n *\n * **Single vs multi:** `multiple={false}` (default) — `value` is a `string` and `onValueChange`\n * receives a `string`. When `multiple={true}`, `value` is `string[]`, `onValueChange` receives\n * `string[]`, and selected items appear as dismissible pills in the trigger.\n *\n * The props form a **discriminated union** on `multiple` — TypeScript will narrow `value` and\n * `onValueChange` automatically, so no manual casts are needed.\n *\n * **Custom rendering:** Use `renderOption` to return custom JSX per option (e.g. avatars, badges).\n *\n * @example\n * // Single-select country picker:\n * <Combobox\n * options={[{ value: 'in', label: 'India' }, { value: 'us', label: 'United States' }]}\n * value={country}\n * onValueChange={(v) => setCountry(v)}\n * placeholder=\"Select country\"\n * />\n *\n * @example\n * // Multi-select tag picker with pill display:\n * <Combobox\n * multiple\n * options={tagOptions}\n * value={selectedTags}\n * onValueChange={(v) => setSelectedTags(v)}\n * placeholder=\"Select tags...\"\n * />\n *\n * @example\n * // Custom option renderer (user avatars in assignee picker):\n * <Combobox\n * options={users.map(u => ({ value: u.id, label: u.name }))}\n * value={assigneeId}\n * onValueChange={(v) => setAssigneeId(v)}\n * renderOption={(option, selected) => (\n * <span className=\"flex items-center gap-ds-03\">\n * <Avatar size=\"xs\"><AvatarFallback>{option.label[0]}</AvatarFallback></Avatar>\n * {option.label}\n * </span>\n * )}\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\ninterface ComboboxBaseProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Available options shown in the dropdown. */\n options: ComboboxOption[]\n /** Placeholder shown in the trigger when no value is selected. */\n placeholder?: string\n /** Placeholder for the search input inside the dropdown. */\n searchPlaceholder?: string\n /** Message shown when the search yields no results. */\n emptyMessage?: string\n disabled?: boolean\n triggerClassName?: string\n /** Max visible items in the dropdown before scroll (default 6). */\n maxVisible?: number\n /** Custom renderer for each option row. Receives the option and whether it's currently selected. */\n renderOption?: (option: ComboboxOption, selected: boolean) => React.ReactNode\n /** Accessible label for the trigger button. Falls back to `placeholder` if not provided. */\n accessibleLabel?: string\n /** Size of the trigger. Controls height, text size, padding, and pill sizing. */\n size?: ComboboxSize\n /** Validation/feedback state. `'error'` also sets `aria-invalid`. Inherited from `FormField` when omitted. */\n state?: FieldState\n}\n\ninterface ComboboxSingleProps extends ComboboxBaseProps {\n multiple?: false\n value?: string\n onValueChange: (value: string) => void\n}\n\ninterface ComboboxMultipleProps extends ComboboxBaseProps {\n multiple: true\n value?: string[]\n onValueChange: (value: string[]) => void\n}\n\nexport type ComboboxProps = ComboboxSingleProps | ComboboxMultipleProps\n\n/** Max pills shown in the trigger before \"+N more\" overflow */\nconst MAX_VISIBLE_PILLS = 2\n\n/** Approximate height of a single option item in px */\nconst ITEM_HEIGHT_PX = 36\n\nconst Combobox = React.forwardRef<HTMLDivElement, ComboboxProps>(\n (\n {\n options,\n value,\n onValueChange,\n placeholder = 'Select...',\n searchPlaceholder = 'Search...',\n emptyMessage = 'No results found',\n multiple = false,\n disabled = false,\n className,\n triggerClassName,\n maxVisible = 6,\n renderOption,\n accessibleLabel,\n id: externalId,\n size: sizeProp = 'md',\n state: stateProp,\n ...rest\n },\n ref,\n ) => {\n const size = sizeProp ?? 'md'\n const [open, setOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [highlightedIndex, setHighlightedIndex] = React.useState(-1)\n const searchInputRef = React.useRef<HTMLInputElement>(null)\n const listRef = React.useRef<HTMLUListElement>(null)\n const optionIdPrefix = React.useId()\n const listboxId = React.useId()\n\n const selectedValues = React.useMemo<string[]>(() => {\n if (value === undefined || value === null) return []\n if (Array.isArray(value)) return value\n return [value]\n }, [value])\n\n const filteredOptions = React.useMemo(\n () =>\n search\n ? options.filter((o) =>\n o.label.toLowerCase().includes(search.toLowerCase()),\n )\n : options,\n [options, search],\n )\n\n const isSelected = React.useCallback(\n (optionValue: string) => selectedValues.includes(optionValue),\n [selectedValues],\n )\n\n const handleSelect = React.useCallback(\n (optionValue: string) => {\n if (multiple) {\n const newValue = selectedValues.includes(optionValue)\n ? selectedValues.filter((v) => v !== optionValue)\n : [...selectedValues, optionValue]\n ;(onValueChange as (value: string[]) => void)(newValue)\n } else {\n ;(onValueChange as (value: string) => void)(optionValue)\n setOpen(false)\n }\n },\n [multiple, selectedValues, onValueChange],\n )\n\n const handleRemovePill = React.useCallback(\n (e: React.SyntheticEvent, optionValue: string) => {\n e.stopPropagation()\n e.preventDefault()\n const newValue = selectedValues.filter((v) => v !== optionValue)\n ;(onValueChange as (value: string[]) => void)(newValue)\n },\n [selectedValues, onValueChange],\n )\n\n const handleOpenChange = React.useCallback(\n (nextOpen: boolean) => {\n if (disabled) return\n setOpen(nextOpen)\n if (!nextOpen) {\n setSearch('')\n setHighlightedIndex(-1)\n }\n },\n [disabled],\n )\n\n // Auto-focus search input when popover opens\n React.useEffect(() => {\n if (open) {\n // Use a small timeout to allow the popover to render\n const timer = setTimeout(() => {\n searchInputRef.current?.focus()\n }, 0)\n return () => clearTimeout(timer)\n }\n }, [open])\n\n const findNextEnabledIndex = React.useCallback(\n (currentIndex: number, direction: 1 | -1): number => {\n const len = filteredOptions.length\n if (len === 0) return -1\n\n let nextIndex = currentIndex + direction\n while (nextIndex >= 0 && nextIndex < len) {\n if (!filteredOptions[nextIndex].disabled) return nextIndex\n nextIndex += direction\n }\n return currentIndex\n },\n [filteredOptions],\n )\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault()\n const nextIdx = findNextEnabledIndex(highlightedIndex, 1)\n setHighlightedIndex(nextIdx)\n break\n }\n case 'ArrowUp': {\n e.preventDefault()\n const prevIdx = findNextEnabledIndex(highlightedIndex, -1)\n setHighlightedIndex(prevIdx)\n break\n }\n case 'Home': {\n e.preventDefault()\n const firstEnabled = filteredOptions.findIndex((o) => !o.disabled)\n setHighlightedIndex(firstEnabled)\n break\n }\n case 'End': {\n e.preventDefault()\n let lastEnabled = -1\n for (let i = filteredOptions.length - 1; i >= 0; i--) {\n if (!filteredOptions[i].disabled) {\n lastEnabled = i\n break\n }\n }\n setHighlightedIndex(lastEnabled)\n break\n }\n case 'Enter': {\n e.preventDefault()\n if (\n highlightedIndex >= 0 &&\n highlightedIndex < filteredOptions.length &&\n !filteredOptions[highlightedIndex].disabled\n ) {\n handleSelect(filteredOptions[highlightedIndex].value)\n }\n break\n }\n case 'Escape': {\n e.preventDefault()\n setOpen(false)\n setSearch('')\n setHighlightedIndex(-1)\n break\n }\n }\n },\n [highlightedIndex, filteredOptions, findNextEnabledIndex, handleSelect],\n )\n\n // Scroll highlighted option into view\n React.useEffect(() => {\n if (highlightedIndex >= 0 && listRef.current) {\n const optionEl = listRef.current.children[highlightedIndex] as HTMLElement\n if (optionEl) {\n optionEl.scrollIntoView?.({ block: 'nearest' })\n }\n }\n }, [highlightedIndex])\n\n const getSelectedLabel = React.useCallback(() => {\n if (selectedValues.length === 0) return null\n const option = options.find((o) => o.value === selectedValues[0])\n return option?.label ?? null\n }, [selectedValues, options])\n\n const resolvedIconSize = iconSizeMap[size]\n const resolvedPillClasses = pillSizeMap[size]\n const resolvedOverflowText = overflowTextMap[size]\n\n const renderTriggerContent = () => {\n if (multiple && selectedValues.length > 0) {\n const visiblePills = selectedValues.slice(0, MAX_VISIBLE_PILLS)\n const remaining = selectedValues.length - MAX_VISIBLE_PILLS\n\n return (\n <span className=\"flex flex-1 flex-wrap items-center gap-ds-02 overflow-hidden\">\n {visiblePills.map((val) => {\n const option = options.find((o) => o.value === val)\n if (!option) return null\n return (\n <span\n key={val}\n className={cn('inline-flex items-center gap-ds-01 rounded-control-inner bg-accent-2', resolvedPillClasses)}\n >\n {option.label}\n <button\n type=\"button\"\n className=\"inline-flex items-center justify-center rounded-pill outline-hidden hover:bg-surface-raised-hover transition-colors duration-fast-01 ease-productive-standard\"\n onClick={(e) => handleRemovePill(e, val)}\n aria-label={`Remove ${option.label}`}\n tabIndex={-1}\n >\n <Icon icon={IconX} size={resolvedIconSize} />\n </button>\n </span>\n )\n })}\n {remaining > 0 && (\n <span className={cn('text-surface-fg-muted', resolvedOverflowText)}>\n +{remaining} more\n </span>\n )}\n </span>\n )\n }\n\n if (!multiple && selectedValues.length === 1) {\n const label = getSelectedLabel()\n if (label) {\n return <span className=\"min-w-0 flex-1 truncate text-left\">{label}</span>\n }\n }\n\n return (\n <span className=\"min-w-0 flex-1 truncate text-left text-surface-fg-subtle\">\n {placeholder}\n </span>\n )\n }\n\n const fieldCtx = useFormField()\n const state = resolveFieldState(stateProp, fieldCtx.state)\n const isError = state === 'error'\n const ariaDescribedBy = fieldCtx.helperTextId\n const ariaRequired = fieldCtx.required\n\n return (\n <PopoverPrimitive.Root open={open} onOpenChange={handleOpenChange}>\n <div className={cn('relative', className)} {...rest}>\n <PopoverPrimitive.Trigger asChild disabled={disabled}>\n {/* role=combobox on a <div>, NOT a <button> — a native button can't\n legally contain the chip remove-<button>s rendered in multi-select\n (invalid HTML + a11y break). This is the W3C select-only combobox\n pattern. Radix Trigger wires click/toggle; we add the keyboard-open\n (Enter/Space/ArrowDown) a real button would have given for free. */}\n <div\n ref={ref}\n role=\"combobox\"\n tabIndex={disabled ? -1 : 0}\n data-disabled={disabled || undefined}\n id={externalId}\n aria-expanded={open}\n aria-controls={listboxId}\n aria-haspopup=\"listbox\"\n aria-disabled={disabled || undefined}\n // The trigger is a div[role=combobox] — non-labellable, so `<Label htmlFor>`\n // can't target it. Inside a FormField, adopt the label's id via\n // aria-labelledby; explicit accessibleLabel wins; placeholder is the\n // standalone fallback.\n aria-labelledby={accessibleLabel ? undefined : fieldCtx.labelId}\n aria-label={accessibleLabel ?? (fieldCtx.labelId ? undefined : placeholder)}\n aria-invalid={isError || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n onKeyDown={(e) => {\n if (disabled) return\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault()\n handleOpenChange(true)\n }\n }}\n className={cn(\n comboboxTriggerVariants({ size }),\n 'cursor-pointer',\n open && 'border-accent-7',\n state && stateBorderClasses[state],\n triggerClassName,\n )}\n >\n {renderTriggerContent()}\n <Icon icon={IconChevronDown} size={resolvedIconSize} className={cn(\"ml-ds-02 shrink-0 opacity-50 transition-transform duration-fast-01 ease-productive-standard\", open && 'rotate-180')} />\n </div>\n </PopoverPrimitive.Trigger>\n\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n asChild\n sideOffset={4}\n align=\"start\"\n onOpenAutoFocus={(e) => {\n e.preventDefault()\n searchInputRef.current?.focus()\n }}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-overlay bg-surface-overlay shadow-floating',\n )}\n >\n {/* Search input */}\n <div className=\"flex items-center gap-ds-02 border-b border-surface-border px-ds-04\">\n <Icon icon={IconSearch} size=\"sm\" className=\"shrink-0 text-surface-fg-subtle\" />\n <input\n ref={searchInputRef}\n type=\"text\"\n className=\"flex-1 bg-transparent py-ds-03 text-body-md outline-hidden placeholder:text-surface-fg-subtle\"\n placeholder={searchPlaceholder}\n value={search}\n onChange={(e) => {\n setSearch(e.target.value)\n setHighlightedIndex(-1)\n }}\n onKeyDown={handleKeyDown}\n aria-autocomplete=\"list\"\n aria-controls={listboxId}\n aria-activedescendant={\n highlightedIndex >= 0\n ? `${optionIdPrefix}-option-${highlightedIndex}`\n : undefined\n }\n aria-label=\"Search options\"\n />\n </div>\n\n {/* Options list */}\n {filteredOptions.length === 0 ? (\n <div className=\"px-ds-04 py-ds-05 text-center text-body-md text-surface-fg-subtle\">\n {emptyMessage}\n </div>\n ) : (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-multiselectable={multiple || undefined}\n className=\"overflow-auto p-ds-02\"\n style={{ maxHeight: `${maxVisible * ITEM_HEIGHT_PX}px` }}\n >\n {filteredOptions.map((option, index) => {\n const selected = isSelected(option.value)\n return (\n <li\n key={option.value}\n id={`${optionIdPrefix}-option-${index}`}\n role=\"option\"\n aria-selected={selected}\n aria-disabled={option.disabled || undefined}\n className={cn(\n 'relative flex cursor-pointer select-none items-center gap-ds-03 rounded-control px-ds-04 py-ds-03 text-body-md outline-hidden',\n 'transition-colors duration-fast-01 ease-productive-standard',\n highlightedIndex === index &&\n 'bg-accent-2',\n selected && 'text-accent-11',\n option.disabled &&\n 'pointer-events-none opacity-action-disabled',\n )}\n onClick={() => {\n if (!option.disabled) {\n handleSelect(option.value)\n }\n }}\n onKeyDown={(e) => {\n if (!option.disabled && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault()\n handleSelect(option.value)\n }\n }}\n onMouseEnter={() => {\n if (!option.disabled) {\n setHighlightedIndex(index)\n }\n }}\n >\n {option.icon && (\n <span className=\"flex h-ico-sm w-ico-sm items-center justify-center shrink-0\">\n <IconProvider size={resolvedIconSize}>\n {normalizeIcon(option.icon)}\n </IconProvider>\n </span>\n )}\n <span className=\"flex flex-1 flex-col\">\n {renderOption ? (\n renderOption(option, selected)\n ) : (\n <>\n <span>{option.label}</span>\n {option.description && (\n <span className=\"text-body-sm text-surface-fg-muted\">\n {option.description}\n </span>\n )}\n </>\n )}\n </span>\n {selected && (\n <Icon icon={IconCheck} size=\"sm\" className=\"shrink-0\" />\n )}\n </li>\n )\n })}\n </ul>\n )}\n </motion.div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </div>\n </PopoverPrimitive.Root>\n )\n },\n)\nCombobox.displayName = 'Combobox'\n\nexport { Combobox }\n"],"mappings":"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"}
|