@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
@@ -0,0 +1,235 @@
1
+ import {useState, useCallback, useMemo, useRef, useId, useEffect} from 'react'
2
+ import {useControllableValue} from '../../hooks/useControllableValue'
3
+ import type {ComboboxContextValue} from './ComboboxContext'
4
+
5
+ interface UseComboboxRootOptions {
6
+ defaultValue?: string
7
+ value?: string
8
+ onValueChange?: (value: string | null) => void
9
+ inputValue?: string
10
+ onInputChange?: (value: string) => void
11
+ initialLabels?: Record<string, string>
12
+ }
13
+
14
+ export function useComboboxRoot({
15
+ defaultValue,
16
+ value: controlledValue,
17
+ onValueChange,
18
+ inputValue: controlledInputValue,
19
+ onInputChange,
20
+ initialLabels,
21
+ }: UseComboboxRootOptions) {
22
+ const [value, setValue] = useControllableValue<string | null>(
23
+ controlledValue,
24
+ defaultValue ?? null,
25
+ onValueChange,
26
+ )
27
+ const [internalInputValue, setInternalInputValue] = useState('')
28
+ const inputValue = controlledInputValue !== undefined ? controlledInputValue : internalInputValue
29
+ const setInputValue = useCallback(
30
+ (v: string) => {
31
+ if (controlledInputValue === undefined) setInternalInputValue(v)
32
+ onInputChange?.(v)
33
+ },
34
+ [controlledInputValue, onInputChange],
35
+ )
36
+
37
+ const [open, setOpen] = useState(false)
38
+ const [labels, setLabels] = useState<Record<string, string>>(initialLabels ?? {})
39
+ const [highlightedId, setHighlightedId] = useState<string | null>(null)
40
+ const listboxId = useId()
41
+ const rootRef = useRef<HTMLDivElement>(null)
42
+ const inputRef = useRef<HTMLInputElement | null>(null)
43
+ const itemsRef = useRef(new Map<string, {value: string; element: HTMLElement}>())
44
+ const orderRef = useRef<string[]>([])
45
+
46
+ const close = useCallback(() => {
47
+ setOpen(false)
48
+ setHighlightedId(null)
49
+ }, [])
50
+
51
+ const openPopup = useCallback(() => {
52
+ setOpen(true)
53
+ // Highlight the selected item or first visible item
54
+ const items = itemsRef.current
55
+ const order = orderRef.current
56
+ let found: string | null = null
57
+ for (const id of order) {
58
+ const item = items.get(id)
59
+ if (item && item.value === value) {
60
+ found = id
61
+ break
62
+ }
63
+ }
64
+ setHighlightedId(found ?? (order.length > 0 ? order[0] : null))
65
+ }, [value])
66
+
67
+ const selectValue = useCallback(
68
+ (itemValue: string) => {
69
+ setValue(itemValue)
70
+ // Set input to show the selected label
71
+ const label = labels[itemValue] ?? itemValue
72
+ setInputValue(label)
73
+ close()
74
+ inputRef.current?.focus()
75
+ },
76
+ [setValue, labels, setInputValue, close],
77
+ )
78
+
79
+ const registerLabel = useCallback((v: string, label: string) => {
80
+ setLabels((prev) => {
81
+ if (prev[v] === label) return prev
82
+ return {...prev, [v]: label}
83
+ })
84
+ }, [])
85
+
86
+ const registerItem = useCallback((id: string, itemValue: string, element: HTMLElement) => {
87
+ itemsRef.current.set(id, {value: itemValue, element})
88
+ const map = itemsRef.current
89
+ const ids = [...map.keys()]
90
+ ids.sort((a, b) => {
91
+ const elA = map.get(a)?.element
92
+ const elB = map.get(b)?.element
93
+ if (!elA || !elB) return 0
94
+ return elA.compareDocumentPosition(elB) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
95
+ })
96
+ orderRef.current = ids
97
+ return () => {
98
+ itemsRef.current.delete(id)
99
+ orderRef.current = orderRef.current.filter((i) => i !== id)
100
+ }
101
+ }, [])
102
+
103
+ // Get visible items (those matching the filter)
104
+ const getVisibleOrder = useCallback(() => {
105
+ const query = inputValue.toLowerCase()
106
+ if (!query) return orderRef.current
107
+ return orderRef.current.filter((id) => {
108
+ const item = itemsRef.current.get(id)
109
+ if (!item) return false
110
+ const label = labels[item.value] ?? item.value
111
+ return label.toLowerCase().includes(query)
112
+ })
113
+ }, [inputValue, labels])
114
+
115
+ // Keyboard navigation
116
+ useEffect(() => {
117
+ const root = rootRef.current
118
+ if (!root || !open) return
119
+
120
+ function handleKeyDown(e: KeyboardEvent) {
121
+ const visibleOrder = getVisibleOrder()
122
+ if (visibleOrder.length === 0 && e.key !== 'Escape' && e.key !== 'Tab') return
123
+
124
+ switch (e.key) {
125
+ case 'ArrowDown': {
126
+ e.preventDefault()
127
+ if (!open) {
128
+ setOpen(true)
129
+ return
130
+ }
131
+ setHighlightedId((prev) => {
132
+ const idx = prev ? visibleOrder.indexOf(prev) : -1
133
+ return visibleOrder[(idx + 1) % visibleOrder.length] ?? null
134
+ })
135
+ break
136
+ }
137
+ case 'ArrowUp': {
138
+ e.preventDefault()
139
+ setHighlightedId((prev) => {
140
+ const idx = prev ? visibleOrder.indexOf(prev) : 0
141
+ return visibleOrder[(idx - 1 + visibleOrder.length) % visibleOrder.length] ?? null
142
+ })
143
+ break
144
+ }
145
+ case 'Home': {
146
+ e.preventDefault()
147
+ setHighlightedId(visibleOrder[0] ?? null)
148
+ break
149
+ }
150
+ case 'End': {
151
+ e.preventDefault()
152
+ setHighlightedId(visibleOrder[visibleOrder.length - 1] ?? null)
153
+ break
154
+ }
155
+ case 'Enter': {
156
+ e.preventDefault()
157
+ setHighlightedId((prev) => {
158
+ if (prev) {
159
+ const item = itemsRef.current.get(prev)
160
+ if (item) selectValue(item.value)
161
+ } else if (!inputValue.trim()) {
162
+ // Empty input + Enter = clear selection (reset to "All")
163
+ selectValue('')
164
+ }
165
+ return prev
166
+ })
167
+ break
168
+ }
169
+ case 'Escape': {
170
+ e.preventDefault()
171
+ close()
172
+ inputRef.current?.focus()
173
+ break
174
+ }
175
+ case 'Tab': {
176
+ close()
177
+ break
178
+ }
179
+ }
180
+ }
181
+
182
+ root.addEventListener('keydown', handleKeyDown)
183
+ return () => root.removeEventListener('keydown', handleKeyDown)
184
+ }, [open, close, selectValue, getVisibleOrder])
185
+
186
+ // Scroll highlighted item into view
187
+ useEffect(() => {
188
+ if (!highlightedId) return
189
+ const item = itemsRef.current.get(highlightedId)
190
+ item?.element.scrollIntoView({block: 'nearest'})
191
+ }, [highlightedId])
192
+
193
+ // Sync input value with selected label when value changes externally
194
+ useEffect(() => {
195
+ if (value && labels[value] && !open) {
196
+ setInputValue(labels[value])
197
+ }
198
+ }, [value, labels, open, setInputValue])
199
+
200
+ const ctx: ComboboxContextValue = useMemo(
201
+ () => ({
202
+ open,
203
+ openPopup,
204
+ close,
205
+ value,
206
+ setValue: selectValue,
207
+ inputValue,
208
+ setInputValue,
209
+ labels,
210
+ registerLabel,
211
+ listboxId,
212
+ highlightedId,
213
+ setHighlightedId,
214
+ registerItem,
215
+ inputRef,
216
+ }),
217
+ [
218
+ open,
219
+ openPopup,
220
+ close,
221
+ value,
222
+ selectValue,
223
+ inputValue,
224
+ setInputValue,
225
+ labels,
226
+ registerLabel,
227
+ listboxId,
228
+ highlightedId,
229
+ setHighlightedId,
230
+ registerItem,
231
+ ],
232
+ )
233
+
234
+ return {ctx, rootRef}
235
+ }
@@ -63,6 +63,7 @@ export const WithError: Story = {
63
63
 
64
64
  const stackStyles = css.create({
65
65
  stack: {display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 400},
66
+ stackWide: {display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 600},
66
67
  })
67
68
 
68
69
  export const FormExample: Story = {
@@ -96,3 +97,62 @@ export const FormExample: Story = {
96
97
  await expect(alert).toHaveTextContent('Passwords do not match.')
97
98
  },
98
99
  }
100
+
101
+ export const WithNecessityIcon: Story = {
102
+ name: 'With necessity indicator (standalone)',
103
+ render: () => (
104
+ <html.div style={stackStyles.stack}>
105
+ <Field.Root required>
106
+ <Field.Label>Email</Field.Label>
107
+ <Input placeholder="you@example.com" />
108
+ <Field.Description>Required field with no Form context — indicator needs Form.</Field.Description>
109
+ </Field.Root>
110
+ </html.div>
111
+ ),
112
+ play: async ({canvas}) => {
113
+ // Without Form context, no indicator shows (necessityIndicator comes from Form)
114
+ const label = canvas.getByText('Email')
115
+ await expect(label).toBeInTheDocument()
116
+ },
117
+ }
118
+
119
+ export const LabelSide: Story = {
120
+ name: 'Label position — side (standalone)',
121
+ render: () => (
122
+ <html.div style={stackStyles.stackWide}>
123
+ <Field.Root labelPosition="side">
124
+ <Field.Label>Username</Field.Label>
125
+ <Input placeholder="Enter username" />
126
+ <Field.Description>3-32 characters, letters and numbers only.</Field.Description>
127
+ </Field.Root>
128
+
129
+ <Field.Root labelPosition="side" invalid>
130
+ <Field.Label>Email</Field.Label>
131
+ <Input variant="error" placeholder="Enter email" />
132
+ <Field.Error>Please enter a valid email address.</Field.Error>
133
+ </Field.Root>
134
+ </html.div>
135
+ ),
136
+ play: async ({canvas}) => {
137
+ await expect(canvas.getByPlaceholderText('Enter username')).toBeInTheDocument()
138
+ await expect(canvas.getByPlaceholderText('Enter email')).toBeInTheDocument()
139
+ await expect(canvas.getByRole('alert')).toHaveTextContent('Please enter a valid email address.')
140
+ },
141
+ }
142
+
143
+ export const Disabled: Story = {
144
+ name: 'Disabled field (standalone)',
145
+ render: () => (
146
+ <html.div style={stackStyles.stack}>
147
+ <Field.Root disabled>
148
+ <Field.Label>Username</Field.Label>
149
+ <Input placeholder="Enter username" />
150
+ <Field.Description>This field is disabled.</Field.Description>
151
+ </Field.Root>
152
+ </html.div>
153
+ ),
154
+ play: async ({canvas}) => {
155
+ const input = canvas.getByPlaceholderText('Enter username')
156
+ await expect(input).toBeDisabled()
157
+ },
158
+ }
@@ -1,21 +1,25 @@
1
- import {type ReactNode, useId, useMemo} from 'react'
1
+ import {type ReactNode, Children, isValidElement, useId, useMemo} from 'react'
2
2
  import {html} from 'react-strict-dom'
3
3
  import {useFormContext as useRHFFormContext, useController} from 'react-hook-form'
4
4
  import {FieldContext, useFieldContext} from './FieldContext'
5
5
  import {useFormContext} from '../Form/FormContext'
6
+ import type {LabelPosition, NecessityIndicator} from '../Form/FormContext'
6
7
  import {styles} from './styles.css'
7
8
 
8
9
  // --- Root ---
9
10
  interface RootProps {
10
11
  name?: string
11
12
  invalid?: boolean
13
+ required?: boolean
14
+ disabled?: boolean
15
+ labelPosition?: LabelPosition
12
16
  children: ReactNode
13
17
  }
14
18
 
15
19
  function Root({name, ...props}: RootProps) {
16
- const insideForm = useFormContext()
17
- if (name && insideForm) {
18
- return <ControlledRoot name={name} {...props} />
20
+ const formCtx = useFormContext()
21
+ if (name && formCtx) {
22
+ return <ControlledRoot name={name} formCtx={formCtx} {...props} />
19
23
  }
20
24
  return <StaticRoot {...props} />
21
25
  }
@@ -23,11 +27,19 @@ function Root({name, ...props}: RootProps) {
23
27
  // Always calls useController — no conditional hooks
24
28
  function ControlledRoot({
25
29
  name,
30
+ formCtx,
26
31
  invalid: invalidProp = false,
32
+ required,
33
+ disabled,
34
+ labelPosition,
27
35
  children,
28
36
  }: {
29
37
  name: string
38
+ formCtx: NonNullable<ReturnType<typeof useFormContext>>
30
39
  invalid?: boolean
40
+ required?: boolean
41
+ disabled?: boolean
42
+ labelPosition?: LabelPosition
31
43
  children: ReactNode
32
44
  }) {
33
45
  const id = useId()
@@ -35,12 +47,20 @@ function ControlledRoot({
35
47
  const {field, fieldState} = useController({control, name})
36
48
  const invalid = invalidProp || !!fieldState.error
37
49
 
50
+ const effectiveDisabled = disabled ?? formCtx.disabled
51
+ const effectiveLabelPosition = labelPosition ?? formCtx.labelPosition
52
+ const effectiveNecessityIndicator = formCtx.necessityIndicator
53
+
38
54
  const ctx = useMemo(
39
55
  () => ({
40
56
  controlId: `${id}-control`,
41
57
  descriptionId: `${id}-description`,
42
58
  errorId: `${id}-error`,
43
59
  invalid,
60
+ required,
61
+ disabled: effectiveDisabled,
62
+ labelPosition: effectiveLabelPosition,
63
+ necessityIndicator: effectiveNecessityIndicator,
44
64
  field: {
45
65
  value: field.value,
46
66
  onChange: field.onChange,
@@ -53,6 +73,10 @@ function ControlledRoot({
53
73
  [
54
74
  id,
55
75
  invalid,
76
+ required,
77
+ effectiveDisabled,
78
+ effectiveLabelPosition,
79
+ effectiveNecessityIndicator,
56
80
  field.value,
57
81
  field.onChange,
58
82
  field.onBlur,
@@ -64,13 +88,25 @@ function ControlledRoot({
64
88
 
65
89
  return (
66
90
  <FieldContext.Provider value={ctx}>
67
- <html.div style={styles.root}>{children}</html.div>
91
+ <FieldLayout labelPosition={effectiveLabelPosition}>{children}</FieldLayout>
68
92
  </FieldContext.Provider>
69
93
  )
70
94
  }
71
95
 
72
96
  // Current behavior, no RHF dependency
73
- function StaticRoot({invalid = false, children}: {invalid?: boolean; children: ReactNode}) {
97
+ function StaticRoot({
98
+ invalid = false,
99
+ required,
100
+ disabled,
101
+ labelPosition = 'top',
102
+ children,
103
+ }: {
104
+ invalid?: boolean
105
+ required?: boolean
106
+ disabled?: boolean
107
+ labelPosition?: LabelPosition
108
+ children: ReactNode
109
+ }) {
74
110
  const id = useId()
75
111
  const ctx = useMemo(
76
112
  () => ({
@@ -78,17 +114,53 @@ function StaticRoot({invalid = false, children}: {invalid?: boolean; children: R
78
114
  descriptionId: `${id}-description`,
79
115
  errorId: `${id}-error`,
80
116
  invalid,
117
+ required,
118
+ disabled,
119
+ labelPosition,
81
120
  }),
82
- [id, invalid],
121
+ [id, invalid, required, disabled, labelPosition],
83
122
  )
84
123
 
85
124
  return (
86
125
  <FieldContext.Provider value={ctx}>
87
- <html.div style={styles.root}>{children}</html.div>
126
+ <FieldLayout labelPosition={labelPosition}>{children}</FieldLayout>
88
127
  </FieldContext.Provider>
89
128
  )
90
129
  }
91
130
 
131
+ // --- FieldLayout ---
132
+ // When labelPosition is 'side', separates Label from other children
133
+ // and wraps the non-label children in a content column.
134
+ function FieldLayout({
135
+ labelPosition,
136
+ children,
137
+ }: {
138
+ labelPosition: LabelPosition
139
+ children: ReactNode
140
+ }) {
141
+ if (labelPosition !== 'side') {
142
+ return <html.div style={styles.root}>{children}</html.div>
143
+ }
144
+
145
+ let labelNode: ReactNode = null
146
+ const rest: ReactNode[] = []
147
+
148
+ Children.forEach(children, (child) => {
149
+ if (isValidElement(child) && child.type === Label && !labelNode) {
150
+ labelNode = child
151
+ } else {
152
+ rest.push(child)
153
+ }
154
+ })
155
+
156
+ return (
157
+ <html.div style={styles.rootSide}>
158
+ {labelNode}
159
+ <html.div style={styles.fieldContent}>{rest}</html.div>
160
+ </html.div>
161
+ )
162
+ }
163
+
92
164
  // --- Label ---
93
165
  interface LabelProps {
94
166
  children: ReactNode
@@ -96,9 +168,25 @@ interface LabelProps {
96
168
 
97
169
  function Label({children}: LabelProps) {
98
170
  const ctx = useFieldContext()
171
+ const isSide = ctx?.labelPosition === 'side'
172
+ const indicator = ctx?.necessityIndicator
173
+
99
174
  return (
100
- <html.label for={ctx?.controlId} style={styles.label}>
175
+ <html.label
176
+ for={ctx?.controlId}
177
+ style={[styles.label, isSide && styles.labelSide]}
178
+ >
101
179
  {children}
180
+ {indicator === 'icon' && ctx?.required && (
181
+ <html.span style={styles.necessityIcon} aria-hidden={true}>
182
+ {' *'}
183
+ </html.span>
184
+ )}
185
+ {indicator === 'label' && (
186
+ <html.span style={styles.necessityLabel}>
187
+ {ctx?.required ? ' (required)' : ' (optional)'}
188
+ </html.span>
189
+ )}
102
190
  </html.label>
103
191
  )
104
192
  }
@@ -1,10 +1,15 @@
1
1
  import {createContext, useContext} from 'react'
2
+ import type {LabelPosition, NecessityIndicator} from '../Form/FormContext'
2
3
 
3
4
  interface FieldContextValue {
4
5
  controlId: string
5
6
  descriptionId: string
6
7
  errorId: string
7
8
  invalid: boolean
9
+ required?: boolean
10
+ disabled?: boolean
11
+ labelPosition?: LabelPosition
12
+ necessityIndicator?: NecessityIndicator
8
13
  field?: {
9
14
  value: unknown
10
15
  onChange: (...event: unknown[]) => void
@@ -9,11 +9,22 @@ export const styles = css.create({
9
9
  flexDirection: 'column',
10
10
  gap: spacing.xs,
11
11
  },
12
+ rootSide: {
13
+ display: 'flex',
14
+ flexDirection: 'row',
15
+ alignItems: 'flex-start',
16
+ gap: spacing.md,
17
+ },
12
18
  label: {
13
19
  fontSize: typography.fontSizeSm,
14
20
  fontWeight: typography.fontWeightMedium,
15
21
  color: colors.text,
16
22
  },
23
+ labelSide: {
24
+ minWidth: 120,
25
+ paddingTop: spacing.sm,
26
+ flexShrink: 0,
27
+ },
17
28
  description: {
18
29
  fontSize: typography.fontSizeXs,
19
30
  color: colors.textMuted,
@@ -22,4 +33,19 @@ export const styles = css.create({
22
33
  fontSize: typography.fontSizeXs,
23
34
  color: colors.error,
24
35
  },
36
+ fieldContent: {
37
+ display: 'flex',
38
+ flexDirection: 'column',
39
+ gap: spacing.xs,
40
+ flex: 1,
41
+ minWidth: 0,
42
+ },
43
+ necessityIcon: {
44
+ color: colors.error,
45
+ },
46
+ necessityLabel: {
47
+ color: colors.textMuted,
48
+ fontWeight: typography.fontWeightNormal,
49
+ fontSize: typography.fontSizeXs,
50
+ },
25
51
  })