@duro-app/ui 0.24.0 → 0.25.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.
Files changed (69) hide show
  1. package/dist/components/ButtonGroup/ButtonGroup.meta.d.ts.map +1 -1
  2. package/dist/components/CheckboxGroup/CheckboxGroup.d.ts.map +1 -1
  3. package/dist/components/CheckboxGroup/CheckboxGroup.meta.d.ts.map +1 -1
  4. package/dist/components/Combobox/Combobox.d.ts +44 -0
  5. package/dist/components/Combobox/Combobox.d.ts.map +1 -0
  6. package/dist/components/Combobox/Combobox.meta.d.ts +3 -0
  7. package/dist/components/Combobox/Combobox.meta.d.ts.map +1 -0
  8. package/dist/components/Combobox/Combobox.stories.d.ts +9 -0
  9. package/dist/components/Combobox/Combobox.stories.d.ts.map +1 -0
  10. package/dist/components/Combobox/ComboboxContext.d.ts +19 -0
  11. package/dist/components/Combobox/ComboboxContext.d.ts.map +1 -0
  12. package/dist/components/Combobox/styles.css.d.ts +112 -0
  13. package/dist/components/Combobox/styles.css.d.ts.map +1 -0
  14. package/dist/components/Combobox/useComboboxRoot.d.ts +15 -0
  15. package/dist/components/Combobox/useComboboxRoot.d.ts.map +1 -0
  16. package/dist/components/Field/Field.d.ts +4 -0
  17. package/dist/components/Field/Field.d.ts.map +1 -1
  18. package/dist/components/Field/Field.stories.d.ts +3 -0
  19. package/dist/components/Field/Field.stories.d.ts.map +1 -1
  20. package/dist/components/Field/FieldContext.d.ts +5 -0
  21. package/dist/components/Field/FieldContext.d.ts.map +1 -1
  22. package/dist/components/Field/styles.css.d.ts +26 -0
  23. package/dist/components/Field/styles.css.d.ts.map +1 -1
  24. package/dist/components/Form/Form.d.ts +7 -2
  25. package/dist/components/Form/Form.d.ts.map +1 -1
  26. package/dist/components/Form/Form.stories.d.ts +6 -0
  27. package/dist/components/Form/Form.stories.d.ts.map +1 -1
  28. package/dist/components/Form/FormContext.d.ts +9 -2
  29. package/dist/components/Form/FormContext.d.ts.map +1 -1
  30. package/dist/components/Input/Input.d.ts.map +1 -1
  31. package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  32. package/dist/components/RadioGroup/RadioGroup.meta.d.ts.map +1 -1
  33. package/dist/components/Table/SortIndicator.d.ts.map +1 -1
  34. package/dist/components/Table/Table.d.ts.map +1 -1
  35. package/dist/components/Table/Table.stories.d.ts.map +1 -1
  36. package/dist/components/Table/useDataTable.d.ts.map +1 -1
  37. package/dist/components/Textarea/Textarea.d.ts.map +1 -1
  38. package/dist/index.css +1 -1
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2964 -2381
  42. package/dist/index.js.map +1 -1
  43. package/package.json +2 -2
  44. package/src/components/ButtonGroup/ButtonGroup.meta.ts +10 -2
  45. package/src/components/CheckboxGroup/CheckboxGroup.meta.ts +4 -1
  46. package/src/components/CheckboxGroup/CheckboxGroup.tsx +2 -9
  47. package/src/components/Combobox/Combobox.meta.ts +35 -0
  48. package/src/components/Combobox/Combobox.stories.tsx +119 -0
  49. package/src/components/Combobox/Combobox.tsx +247 -0
  50. package/src/components/Combobox/ComboboxContext.ts +26 -0
  51. package/src/components/Combobox/styles.css.ts +119 -0
  52. package/src/components/Combobox/useComboboxRoot.ts +235 -0
  53. package/src/components/Field/Field.stories.tsx +60 -0
  54. package/src/components/Field/Field.tsx +97 -9
  55. package/src/components/Field/FieldContext.ts +5 -0
  56. package/src/components/Field/styles.css.ts +26 -0
  57. package/src/components/Form/Form.stories.tsx +275 -0
  58. package/src/components/Form/Form.tsx +73 -7
  59. package/src/components/Form/FormContext.ts +10 -1
  60. package/src/components/Input/Input.tsx +2 -1
  61. package/src/components/RadioGroup/RadioGroup.meta.ts +4 -1
  62. package/src/components/RadioGroup/RadioGroup.tsx +1 -4
  63. package/src/components/Table/SortIndicator.tsx +1 -4
  64. package/src/components/Table/Table.stories.tsx +135 -84
  65. package/src/components/Table/Table.tsx +10 -5
  66. package/src/components/Table/useDataTable.ts +34 -48
  67. package/src/components/Textarea/Textarea.tsx +2 -1
  68. package/src/index.ts +2 -0
  69. package/src/strict.css +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -53,7 +53,7 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
- "@duro-app/tokens": "^0.24.0"
56
+ "@duro-app/tokens": "^0.25.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@babel/preset-typescript": "^7.28.0",
@@ -14,8 +14,16 @@ export const meta: ComponentMeta = {
14
14
  'Navigation items — use Inline or SideNav',
15
15
  ],
16
16
  relatedTo: [
17
- {component: 'Inline', relationship: 'Inline is a general-purpose horizontal layout; ButtonGroup adds role="group" and disabled support'},
18
- {component: 'ActionBar', relationship: 'ActionBar is a floating bulk-selection toolbar; ButtonGroup is a static grouping'},
17
+ {
18
+ component: 'Inline',
19
+ relationship:
20
+ 'Inline is a general-purpose horizontal layout; ButtonGroup adds role="group" and disabled support',
21
+ },
22
+ {
23
+ component: 'ActionBar',
24
+ relationship:
25
+ 'ActionBar is a floating bulk-selection toolbar; ButtonGroup is a static grouping',
26
+ },
19
27
  ],
20
28
  example: `<ButtonGroup align="end" gap="sm">
21
29
  <Button variant="secondary">Cancel</Button>
@@ -13,7 +13,10 @@ export const meta: ComponentMeta = {
13
13
  'Many options (10+) — consider a filterable list or Select with multi-select',
14
14
  ],
15
15
  relatedTo: [
16
- {component: 'RadioGroup', relationship: 'RadioGroup for single-select; CheckboxGroup for multi-select'},
16
+ {
17
+ component: 'RadioGroup',
18
+ relationship: 'RadioGroup for single-select; CheckboxGroup for multi-select',
19
+ },
17
20
  {component: 'Checkbox', relationship: 'CheckboxGroup.Item wraps Checkbox internally'},
18
21
  {component: 'ToggleGroup', relationship: 'ToggleGroup for toolbar-style multi-select'},
19
22
  ],
@@ -46,9 +46,7 @@ function Root({
46
46
  const onToggle = useCallback(
47
47
  (itemValue: string, checked: boolean) => {
48
48
  if (disabled) return
49
- const next = checked
50
- ? [...value, itemValue]
51
- : value.filter((v) => v !== itemValue)
49
+ const next = checked ? [...value, itemValue] : value.filter((v) => v !== itemValue)
52
50
  setValue(next)
53
51
  },
54
52
  [disabled, value, setValue],
@@ -88,12 +86,7 @@ function Item({value, disabled: itemDisabled = false, children}: ItemProps) {
88
86
  )
89
87
 
90
88
  return (
91
- <Checkbox
92
- value={value}
93
- checked={isChecked}
94
- disabled={isDisabled}
95
- onChange={handleChange}
96
- >
89
+ <Checkbox value={value} checked={isChecked} disabled={isDisabled} onChange={handleChange}>
97
90
  {children}
98
91
  </Checkbox>
99
92
  )
@@ -0,0 +1,35 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Searchable dropdown for selecting a value from a filterable list. Combines a text input with a popup listbox. Compound component — Root is required.',
6
+ whenToUse: [
7
+ 'Large option lists where users benefit from typing to filter (e.g. country, city, user picker)',
8
+ 'Autocomplete or typeahead scenarios',
9
+ 'Table column filters with known categorical values',
10
+ ],
11
+ whenNotToUse: [
12
+ 'Small lists (fewer than 7 options) — use Select instead',
13
+ 'Free-form text entry — use Input',
14
+ 'Multiple selections — use CheckboxGroup or ToggleGroup',
15
+ ],
16
+ anatomy: {
17
+ required: ['Root', 'Input', 'Popup', 'Item'],
18
+ optional: ['Empty'],
19
+ },
20
+ relatedTo: [
21
+ {component: 'Select', relationship: 'Select for short lists without search; Combobox for long filterable lists'},
22
+ {component: 'Input', relationship: 'Input for free-form text; Combobox for constrained selection'},
23
+ {component: 'Menu', relationship: 'Menu triggers actions; Combobox picks a value'},
24
+ ],
25
+ example: `<Combobox.Root name="country" defaultValue="us" initialLabels={{us: 'United States'}}>
26
+ <Combobox.Input placeholder="Search countries…" />
27
+ <Combobox.Popup>
28
+ <Combobox.Item value="us">United States</Combobox.Item>
29
+ <Combobox.Item value="gb">United Kingdom</Combobox.Item>
30
+ <Combobox.Item value="fr">France</Combobox.Item>
31
+ <Combobox.Item value="de">Germany</Combobox.Item>
32
+ <Combobox.Empty>No countries found</Combobox.Empty>
33
+ </Combobox.Popup>
34
+ </Combobox.Root>`,
35
+ }
@@ -0,0 +1,119 @@
1
+ import {useState} from 'react'
2
+ import type {Meta, StoryObj} from '@storybook/react'
3
+ import {expect} from 'storybook/test'
4
+ import {Combobox} from './Combobox'
5
+
6
+ const meta: Meta = {
7
+ title: 'Components/Combobox',
8
+ }
9
+
10
+ export default meta
11
+ type Story = StoryObj
12
+
13
+ const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape', 'Honeydew']
14
+
15
+ export const Default: Story = {
16
+ render: () => (
17
+ <Combobox.Root>
18
+ <Combobox.Input placeholder="Search fruit...">
19
+ <Combobox.Trigger />
20
+ </Combobox.Input>
21
+ <Combobox.Popup>
22
+ {fruits.map((fruit) => (
23
+ <Combobox.Item key={fruit} value={fruit.toLowerCase()}>
24
+ <Combobox.ItemText>{fruit}</Combobox.ItemText>
25
+ </Combobox.Item>
26
+ ))}
27
+ <Combobox.Empty>No fruits found</Combobox.Empty>
28
+ </Combobox.Popup>
29
+ </Combobox.Root>
30
+ ),
31
+ play: async ({canvas}) => {
32
+ const input = canvas.getByRole('combobox')
33
+ await expect(input).toBeInTheDocument()
34
+ await expect(input).toHaveAttribute('aria-expanded', 'false')
35
+ },
36
+ }
37
+
38
+ export const WithDefaultValue: Story = {
39
+ render: () => (
40
+ <Combobox.Root defaultValue="cherry" initialLabels={{cherry: 'Cherry'}}>
41
+ <Combobox.Input placeholder="Search fruit...">
42
+ <Combobox.Trigger />
43
+ </Combobox.Input>
44
+ <Combobox.Popup>
45
+ {fruits.map((fruit) => (
46
+ <Combobox.Item key={fruit} value={fruit.toLowerCase()}>
47
+ <Combobox.ItemText>{fruit}</Combobox.ItemText>
48
+ </Combobox.Item>
49
+ ))}
50
+ <Combobox.Empty>No fruits found</Combobox.Empty>
51
+ </Combobox.Popup>
52
+ </Combobox.Root>
53
+ ),
54
+ play: async ({canvas}) => {
55
+ const input = canvas.getByRole('combobox')
56
+ await expect(input).toHaveValue('Cherry')
57
+ },
58
+ }
59
+
60
+ function ControlledDemo() {
61
+ const [value, setValue] = useState<string | null>(null)
62
+
63
+ return (
64
+ <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
65
+ <Combobox.Root value={value} onValueChange={setValue}>
66
+ <Combobox.Input placeholder="Pick a role...">
67
+ <Combobox.Trigger />
68
+ </Combobox.Input>
69
+ <Combobox.Popup>
70
+ <Combobox.Item value="admin"><Combobox.ItemText>Admin</Combobox.ItemText></Combobox.Item>
71
+ <Combobox.Item value="editor"><Combobox.ItemText>Editor</Combobox.ItemText></Combobox.Item>
72
+ <Combobox.Item value="viewer"><Combobox.ItemText>Viewer</Combobox.ItemText></Combobox.Item>
73
+ <Combobox.Item value="moderator"><Combobox.ItemText>Moderator</Combobox.ItemText></Combobox.Item>
74
+ <Combobox.Item value="analyst"><Combobox.ItemText>Analyst</Combobox.ItemText></Combobox.Item>
75
+ <Combobox.Empty>No roles found</Combobox.Empty>
76
+ </Combobox.Popup>
77
+ </Combobox.Root>
78
+ <span style={{fontSize: 14, color: '#888'}}>
79
+ Selected: {value ?? 'none'}
80
+ </span>
81
+ </div>
82
+ )
83
+ }
84
+
85
+ export const Controlled: Story = {
86
+ render: () => <ControlledDemo />,
87
+ play: async ({canvas}) => {
88
+ await expect(canvas.getByText('Selected: none')).toBeInTheDocument()
89
+ },
90
+ }
91
+
92
+ export const ManyItems: Story = {
93
+ render: () => {
94
+ const countries = [
95
+ 'Argentina', 'Australia', 'Austria', 'Belgium', 'Brazil', 'Canada',
96
+ 'Chile', 'China', 'Colombia', 'Denmark', 'Egypt', 'Finland',
97
+ 'France', 'Germany', 'Greece', 'India', 'Indonesia', 'Ireland',
98
+ 'Italy', 'Japan', 'Kenya', 'Mexico', 'Netherlands', 'New Zealand',
99
+ 'Norway', 'Peru', 'Poland', 'Portugal', 'South Korea', 'Spain',
100
+ 'Sweden', 'Switzerland', 'Thailand', 'Turkey', 'United Kingdom', 'United States',
101
+ ]
102
+
103
+ return (
104
+ <Combobox.Root>
105
+ <Combobox.Input placeholder="Search country...">
106
+ <Combobox.Trigger />
107
+ </Combobox.Input>
108
+ <Combobox.Popup>
109
+ {countries.map((country) => (
110
+ <Combobox.Item key={country} value={country.toLowerCase()}>
111
+ <Combobox.ItemText>{country}</Combobox.ItemText>
112
+ </Combobox.Item>
113
+ ))}
114
+ <Combobox.Empty>No countries found</Combobox.Empty>
115
+ </Combobox.Popup>
116
+ </Combobox.Root>
117
+ )
118
+ },
119
+ }
@@ -0,0 +1,247 @@
1
+ import {type ReactNode, useRef, useId, useEffect, useState} from 'react'
2
+ import {html} from 'react-strict-dom'
3
+ import {styles} from './styles.css'
4
+ import {ComboboxContext, useCombobox} from './ComboboxContext'
5
+ import {useComboboxRoot} from './useComboboxRoot'
6
+
7
+ // --- Root ---
8
+ interface RootProps {
9
+ name?: string
10
+ defaultValue?: string
11
+ value?: string
12
+ onValueChange?: (value: string | null) => void
13
+ inputValue?: string
14
+ onInputChange?: (value: string) => void
15
+ initialLabels?: Record<string, string>
16
+ children: ReactNode
17
+ }
18
+
19
+ function Root({
20
+ name,
21
+ defaultValue,
22
+ value,
23
+ onValueChange,
24
+ inputValue,
25
+ onInputChange,
26
+ initialLabels,
27
+ children,
28
+ }: RootProps) {
29
+ const {ctx, rootRef} = useComboboxRoot({
30
+ defaultValue,
31
+ value,
32
+ onValueChange,
33
+ inputValue,
34
+ onInputChange,
35
+ initialLabels,
36
+ })
37
+
38
+ return (
39
+ <ComboboxContext.Provider value={ctx}>
40
+ <html.div ref={rootRef} style={styles.root}>
41
+ {name && <html.input type="hidden" name={name} value={ctx.value ?? ''} />}
42
+ {children}
43
+ </html.div>
44
+ </ComboboxContext.Provider>
45
+ )
46
+ }
47
+
48
+ // --- Input ---
49
+ function Input({placeholder, children}: {placeholder?: string; children?: ReactNode}) {
50
+ const {
51
+ open,
52
+ openPopup,
53
+ close,
54
+ inputValue,
55
+ setInputValue,
56
+ listboxId,
57
+ highlightedId,
58
+ inputRef,
59
+ } = useCombobox()
60
+ const localRef = useRef<HTMLInputElement>(null)
61
+ const [focused, setFocused] = useState(false)
62
+
63
+ useEffect(() => {
64
+ inputRef.current = localRef.current
65
+ })
66
+
67
+ return (
68
+ <html.div style={[styles.inputWrapper, focused && styles.inputWrapperFocused]}>
69
+ <html.input
70
+ ref={localRef}
71
+ type="text"
72
+ role={'combobox' as 'listbox'}
73
+ value={inputValue}
74
+ placeholder={placeholder}
75
+ aria-expanded={open}
76
+ aria-haspopup="listbox"
77
+ aria-controls={open ? listboxId : undefined}
78
+ aria-activedescendant={highlightedId ?? undefined}
79
+ aria-autocomplete={'list' as 'none'}
80
+ autoComplete="off"
81
+ style={styles.input}
82
+ onFocus={() => {
83
+ setFocused(true)
84
+ openPopup()
85
+ }}
86
+ onBlur={() => {
87
+ setFocused(false)
88
+ // Delay close to allow item click to register
89
+ setTimeout(close, 150)
90
+ }}
91
+ onChange={(e: any) => {
92
+ setInputValue(e.target.value)
93
+ if (!open) openPopup()
94
+ }}
95
+ />
96
+ {children}
97
+ </html.div>
98
+ )
99
+ }
100
+
101
+ // --- Trigger ---
102
+ function Trigger({children}: {children?: ReactNode}) {
103
+ const {open, openPopup, close, inputRef} = useCombobox()
104
+
105
+ return (
106
+ <html.button
107
+ type="button"
108
+ tabIndex={-1}
109
+ aria-label={open ? 'Close' : 'Open'}
110
+ onClick={() => {
111
+ if (open) {
112
+ close()
113
+ } else {
114
+ openPopup()
115
+ }
116
+ inputRef.current?.focus()
117
+ }}
118
+ style={styles.trigger}
119
+ >
120
+ {children ?? (
121
+ <svg
122
+ width="12"
123
+ height="12"
124
+ viewBox="0 0 24 24"
125
+ fill="none"
126
+ stroke="currentColor"
127
+ strokeWidth="2"
128
+ strokeLinecap="round"
129
+ strokeLinejoin="round"
130
+ aria-hidden="true"
131
+ >
132
+ <path d="M6 9l6 6 6-6" />
133
+ </svg>
134
+ )}
135
+ </html.button>
136
+ )
137
+ }
138
+
139
+ // --- Popup ---
140
+ function Popup({children}: {children: ReactNode}) {
141
+ const {open, listboxId} = useCombobox()
142
+
143
+ if (!open) return null
144
+
145
+ return (
146
+ <html.div
147
+ id={listboxId}
148
+ role="listbox"
149
+ style={styles.popup}
150
+ >
151
+ {children}
152
+ </html.div>
153
+ )
154
+ }
155
+
156
+ // --- Item ---
157
+ interface ItemProps {
158
+ value: string
159
+ children: ReactNode
160
+ }
161
+
162
+ function Item({value: itemValue, children}: ItemProps) {
163
+ const {
164
+ value: selectedValue,
165
+ setValue,
166
+ inputValue,
167
+ labels,
168
+ registerLabel,
169
+ highlightedId,
170
+ setHighlightedId,
171
+ registerItem,
172
+ } = useCombobox()
173
+ const id = useId()
174
+ const ref = useRef<HTMLDivElement>(null)
175
+ const isSelected = selectedValue === itemValue
176
+ const isHighlighted = highlightedId === id
177
+
178
+ // Filter: check if this item matches the input
179
+ const label = labels[itemValue] ?? itemValue
180
+ const query = inputValue.toLowerCase()
181
+ const isVisible = !query || label.toLowerCase().includes(query)
182
+
183
+ useEffect(() => {
184
+ const el = ref.current
185
+ if (!el) return
186
+ const text = el.textContent
187
+ if (text) registerLabel(itemValue, text)
188
+ }, [itemValue, registerLabel])
189
+
190
+ useEffect(() => {
191
+ const el = ref.current
192
+ if (!el) return
193
+ return registerItem(id, itemValue, el)
194
+ }, [id, itemValue, registerItem])
195
+
196
+ if (!isVisible) return null
197
+
198
+ return (
199
+ <html.div
200
+ ref={ref}
201
+ id={id}
202
+ role="option"
203
+ aria-selected={isSelected}
204
+ onClick={() => setValue(itemValue)}
205
+ onPointerEnter={() => setHighlightedId(id)}
206
+ style={[
207
+ styles.item,
208
+ isSelected && styles.itemSelected,
209
+ isHighlighted && styles.itemHighlighted,
210
+ ]}
211
+ >
212
+ {children}
213
+ </html.div>
214
+ )
215
+ }
216
+
217
+ // --- ItemText ---
218
+ function ItemText({children}: {children: ReactNode}) {
219
+ return <html.span>{children}</html.span>
220
+ }
221
+
222
+ // --- Empty ---
223
+ function Empty({children}: {children: ReactNode}) {
224
+ const {open, inputValue, labels} = useCombobox()
225
+
226
+ if (!open) return null
227
+
228
+ // Check if any items are visible
229
+ const query = inputValue.toLowerCase()
230
+ const hasVisibleItems = query
231
+ ? Object.values(labels).some((label) => label.toLowerCase().includes(query))
232
+ : true
233
+
234
+ if (hasVisibleItems) return null
235
+
236
+ return <html.div style={styles.empty}>{children}</html.div>
237
+ }
238
+
239
+ export const Combobox = {
240
+ Root,
241
+ Input,
242
+ Trigger,
243
+ Popup,
244
+ Item,
245
+ ItemText,
246
+ Empty,
247
+ }
@@ -0,0 +1,26 @@
1
+ import {createContext, useContext} from 'react'
2
+
3
+ export interface ComboboxContextValue {
4
+ open: boolean
5
+ openPopup: () => void
6
+ close: () => void
7
+ value: string | null
8
+ setValue: (value: string) => void
9
+ inputValue: string
10
+ setInputValue: (value: string) => void
11
+ labels: Record<string, string>
12
+ registerLabel: (value: string, label: string) => void
13
+ listboxId: string
14
+ highlightedId: string | null
15
+ setHighlightedId: (id: string | null) => void
16
+ registerItem: (id: string, value: string, element: HTMLElement) => () => void
17
+ inputRef: React.RefObject<HTMLInputElement | null>
18
+ }
19
+
20
+ export const ComboboxContext = createContext<ComboboxContextValue | null>(null)
21
+
22
+ export function useCombobox() {
23
+ const ctx = useContext(ComboboxContext)
24
+ if (!ctx) throw new Error('Combobox compound components must be used within Combobox.Root')
25
+ return ctx
26
+ }
@@ -0,0 +1,119 @@
1
+ import {css} from 'react-strict-dom'
2
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
+ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
+
7
+ export const styles = css.create({
8
+ root: {
9
+ position: 'relative',
10
+ display: 'inline-flex',
11
+ },
12
+ inputWrapper: {
13
+ display: 'inline-flex',
14
+ alignItems: 'center',
15
+ gap: spacing.sm,
16
+ paddingLeft: spacing.md,
17
+ paddingRight: spacing.xs,
18
+ backgroundColor: colors.bgCard,
19
+ borderWidth: 1,
20
+ borderStyle: 'solid',
21
+ borderColor: {
22
+ default: colors.border,
23
+ ':hover': colors.accent,
24
+ },
25
+ borderRadius: radii.sm,
26
+ transitionProperty: 'border-color',
27
+ transitionDuration: '150ms',
28
+ },
29
+ inputWrapperFocused: {
30
+ borderColor: colors.accent,
31
+ },
32
+ input: {
33
+ flex: 1,
34
+ paddingTop: spacing.sm,
35
+ paddingBottom: spacing.sm,
36
+ fontFamily: typography.fontFamily,
37
+ fontSize: typography.fontSizeSm,
38
+ color: colors.text,
39
+ backgroundColor: 'transparent',
40
+ borderWidth: 0,
41
+ outline: 'none',
42
+ minWidth: 0,
43
+ },
44
+ trigger: {
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ paddingTop: spacing.xs,
49
+ paddingBottom: spacing.xs,
50
+ paddingLeft: spacing.xs,
51
+ paddingRight: spacing.xs,
52
+ backgroundColor: 'transparent',
53
+ borderWidth: 0,
54
+ color: colors.textMuted,
55
+ cursor: 'pointer',
56
+ },
57
+ backdrop: {
58
+ position: 'fixed',
59
+ top: 0,
60
+ left: 0,
61
+ right: 0,
62
+ bottom: 0,
63
+ zIndex: 49,
64
+ },
65
+ popup: {
66
+ position: 'absolute',
67
+ top: '100%',
68
+ left: 0,
69
+ right: 0,
70
+ marginTop: spacing.xs,
71
+ backgroundColor: colors.bgCard,
72
+ borderWidth: 1,
73
+ borderStyle: 'solid',
74
+ borderColor: colors.border,
75
+ borderRadius: radii.sm,
76
+ boxShadow: shadows.md,
77
+ paddingTop: spacing.xs,
78
+ paddingBottom: spacing.xs,
79
+ maxHeight: 200,
80
+ overflowY: 'auto',
81
+ zIndex: 50,
82
+ },
83
+ item: {
84
+ display: 'flex',
85
+ alignItems: 'center',
86
+ paddingTop: spacing.sm,
87
+ paddingBottom: spacing.sm,
88
+ paddingLeft: spacing.md,
89
+ paddingRight: spacing.md,
90
+ fontSize: typography.fontSizeSm,
91
+ fontFamily: typography.fontFamily,
92
+ color: colors.text,
93
+ cursor: 'pointer',
94
+ backgroundColor: 'transparent',
95
+ transitionProperty: 'background-color',
96
+ transitionDuration: '150ms',
97
+ },
98
+ itemSelected: {
99
+ color: colors.accent,
100
+ fontWeight: typography.fontWeightMedium,
101
+ },
102
+ itemHighlighted: {
103
+ backgroundColor: colors.bgCardHover,
104
+ },
105
+ itemHidden: {
106
+ display: 'none',
107
+ },
108
+ empty: {
109
+ paddingTop: spacing.sm,
110
+ paddingBottom: spacing.sm,
111
+ paddingLeft: spacing.md,
112
+ paddingRight: spacing.md,
113
+ fontSize: typography.fontSizeSm,
114
+ color: colors.textMuted,
115
+ },
116
+ hidden: {
117
+ display: 'none',
118
+ },
119
+ })