@duro-app/ui 0.24.0 → 0.26.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/dist/components/ButtonGroup/ButtonGroup.meta.d.ts.map +1 -1
- package/dist/components/CheckboxGroup/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/CheckboxGroup/CheckboxGroup.meta.d.ts.map +1 -1
- package/dist/components/Combobox/Combobox.d.ts +44 -0
- package/dist/components/Combobox/Combobox.d.ts.map +1 -0
- package/dist/components/Combobox/Combobox.meta.d.ts +3 -0
- package/dist/components/Combobox/Combobox.meta.d.ts.map +1 -0
- package/dist/components/Combobox/Combobox.stories.d.ts +9 -0
- package/dist/components/Combobox/Combobox.stories.d.ts.map +1 -0
- package/dist/components/Combobox/ComboboxContext.d.ts +19 -0
- package/dist/components/Combobox/ComboboxContext.d.ts.map +1 -0
- package/dist/components/Combobox/styles.css.d.ts +112 -0
- package/dist/components/Combobox/styles.css.d.ts.map +1 -0
- package/dist/components/Combobox/useComboboxRoot.d.ts +15 -0
- package/dist/components/Combobox/useComboboxRoot.d.ts.map +1 -0
- package/dist/components/Field/Field.d.ts +4 -0
- package/dist/components/Field/Field.d.ts.map +1 -1
- package/dist/components/Field/Field.stories.d.ts +3 -0
- package/dist/components/Field/Field.stories.d.ts.map +1 -1
- package/dist/components/Field/FieldContext.d.ts +5 -0
- package/dist/components/Field/FieldContext.d.ts.map +1 -1
- package/dist/components/Field/styles.css.d.ts +26 -0
- package/dist/components/Field/styles.css.d.ts.map +1 -1
- package/dist/components/Form/Form.d.ts +7 -2
- package/dist/components/Form/Form.d.ts.map +1 -1
- package/dist/components/Form/Form.stories.d.ts +6 -0
- package/dist/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/components/Form/FormContext.d.ts +9 -2
- package/dist/components/Form/FormContext.d.ts.map +1 -1
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.meta.d.ts.map +1 -1
- package/dist/components/Table/SortIndicator.d.ts.map +1 -1
- package/dist/components/Table/Table.d.ts.map +1 -1
- package/dist/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/components/Table/useDataTable.d.ts.map +1 -1
- package/dist/components/Tag/Tag.d.ts +18 -0
- package/dist/components/Tag/Tag.d.ts.map +1 -0
- package/dist/components/Tag/Tag.meta.d.ts +3 -0
- package/dist/components/Tag/Tag.meta.d.ts.map +1 -0
- package/dist/components/Tag/Tag.stories.d.ts +11 -0
- package/dist/components/Tag/Tag.stories.d.ts.map +1 -0
- package/dist/components/Tag/styles.css.d.ts +80 -0
- package/dist/components/Tag/styles.css.d.ts.map +1 -0
- package/dist/components/TagGroup/TagGroup.d.ts +30 -0
- package/dist/components/TagGroup/TagGroup.d.ts.map +1 -0
- package/dist/components/TagGroup/TagGroup.meta.d.ts +3 -0
- package/dist/components/TagGroup/TagGroup.meta.d.ts.map +1 -0
- package/dist/components/TagGroup/TagGroup.stories.d.ts +11 -0
- package/dist/components/TagGroup/TagGroup.stories.d.ts.map +1 -0
- package/dist/components/TagGroup/TagGroupContext.d.ts +17 -0
- package/dist/components/TagGroup/TagGroupContext.d.ts.map +1 -0
- package/dist/components/TagGroup/styles.css.d.ts +75 -0
- package/dist/components/TagGroup/styles.css.d.ts.map +1 -0
- package/dist/components/Textarea/Textarea.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3256 -2318
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ButtonGroup/ButtonGroup.meta.ts +10 -2
- package/src/components/CheckboxGroup/CheckboxGroup.meta.ts +4 -1
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +2 -9
- package/src/components/Combobox/Combobox.meta.ts +41 -0
- package/src/components/Combobox/Combobox.stories.tsx +157 -0
- package/src/components/Combobox/Combobox.tsx +235 -0
- package/src/components/Combobox/ComboboxContext.ts +26 -0
- package/src/components/Combobox/styles.css.ts +119 -0
- package/src/components/Combobox/useComboboxRoot.ts +235 -0
- package/src/components/Field/Field.stories.tsx +62 -0
- package/src/components/Field/Field.tsx +94 -9
- package/src/components/Field/FieldContext.ts +5 -0
- package/src/components/Field/styles.css.ts +26 -0
- package/src/components/Form/Form.stories.tsx +275 -0
- package/src/components/Form/Form.tsx +69 -7
- package/src/components/Form/FormContext.ts +10 -1
- package/src/components/Input/Input.tsx +2 -1
- package/src/components/RadioGroup/RadioGroup.meta.ts +4 -1
- package/src/components/RadioGroup/RadioGroup.tsx +1 -4
- package/src/components/Table/SortIndicator.tsx +1 -4
- package/src/components/Table/Table.stories.tsx +190 -84
- package/src/components/Table/Table.tsx +10 -5
- package/src/components/Table/useDataTable.ts +34 -48
- package/src/components/Tag/Tag.meta.ts +22 -0
- package/src/components/Tag/Tag.stories.tsx +92 -0
- package/src/components/Tag/Tag.tsx +139 -0
- package/src/components/Tag/styles.css.ts +99 -0
- package/src/components/TagGroup/TagGroup.meta.ts +27 -0
- package/src/components/TagGroup/TagGroup.stories.tsx +193 -0
- package/src/components/TagGroup/TagGroup.tsx +302 -0
- package/src/components/TagGroup/TagGroupContext.ts +22 -0
- package/src/components/TagGroup/styles.css.ts +97 -0
- package/src/components/Textarea/Textarea.tsx +2 -1
- package/src/index.ts +4 -0
- package/src/strict.css +1 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ReactNode,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useId,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react'
|
|
11
|
+
import {html} from 'react-strict-dom'
|
|
12
|
+
import {useControllableValue} from '../../hooks/useControllableValue'
|
|
13
|
+
import {useFieldContext} from '../Field/FieldContext'
|
|
14
|
+
import {TagGroupContext, type TagGroupContextValue} from './TagGroupContext'
|
|
15
|
+
import {styles} from './styles.css'
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Local context accessor (non-throwing, returns null)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
function useTagGroupCtx() {
|
|
22
|
+
return useContext(TagGroupContext)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// TagGroup.Root
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
interface RootProps {
|
|
30
|
+
value?: string[]
|
|
31
|
+
defaultValue?: string[]
|
|
32
|
+
onValueChange?: (values: string[]) => void
|
|
33
|
+
/** Validate a value before adding. Return `true` if valid, or an error string. */
|
|
34
|
+
onValidate?: (value: string) => true | string
|
|
35
|
+
disabled?: boolean
|
|
36
|
+
/** Form field name — renders hidden inputs for each tag value */
|
|
37
|
+
name?: string
|
|
38
|
+
'aria-label'?: string
|
|
39
|
+
children: ReactNode
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function Root({
|
|
43
|
+
value: controlledValue,
|
|
44
|
+
defaultValue = [],
|
|
45
|
+
onValueChange,
|
|
46
|
+
onValidate,
|
|
47
|
+
disabled: disabledProp = false,
|
|
48
|
+
name,
|
|
49
|
+
'aria-label': ariaLabel,
|
|
50
|
+
children,
|
|
51
|
+
}: RootProps) {
|
|
52
|
+
const gridId = useId()
|
|
53
|
+
const fieldCtx = useFieldContext()
|
|
54
|
+
const [values, setValues] = useControllableValue(controlledValue, defaultValue, onValueChange)
|
|
55
|
+
const [focusedIndex, setFocusedIndex] = useState(-1)
|
|
56
|
+
const [editable, setEditable] = useState(false)
|
|
57
|
+
const [announcement, setAnnouncement] = useState('')
|
|
58
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
59
|
+
|
|
60
|
+
const disabled = disabledProp || (fieldCtx?.disabled ?? false)
|
|
61
|
+
const isInvalid = fieldCtx?.invalid ?? false
|
|
62
|
+
|
|
63
|
+
const addTag = useCallback(
|
|
64
|
+
(value: string) => {
|
|
65
|
+
const trimmed = value.trim()
|
|
66
|
+
if (!trimmed) return
|
|
67
|
+
if (values.includes(trimmed)) return
|
|
68
|
+
if (onValidate) {
|
|
69
|
+
const result = onValidate(trimmed)
|
|
70
|
+
if (result !== true) return
|
|
71
|
+
}
|
|
72
|
+
setValues([...values, trimmed])
|
|
73
|
+
setAnnouncement(`Added ${trimmed}`)
|
|
74
|
+
},
|
|
75
|
+
[values, setValues, onValidate],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const removeTag = useCallback(
|
|
79
|
+
(value: string) => {
|
|
80
|
+
const idx = values.indexOf(value)
|
|
81
|
+
if (idx === -1) return
|
|
82
|
+
const next = values.filter((v) => v !== value)
|
|
83
|
+
setValues(next)
|
|
84
|
+
setAnnouncement(`Removed ${value}`)
|
|
85
|
+
|
|
86
|
+
// Focus management: move to next tag, or previous, or input
|
|
87
|
+
if (next.length === 0) {
|
|
88
|
+
inputRef.current?.focus()
|
|
89
|
+
setFocusedIndex(-1)
|
|
90
|
+
} else if (idx >= next.length) {
|
|
91
|
+
setFocusedIndex(next.length - 1)
|
|
92
|
+
} else {
|
|
93
|
+
setFocusedIndex(idx)
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[values, setValues],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Focus the tag at focusedIndex when it changes
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (focusedIndex >= 0 && focusedIndex < values.length) {
|
|
102
|
+
const container = document.getElementById(gridId)
|
|
103
|
+
if (!container) return
|
|
104
|
+
const rows = container.querySelectorAll('[role="row"]')
|
|
105
|
+
const target = rows[focusedIndex] as HTMLElement | undefined
|
|
106
|
+
target?.focus()
|
|
107
|
+
}
|
|
108
|
+
}, [focusedIndex, gridId, values.length])
|
|
109
|
+
|
|
110
|
+
const ctx = useMemo<TagGroupContextValue>(
|
|
111
|
+
() => ({
|
|
112
|
+
values,
|
|
113
|
+
addTag,
|
|
114
|
+
removeTag,
|
|
115
|
+
editable,
|
|
116
|
+
setEditable,
|
|
117
|
+
disabled,
|
|
118
|
+
validate: onValidate,
|
|
119
|
+
focusedIndex,
|
|
120
|
+
setFocusedIndex,
|
|
121
|
+
inputRef,
|
|
122
|
+
gridId,
|
|
123
|
+
name,
|
|
124
|
+
}),
|
|
125
|
+
[values, addTag, removeTag, editable, disabled, onValidate, focusedIndex, gridId, name],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<TagGroupContext.Provider value={ctx}>
|
|
130
|
+
<html.div style={styles.root} aria-label={ariaLabel}>
|
|
131
|
+
<html.div
|
|
132
|
+
style={[
|
|
133
|
+
styles.container,
|
|
134
|
+
!editable && styles.containerStatic,
|
|
135
|
+
isInvalid && styles.containerError,
|
|
136
|
+
disabled && styles.containerDisabled,
|
|
137
|
+
]}
|
|
138
|
+
aria-describedby={
|
|
139
|
+
fieldCtx
|
|
140
|
+
? `${fieldCtx.descriptionId} ${fieldCtx.invalid ? fieldCtx.errorId : ''}`.trim()
|
|
141
|
+
: undefined
|
|
142
|
+
}
|
|
143
|
+
aria-invalid={isInvalid || undefined}
|
|
144
|
+
>
|
|
145
|
+
{children}
|
|
146
|
+
</html.div>
|
|
147
|
+
|
|
148
|
+
{/* Hidden inputs for form submission */}
|
|
149
|
+
{name && values.map((v) => <html.input key={v} type="hidden" name={name} value={v} />)}
|
|
150
|
+
|
|
151
|
+
{/* Live region for screen reader announcements */}
|
|
152
|
+
<html.div role="status" aria-live="polite" aria-atomic style={styles.liveRegion}>
|
|
153
|
+
{announcement}
|
|
154
|
+
</html.div>
|
|
155
|
+
</html.div>
|
|
156
|
+
</TagGroupContext.Provider>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// TagGroup.List
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
interface ListProps {
|
|
165
|
+
children: ReactNode
|
|
166
|
+
'aria-label'?: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function List({children, 'aria-label': ariaLabel}: ListProps) {
|
|
170
|
+
const ctx = useTagGroupCtx()
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<html.div role={'grid' as any} aria-label={ariaLabel} id={ctx?.gridId} style={styles.list}>
|
|
174
|
+
{children}
|
|
175
|
+
</html.div>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// TagGroup.Input
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
interface InputProps {
|
|
184
|
+
placeholder?: string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function Input({placeholder}: InputProps) {
|
|
188
|
+
const ctx = useTagGroupCtx()
|
|
189
|
+
const [inputValue, setInputValue] = useState('')
|
|
190
|
+
const localRef = useRef<HTMLInputElement>(null)
|
|
191
|
+
|
|
192
|
+
// Sync local ref with context inputRef
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (ctx?.inputRef) {
|
|
195
|
+
;(ctx.inputRef as React.MutableRefObject<HTMLInputElement | null>).current = localRef.current
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Register as editable on mount
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
ctx?.setEditable(true)
|
|
202
|
+
return () => ctx?.setEditable(false)
|
|
203
|
+
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
|
204
|
+
|
|
205
|
+
const commitValueRef = useRef<(raw: string) => boolean>(() => false)
|
|
206
|
+
commitValueRef.current = (raw: string) => {
|
|
207
|
+
const trimmed = raw.trim()
|
|
208
|
+
if (!trimmed || !ctx) return false
|
|
209
|
+
ctx.addTag(trimmed)
|
|
210
|
+
setInputValue('')
|
|
211
|
+
return true
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Native keydown for full KeyboardEvent access (preventDefault)
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
const el = localRef.current
|
|
217
|
+
if (!el) return
|
|
218
|
+
|
|
219
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
220
|
+
if (!ctx) return
|
|
221
|
+
const target = e.target as HTMLInputElement
|
|
222
|
+
|
|
223
|
+
if (e.key === 'Enter' || e.key === ',' || e.key === ';') {
|
|
224
|
+
e.preventDefault()
|
|
225
|
+
commitValueRef.current(target.value)
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (e.key === 'Tab' && target.value.trim()) {
|
|
230
|
+
e.preventDefault()
|
|
231
|
+
commitValueRef.current(target.value)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (e.key === 'Backspace' && !target.value && ctx.values.length > 0) {
|
|
236
|
+
e.preventDefault()
|
|
237
|
+
ctx.removeTag(ctx.values[ctx.values.length - 1])
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (e.key === 'ArrowLeft' && (target.selectionStart ?? 0) === 0 && ctx.values.length > 0) {
|
|
242
|
+
e.preventDefault()
|
|
243
|
+
ctx.setFocusedIndex(ctx.values.length - 1)
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (e.key === 'Escape') {
|
|
248
|
+
setInputValue('')
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function handlePaste(e: ClipboardEvent) {
|
|
253
|
+
if (!ctx) return
|
|
254
|
+
const text = e.clipboardData?.getData('text') ?? ''
|
|
255
|
+
const items = text
|
|
256
|
+
.split(/[\n,;]+/)
|
|
257
|
+
.map((s) => s.trim())
|
|
258
|
+
.filter(Boolean)
|
|
259
|
+
if (items.length > 1) {
|
|
260
|
+
e.preventDefault()
|
|
261
|
+
for (const item of items) {
|
|
262
|
+
ctx.addTag(item)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function handleFocus() {
|
|
268
|
+
ctx?.setFocusedIndex(-1)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
el.addEventListener('keydown', handleKeyDown)
|
|
272
|
+
el.addEventListener('paste', handlePaste)
|
|
273
|
+
el.addEventListener('focus', handleFocus)
|
|
274
|
+
return () => {
|
|
275
|
+
el.removeEventListener('keydown', handleKeyDown)
|
|
276
|
+
el.removeEventListener('paste', handlePaste)
|
|
277
|
+
el.removeEventListener('focus', handleFocus)
|
|
278
|
+
}
|
|
279
|
+
}, [ctx])
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<html.input
|
|
283
|
+
ref={localRef as React.Ref<HTMLInputElement>}
|
|
284
|
+
type="text"
|
|
285
|
+
value={inputValue}
|
|
286
|
+
onChange={(e: any) => setInputValue(e.target.value)}
|
|
287
|
+
placeholder={!ctx?.values.length ? placeholder : undefined}
|
|
288
|
+
disabled={ctx?.disabled}
|
|
289
|
+
style={styles.input}
|
|
290
|
+
/>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Namespace export
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
export const TagGroup = {
|
|
299
|
+
Root,
|
|
300
|
+
List,
|
|
301
|
+
Input,
|
|
302
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {createContext, useContext} from 'react'
|
|
2
|
+
|
|
3
|
+
export interface TagGroupContextValue {
|
|
4
|
+
values: string[]
|
|
5
|
+
addTag: (value: string) => void
|
|
6
|
+
removeTag: (value: string) => void
|
|
7
|
+
editable: boolean
|
|
8
|
+
setEditable: (v: boolean) => void
|
|
9
|
+
disabled: boolean
|
|
10
|
+
validate?: (value: string) => true | string
|
|
11
|
+
focusedIndex: number
|
|
12
|
+
setFocusedIndex: (index: number) => void
|
|
13
|
+
inputRef: React.RefObject<HTMLInputElement | null>
|
|
14
|
+
gridId: string
|
|
15
|
+
name?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const TagGroupContext = createContext<TagGroupContextValue | null>(null)
|
|
19
|
+
|
|
20
|
+
export function useTagGroup(): TagGroupContextValue | null {
|
|
21
|
+
return useContext(TagGroupContext)
|
|
22
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
|
|
6
|
+
export const styles = css.create({
|
|
7
|
+
root: {
|
|
8
|
+
display: 'flex',
|
|
9
|
+
flexDirection: 'column',
|
|
10
|
+
},
|
|
11
|
+
container: {
|
|
12
|
+
display: 'flex',
|
|
13
|
+
flexWrap: 'wrap',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
gap: spacing.xs,
|
|
16
|
+
paddingTop: spacing.xs,
|
|
17
|
+
paddingBottom: spacing.xs,
|
|
18
|
+
paddingLeft: spacing.sm,
|
|
19
|
+
paddingRight: spacing.sm,
|
|
20
|
+
borderWidth: 1,
|
|
21
|
+
borderStyle: 'solid',
|
|
22
|
+
borderColor: {
|
|
23
|
+
default: colors.border,
|
|
24
|
+
':focus-within': colors.accent,
|
|
25
|
+
},
|
|
26
|
+
borderRadius: radii.sm,
|
|
27
|
+
backgroundColor: colors.bg,
|
|
28
|
+
minHeight: 40,
|
|
29
|
+
transitionProperty: 'border-color',
|
|
30
|
+
transitionDuration: '150ms',
|
|
31
|
+
transitionTimingFunction: 'ease',
|
|
32
|
+
outlineWidth: {
|
|
33
|
+
default: 0,
|
|
34
|
+
':focus-within': 2,
|
|
35
|
+
},
|
|
36
|
+
outlineStyle: {
|
|
37
|
+
default: 'none',
|
|
38
|
+
':focus-within': 'solid',
|
|
39
|
+
},
|
|
40
|
+
outlineColor: {
|
|
41
|
+
default: 'transparent',
|
|
42
|
+
':focus-within': colors.accent,
|
|
43
|
+
},
|
|
44
|
+
outlineOffset: {
|
|
45
|
+
default: 0,
|
|
46
|
+
':focus-within': 1,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
containerError: {
|
|
50
|
+
borderColor: {
|
|
51
|
+
default: colors.error,
|
|
52
|
+
':focus-within': colors.error,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
containerDisabled: {
|
|
56
|
+
opacity: 0.5,
|
|
57
|
+
cursor: 'not-allowed',
|
|
58
|
+
},
|
|
59
|
+
// Static display (no Input) — no input-field border
|
|
60
|
+
containerStatic: {
|
|
61
|
+
borderWidth: 0,
|
|
62
|
+
paddingTop: 0,
|
|
63
|
+
paddingBottom: 0,
|
|
64
|
+
paddingLeft: 0,
|
|
65
|
+
paddingRight: 0,
|
|
66
|
+
minHeight: 0,
|
|
67
|
+
backgroundColor: 'transparent',
|
|
68
|
+
outlineWidth: 0,
|
|
69
|
+
},
|
|
70
|
+
list: {
|
|
71
|
+
display: 'flex',
|
|
72
|
+
flexWrap: 'wrap',
|
|
73
|
+
gap: spacing.xs,
|
|
74
|
+
alignItems: 'center',
|
|
75
|
+
},
|
|
76
|
+
input: {
|
|
77
|
+
flex: 1,
|
|
78
|
+
minWidth: 120,
|
|
79
|
+
borderWidth: 0,
|
|
80
|
+
backgroundColor: 'transparent',
|
|
81
|
+
fontFamily: typography.fontFamily,
|
|
82
|
+
fontSize: typography.fontSizeSm,
|
|
83
|
+
lineHeight: typography.lineHeight,
|
|
84
|
+
color: colors.text,
|
|
85
|
+
outlineWidth: 0,
|
|
86
|
+
paddingTop: spacing.xs,
|
|
87
|
+
paddingBottom: spacing.xs,
|
|
88
|
+
paddingLeft: spacing.xs,
|
|
89
|
+
paddingRight: spacing.xs,
|
|
90
|
+
},
|
|
91
|
+
liveRegion: {
|
|
92
|
+
position: 'absolute',
|
|
93
|
+
width: 1,
|
|
94
|
+
height: 1,
|
|
95
|
+
overflow: 'hidden',
|
|
96
|
+
},
|
|
97
|
+
})
|
|
@@ -41,6 +41,7 @@ export function Textarea({
|
|
|
41
41
|
const effectiveOnChange = onChange ?? fieldCtx?.onChange
|
|
42
42
|
const effectiveOnBlur = onBlur ?? fieldCtx?.onBlur
|
|
43
43
|
const effectiveRef = ref ?? fieldCtx?.ref
|
|
44
|
+
const effectiveDisabled = disabled ?? ctx?.disabled
|
|
44
45
|
const effectiveVariant = ctx?.invalid ? 'error' : variant
|
|
45
46
|
|
|
46
47
|
return (
|
|
@@ -52,7 +53,7 @@ export function Textarea({
|
|
|
52
53
|
rows={rows}
|
|
53
54
|
value={effectiveValue}
|
|
54
55
|
defaultValue={defaultValue}
|
|
55
|
-
disabled={
|
|
56
|
+
disabled={effectiveDisabled}
|
|
56
57
|
aria-describedby={
|
|
57
58
|
ctx ? `${ctx.descriptionId} ${ctx.invalid ? ctx.errorId : ''}`.trim() : undefined
|
|
58
59
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export {Heading, type HeadingVariant} from './components/Heading/Heading'
|
|
|
21
21
|
export {List, type ListSelectionMode} from './components/List/List'
|
|
22
22
|
export {Field} from './components/Field/Field'
|
|
23
23
|
export {Form, type FormProps} from './components/Form/Form'
|
|
24
|
+
export type {LabelPosition, NecessityIndicator} from './components/Form/FormContext'
|
|
24
25
|
export {Fieldset, type FieldsetGap} from './components/Fieldset/Fieldset'
|
|
25
26
|
export {Input, type InputVariant} from './components/Input/Input'
|
|
26
27
|
export {InputGroup} from './components/InputGroup/InputGroup'
|
|
@@ -33,6 +34,7 @@ export {Menu} from './components/Menu/Menu'
|
|
|
33
34
|
export {Panel} from './components/Panel/Panel'
|
|
34
35
|
export {RadioGroup} from './components/RadioGroup/RadioGroup'
|
|
35
36
|
export {ScrollArea} from './components/ScrollArea/ScrollArea'
|
|
37
|
+
export {Combobox} from './components/Combobox/Combobox'
|
|
36
38
|
export {Select} from './components/Select/Select'
|
|
37
39
|
export {SideNav} from './components/SideNav/SideNav'
|
|
38
40
|
export {Spinner, type SpinnerSize} from './components/Spinner/Spinner'
|
|
@@ -41,6 +43,8 @@ export {
|
|
|
41
43
|
type StatusIconName,
|
|
42
44
|
type StatusIconVariant,
|
|
43
45
|
} from './components/StatusIcon/StatusIcon'
|
|
46
|
+
export {Tag, type TagVariant, type TagSize} from './components/Tag/Tag'
|
|
47
|
+
export {TagGroup} from './components/TagGroup/TagGroup'
|
|
44
48
|
export {Table, type TableVariant, type TableSize} from './components/Table/Table'
|
|
45
49
|
// TanStack Table: consumers install @tanstack/react-table directly and use
|
|
46
50
|
// Table.Pagination, Table.SortIndicator, Table.ColumnFilter for styled integration
|