@cdx-ui/components 0.3.3 → 0.3.4

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 (65) hide show
  1. package/coverage.svg +1 -1
  2. package/docs/components/color-field/content.md +2 -2
  3. package/docs/components/content.md +4 -4
  4. package/docs/components/date-field/content.md +2 -2
  5. package/docs/components/date-time-field/content.md +2 -2
  6. package/docs/components/input/content.md +20 -2
  7. package/docs/components/input/design.md +1 -1
  8. package/docs/components/insurance-disclosure/content.md +1 -1
  9. package/docs/components/otp-input/content.md +2 -2
  10. package/docs/components/select/content.md +223 -24
  11. package/docs/components/text/content.md +97 -0
  12. package/docs/llms.txt +5 -4
  13. package/docs/patterns/forms/content.md +152 -0
  14. package/docs/resources/release-notes/0.3.3/content.md +23 -0
  15. package/docs/resources/release-notes/content.md +1 -0
  16. package/lib/commonjs/components/Drawer/index.js +1 -1
  17. package/lib/commonjs/components/Drawer/index.js.map +1 -1
  18. package/lib/commonjs/components/Input/index.js.map +1 -1
  19. package/lib/commonjs/components/Input/styles.js +13 -6
  20. package/lib/commonjs/components/Input/styles.js.map +1 -1
  21. package/lib/commonjs/components/OtpInput/index.js.map +1 -1
  22. package/lib/commonjs/components/Select/index.js +326 -24
  23. package/lib/commonjs/components/Select/index.js.map +1 -1
  24. package/lib/commonjs/components/Select/styles.js +32 -1
  25. package/lib/commonjs/components/Select/styles.js.map +1 -1
  26. package/lib/commonjs/components/picker/surfaces.js +18 -3
  27. package/lib/commonjs/components/picker/surfaces.js.map +1 -1
  28. package/lib/module/components/Drawer/index.js +1 -1
  29. package/lib/module/components/Drawer/index.js.map +1 -1
  30. package/lib/module/components/Input/index.js.map +1 -1
  31. package/lib/module/components/Input/styles.js +14 -6
  32. package/lib/module/components/Input/styles.js.map +1 -1
  33. package/lib/module/components/OtpInput/index.js.map +1 -1
  34. package/lib/module/components/Select/index.js +328 -26
  35. package/lib/module/components/Select/index.js.map +1 -1
  36. package/lib/module/components/Select/styles.js +31 -0
  37. package/lib/module/components/Select/styles.js.map +1 -1
  38. package/lib/module/components/picker/surfaces.js +17 -3
  39. package/lib/module/components/picker/surfaces.js.map +1 -1
  40. package/lib/typescript/components/ColorField/styles.d.ts +4 -2
  41. package/lib/typescript/components/ColorField/styles.d.ts.map +1 -1
  42. package/lib/typescript/components/DateField/styles.d.ts +4 -2
  43. package/lib/typescript/components/DateField/styles.d.ts.map +1 -1
  44. package/lib/typescript/components/Input/index.d.ts +3 -2
  45. package/lib/typescript/components/Input/index.d.ts.map +1 -1
  46. package/lib/typescript/components/Input/styles.d.ts +1 -1
  47. package/lib/typescript/components/Input/styles.d.ts.map +1 -1
  48. package/lib/typescript/components/OtpInput/index.d.ts +3 -2
  49. package/lib/typescript/components/OtpInput/index.d.ts.map +1 -1
  50. package/lib/typescript/components/Select/index.d.ts +73 -6
  51. package/lib/typescript/components/Select/index.d.ts.map +1 -1
  52. package/lib/typescript/components/Select/styles.d.ts +9 -0
  53. package/lib/typescript/components/Select/styles.d.ts.map +1 -1
  54. package/lib/typescript/components/picker/surfaces.d.ts +14 -3
  55. package/lib/typescript/components/picker/surfaces.d.ts.map +1 -1
  56. package/package.json +5 -5
  57. package/src/components/ColorField/styles.ts +3 -1
  58. package/src/components/DateField/styles.ts +6 -1
  59. package/src/components/Drawer/index.tsx +1 -1
  60. package/src/components/Input/index.tsx +3 -2
  61. package/src/components/Input/styles.ts +35 -15
  62. package/src/components/OtpInput/index.tsx +3 -2
  63. package/src/components/Select/index.tsx +411 -29
  64. package/src/components/Select/styles.ts +36 -0
  65. package/src/components/picker/surfaces.tsx +29 -4
package/coverage.svg CHANGED
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 92.9%"><title>coverage: 92.9%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#4b0"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="835" y="150" fill-opacity=".8" filter="url(#blur)" textLength="370">92.9%</text><text x="835" y="150" fill-opacity=".3" textLength="370">92.9%</text></g><text x="835" y="140" textLength="370">92.9%</text></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 93.1%"><title>coverage: 93.1%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#4b0"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="835" y="150" fill-opacity=".8" filter="url(#blur)" textLength="370">93.1%</text><text x="835" y="150" fill-opacity=".3" textLength="370">93.1%</text></g><text x="835" y="140" textLength="370">93.1%</text></g></g></svg>
@@ -53,7 +53,7 @@ Owns standalone text and color state or edits the shared value when nested in `C
53
53
  | --- | --- | --- | --- |
54
54
  | `className?` | `string` | — | — |
55
55
  | `children?` | `ReactNode` | — | — |
56
- | `variant?` | `'outline' \| null` | — | — |
56
+ | `variant?` | `'outline'` | — | — |
57
57
  | `size?` | `'default' \| 'small' \| null` | — | — |
58
58
  | `value?` | `Color` | — | — |
59
59
  | `defaultValue?` | `Color` | — | — |
@@ -79,7 +79,7 @@ Provides the styled input shell and optional slots.
79
79
  | --- | --- | --- | --- |
80
80
  | `className?` | `string` | — | — |
81
81
  | `children?` | `ReactNode` | — | — |
82
- | `variant?` | `'outline' \| null` | — | — |
82
+ | `variant?` | `'outline'` | — | — |
83
83
  | `size?` | `'default' \| 'small' \| null` | — | — |
84
84
  | `asChild?` | `boolean` | — | — |
85
85
  | `fullWidth?` | `boolean` | — | — |
@@ -14,12 +14,12 @@
14
14
  - [Currency Input](https://design.candescent.com/docs/components/currency-input/content.md) — A calculator-style monetary input that formats digits as currency while the user types.
15
15
  - [Field](https://design.candescent.com/docs/components/field/content.md) — A form-control wrapper that coordinates labels, supporting text, validation messages, and shared state.
16
16
  - [Form](https://design.candescent.com/docs/components/form/content.md) — A container that spaces related fields vertically and renders a form on web or a view on native.
17
- - [Input Field](https://design.candescent.com/docs/components/input/content.md) — A single-line text entry with optional label, helper text, and adornments.
17
+ - [Input](https://design.candescent.com/docs/components/input/content.md) — A single-line text entry with optional label, helper text, and adornments.
18
18
  - [Menu](https://design.candescent.com/docs/components/menu/content.md) — Menus present actions or selectable options in an anchored overlay or a standalone list surface.
19
- - [OTP Input Field](https://design.candescent.com/docs/components/otp-input/content.md) — A controlled multi-cell input for one-time passwords and verification codes.
19
+ - [OTP Input](https://design.candescent.com/docs/components/otp-input/content.md) — A controlled multi-cell input for one-time passwords and verification codes.
20
20
  - [Radio](https://design.candescent.com/docs/components/radio/content.md) — A radio group is an input control that allows the user to select a single option from a set of mutually exclusive options.
21
21
  - [Range Calendar](https://design.candescent.com/docs/components/range-calendar/content.md) — A two-tap day grid for selecting a start and end date, with a continuous range band, month navigation, and a year picker.
22
- - [Select](https://design.candescent.com/docs/components/select/content.md) — A form control that allows users to choose one option from a dropdown list.
22
+ - [Select](https://design.candescent.com/docs/components/select/content.md) — A form control that allows users to choose one or more options from a dropdown list.
23
23
  - [Textarea](https://design.candescent.com/docs/components/textarea/content.md) — A multi-line text entry for longer free-form content.
24
24
  - [Slider](https://design.candescent.com/docs/components/slider/content.md) — A control for selecting a numeric value along a track.
25
25
  - [Color Picker](https://design.candescent.com/docs/components/color-picker/content.md) — A compound color picker that coordinates an opaque color value with a trigger and popover.
@@ -58,7 +58,7 @@
58
58
  - [Avatar](https://design.candescent.com/docs/components/avatar/content.md) — A visual representation of a user or entity, displayed as an image, initials, or fallback icon.
59
59
  - [Carousel](https://design.candescent.com/docs/components/carousel/content.md) — A swipeable, paginated carousel for promotional banners, onboarding, and card browsing. Wraps react-native-reanimated-carousel with token-styled Pagination and Prev/Next nav button compounds plus useCarousel hook.
60
60
  - [Divider](https://design.candescent.com/docs/components/divider/content.md) — A horizontal or vertical separator for grouping related content, with an optional inline label.
61
- - [FDIC Digital Sign](https://design.candescent.com/docs/components/insurance-disclosure/content.md) — Domain-specific regulatory disclosure — official FDIC lockup with optional copy for account footers, deposit screens, and onboarding.
61
+ - [Insurance Disclosure](https://design.candescent.com/docs/components/insurance-disclosure/content.md) — Domain-specific regulatory disclosure — official FDIC lockup with optional copy for account footers, deposit screens, and onboarding.
62
62
  - [Icon](https://design.candescent.com/docs/components/icon/content.md) — A cross-platform SVG icon wrapper with semantic color, sizing, filled variants, and accessibility support.
63
63
  - [Image](https://design.candescent.com/docs/components/image/content.md) — Displays local or remote images across native and web platforms.
64
64
  - [List Item](https://design.candescent.com/docs/components/list-item/content.md) — A composable row for display and navigation — accounts, transactions, alerts, settings, and nav rows.
@@ -74,7 +74,7 @@ Root of the compound. Owns the value, `mode`, and `locale`, and provides `varian
74
74
  | --- | --- | --- | --- |
75
75
  | `className?` | `string` | — | — |
76
76
  | `children?` | `ReactNode` | — | — |
77
- | `variant?` | `'outline' \| null` | — | — |
77
+ | `variant?` | `'outline'` | — | — |
78
78
  | `size?` | `'default' \| 'small' \| null` | — | — |
79
79
  | `fullWidth?` | `boolean` | — | When true (the default), the group stretches to fill its container; when false, it hugs the segments. |
80
80
  | `labels?` | `Partial<DateComponentLabels>` | — | Overrides the accessible labels announced for each segment. |
@@ -101,7 +101,7 @@ Input chrome that surrounds the segments, sharing `Input`'s outline styling. Add
101
101
  | --- | --- | --- | --- |
102
102
  | `className?` | `string` | — | — |
103
103
  | `children?` | `ReactNode` | — | — |
104
- | `variant?` | `'outline' \| null` | — | — |
104
+ | `variant?` | `'outline'` | — | — |
105
105
  | `size?` | `'default' \| 'small' \| null` | — | — |
106
106
  | `fullWidth?` | `boolean` | — | — |
107
107
 
@@ -80,7 +80,7 @@ Root of the compound. Owns the value, `granularity`, `hour12`, and `locale`, and
80
80
  | --- | --- | --- | --- |
81
81
  | `className?` | `string` | — | — |
82
82
  | `children?` | `ReactNode` | — | — |
83
- | `variant?` | `'outline' \| null` | — | — |
83
+ | `variant?` | `'outline'` | — | — |
84
84
  | `size?` | `'default' \| 'small' \| null` | — | — |
85
85
  | `fullWidth?` | `boolean` | — | When true (the default), the group stretches to fill its container; when false, it hugs the segments. |
86
86
  | `asChild?` | `boolean` | — | — |
@@ -109,7 +109,7 @@ Input chrome that surrounds the segments, shared with [`DateField.Group`](https:
109
109
  | `className?` | `string` | — | — |
110
110
  | `children?` | `ReactNode` | — | — |
111
111
  | `ref?` | `React.Ref<View>` | — | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). |
112
- | `variant?` | `'outline' \| null` | — | — |
112
+ | `variant?` | `'outline'` | — | — |
113
113
  | `size?` | `'default' \| 'small' \| null` | — | — |
114
114
  | `fullWidth?` | `boolean` | — | — |
115
115
  | `key?` | `React.Key \| null` | — | — |
@@ -1,4 +1,4 @@
1
- # Input Field (https://design.candescent.com/docs/components/input)
1
+ # Input (https://design.candescent.com/docs/components/input)
2
2
 
3
3
  Resources: [Figma](https://www.figma.com/design/0lEVMVlLj8ZS4fgItr3pmd/%E2%AD%90-CDS-V2--React-Native?node-id=159-22), [Storybook](https://victorious-wave-0daadf00f.6.azurestaticapps.net/?path=/story/inputs-input--default)
4
4
 
@@ -22,6 +22,24 @@ export default function InputBasicDemo() {
22
22
  }
23
23
  ```
24
24
 
25
+ ## Subtle
26
+
27
+ Use `variant="subtle"` for a filled, borderless field on a surface that already frames it, such as a search field at the top of a popover or sheet. Focus, invalid, disabled, and read-only states stay visible in both variants.
28
+
29
+ ```tsx
30
+ import { Input } from '@cdx-ui/components';
31
+ import { Search } from '@cdx-ui/icons';
32
+
33
+ export default function InputSubtleDemo() {
34
+ return (
35
+ <Input variant="subtle">
36
+ <Input.Icon as={Search} />
37
+ <Input.Field accessibilityLabel="Search teams" placeholder="Search teams" />
38
+ </Input>
39
+ );
40
+ }
41
+ ```
42
+
25
43
  ## Sizes
26
44
 
27
45
  ```tsx
@@ -266,7 +284,7 @@ The root shell that lays out the field alongside optional slots and icons, and p
266
284
  | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the shell's own styles. |
267
285
  | `children?` | `ReactNode` | — | Shell content — typically `Input.Field` plus optional `Input.Slot`/`Input.Icon`. |
268
286
  | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `View` host. |
269
- | `variant?` | `'outline'` | `'outline'` | Visual style variant. |
287
+ | `variant?` | `'outline' \| 'subtle'` | `'outline'` | Visual style variant. `outline` is a bordered field on the page surface; `subtle` is a filled, borderless field for dense or nested surfaces such as a search field in a popup. |
270
288
  | `size?` | `'default' \| 'small'` | `'default'` | Height, padding, and field font size. |
271
289
  | `isInvalid?` | `boolean` | — | If true, the input will indicate an error. |
272
290
  | `isDisabled?` | `boolean` | — | If true, the input will be disabled. |
@@ -1,4 +1,4 @@
1
- # Input Field — Design (https://design.candescent.com/docs/components/input)
1
+ # Input — Design (https://design.candescent.com/docs/components/input)
2
2
 
3
3
  ## Usage
4
4
 
@@ -1,4 +1,4 @@
1
- # FDIC Digital Sign (https://design.candescent.com/docs/components/insurance-disclosure)
1
+ # Insurance Disclosure (https://design.candescent.com/docs/components/insurance-disclosure)
2
2
 
3
3
  Resources: [Figma](https://www.figma.com/design/0lEVMVlLj8ZS4fgItr3pmd/%E2%AD%90-Forge-DS--Components?node-id=8550-3291), [Storybook](https://victorious-wave-0daadf00f.6.azurestaticapps.net/?path=/story/domain-insurancedisclosure--default)
4
4
 
@@ -1,4 +1,4 @@
1
- # OTP Input Field (https://design.candescent.com/docs/components/otp-input)
1
+ # OTP Input (https://design.candescent.com/docs/components/otp-input)
2
2
 
3
3
  Resources: [Figma](https://www.figma.com/design/0lEVMVlLj8ZS4fgItr3pmd/%E2%AD%90-Forge-DS--Components?node-id=5653-5035), [Storybook](https://victorious-wave-0daadf00f.6.azurestaticapps.net/?path=/story/inputs-otpinput--default)
4
4
 
@@ -298,7 +298,7 @@ Styled cell chrome built on `Input`. Inherits variant and size from the parent `
298
298
  | --- | --- | --- | --- |
299
299
  | `className?` | `string` | — | — |
300
300
  | `children?` | `React.ReactNode` | — | — |
301
- | `variant?` | `'outline' \| null` | — | — |
301
+ | `variant?` | `'outline'` | — | — |
302
302
  | `size?` | `'default' \| 'small' \| null` | — | — |
303
303
  | `isInvalid?` | `boolean` | — | — |
304
304
  | `isReadOnly?` | `boolean` | — | — |
@@ -107,6 +107,120 @@ export default function SelectWithFieldDemo() {
107
107
  }
108
108
  ```
109
109
 
110
+ ## Multiple selection
111
+
112
+ Set `type="multiple"` to let users pick several options. `value` becomes a `string[]`, each press toggles an option and keeps the dropdown open, and every toggle calls `onValueChange` with the full array. Add `Select.ItemControl` for a leading checkbox. Without custom children, `Select.Value` shows the selected labels on one line.
113
+
114
+ ```tsx
115
+ import { useState } from 'react';
116
+ import { Field, Select } from '@cdx-ui/components';
117
+ import { teams } from './_data';
118
+
119
+ export default function SelectMultipleDemo() {
120
+ const [teamIds, setTeamIds] = useState<string[]>([]);
121
+
122
+ return (
123
+ <Field className="w-full" isRequired>
124
+ <Field.Label>Assign teams</Field.Label>
125
+ <Select type="multiple" items={teams} value={teamIds} onValueChange={setTeamIds} isRequired>
126
+ <Select.Trigger>
127
+ <Select.Value placeholder="Select teams">
128
+ {({ isPlaceholder, selectedItems }) =>
129
+ isPlaceholder ? null : `${selectedItems.length} teams selected`
130
+ }
131
+ </Select.Value>
132
+ </Select.Trigger>
133
+ <Select.Content>
134
+ {teams.map((team) => (
135
+ <Select.Item key={team.value} value={team.value}>
136
+ <Select.ItemControl />
137
+ <Select.ItemLabel>{team.label}</Select.ItemLabel>
138
+ </Select.Item>
139
+ ))}
140
+ </Select.Content>
141
+ </Select>
142
+ <Field.Helper>Each change applies immediately.</Field.Helper>
143
+ </Field>
144
+ );
145
+ }
146
+ ```
147
+
148
+ ## With search
149
+
150
+ Add `Select.Search` as the first child of `Select.Content` to build a searchable select, the pattern other libraries call an autocomplete. Typing filters the options in both `single` and `multiple` mode, and `Select.Empty` shows when nothing matches. Selected options that the query hides stay selected and still count in `Select.Value`. The default filter is a case- and accent-insensitive "contains" on each option's `label`; pass `filter` to replace it, or control `inputValue` and `onInputChange` to swap `items` for server results. The query clears when the dropdown closes.
151
+
152
+ In `Select.Content` on iOS and Android, the search input renders inside the anchored dropdown and the on-screen keyboard can cover the options. Use `Select.BottomSheet` for native search.
153
+
154
+ ```tsx
155
+ import { useState } from 'react';
156
+ import { Field, Select } from '@cdx-ui/components';
157
+ import { teams } from './_data';
158
+
159
+ export default function SelectWithSearchDemo() {
160
+ const [teamIds, setTeamIds] = useState<string[]>([]);
161
+
162
+ return (
163
+ <Field className="w-full" isRequired>
164
+ <Field.Label>Assign teams</Field.Label>
165
+ <Select type="multiple" items={teams} value={teamIds} onValueChange={setTeamIds}>
166
+ <Select.Trigger>
167
+ <Select.Value placeholder="Select teams you want to assign to this user">
168
+ {({ isPlaceholder, selectedItems }) =>
169
+ isPlaceholder ? null : `${String(selectedItems.length)} teams selected`
170
+ }
171
+ </Select.Value>
172
+ </Select.Trigger>
173
+ <Select.Content>
174
+ <Select.Search placeholder="Search Teams" accessibilityLabel="Search teams" />
175
+ {teams.map((t) => (
176
+ <Select.Item key={t.value} value={t.value} disabled={t.disabled}>
177
+ <Select.ItemControl />
178
+ <Select.ItemLabel>{t.label}</Select.ItemLabel>
179
+ </Select.Item>
180
+ ))}
181
+ <Select.Empty>No teams found</Select.Empty>
182
+ </Select.Content>
183
+ </Select>
184
+ </Field>
185
+ );
186
+ }
187
+ ```
188
+
189
+ ## Bottom sheet
190
+
191
+ Use `Select.BottomSheet` in place of `Select.Content` to present the options in a sheet that slides up from the bottom — the preferred surface on native, especially with `Select.Search`. It takes the same children: `Select.Search`, the options, and `Select.Empty`. The sheet keeps the search input and the results above the on-screen keyboard, and swiping it down, pressing the backdrop, or pressing Android back closes it without changing the value. On web it renders the same dropdown as `Select.Content`, so one composition serves every platform. On native it requires `BottomSheet.Provider` at the app root.
192
+
193
+ ```tsx
194
+ import { useState } from 'react';
195
+ import { Field, Select } from '@cdx-ui/components';
196
+ import { teams } from './_data';
197
+
198
+ export default function SelectBottomSheetDemo() {
199
+ const [teamIds, setTeamIds] = useState<string[]>([]);
200
+
201
+ return (
202
+ <Field className="w-full">
203
+ <Field.Label>Assign teams</Field.Label>
204
+ <Select type="multiple" items={teams} value={teamIds} onValueChange={setTeamIds}>
205
+ <Select.Trigger>
206
+ <Select.Value placeholder="Select teams" />
207
+ </Select.Trigger>
208
+ <Select.BottomSheet>
209
+ <Select.Search placeholder="Search Teams" accessibilityLabel="Search teams" />
210
+ {teams.map((t) => (
211
+ <Select.Item key={t.value} value={t.value} disabled={t.disabled}>
212
+ <Select.ItemControl />
213
+ <Select.ItemLabel>{t.label}</Select.ItemLabel>
214
+ </Select.Item>
215
+ ))}
216
+ <Select.Empty>No teams found</Select.Empty>
217
+ </Select.BottomSheet>
218
+ </Select>
219
+ </Field>
220
+ );
221
+ }
222
+ ```
223
+
110
224
  ## Custom trigger
111
225
 
112
226
  Set `asChild` on `Select.Trigger` to use a Forge pressable such as `Button`, `IconButton`, or `Chip`. Use the trigger and item `indicator` props to replace their default icons, or pass `null` to suppress an indicator.
@@ -145,7 +259,7 @@ export default function SelectCustomTriggerDemo() {
145
259
 
146
260
  ## Custom value display
147
261
 
148
- Pass children to `Select.Value` to replace the default label. Read the selected option from your own state, and render the placeholder by leaving the children empty when nothing is selected.
262
+ Pass children to `Select.Value` to replace the default label. Read the selected option from your own state, and render the placeholder by leaving the children empty when nothing is selected. A function child receives `isPlaceholder`, `selectedItems`, and `selectedText`; return `null` to fall back to the default.
149
263
 
150
264
  ```tsx
151
265
  import { useState } from 'react';
@@ -295,7 +409,7 @@ export default function SelectPlacementDemo() {
295
409
 
296
410
  ### Select
297
411
 
298
- The root manages the selected value, open state, options, and form-control state.
412
+ The root manages the selected value, open state, options, and form-control state. `type` sets the selection mode: `single` (the default) takes a `string` value, and `multiple` takes a `string[]`, where an empty array means no selection.
299
413
 
300
414
  | Prop | Type | Default | Description |
301
415
  | --- | --- | --- | --- |
@@ -303,18 +417,23 @@ The root manages the selected value, open state, options, and form-control state
303
417
  | `children?` | `ReactNode` | — | — |
304
418
  | `variant?` | `'outline' \| null` | — | — |
305
419
  | `size?` | `'default' \| 'small' \| null` | — | — |
306
- | `items` | `SelectItemData[]` | — | Options used to resolve `Select.Value` labels and drive keyboard navigation. Render matching `Select.Item` elements inside `Select.Content`. |
307
- | `value?` | `string` | — | — |
308
- | `defaultValue?` | `string` | — | — |
309
- | `onValueChange?` | `((value: string) => void)` | — | — |
310
- | `isOpen?` | `boolean` | — | Controlled open state. Use `defaultOpen` for the uncontrolled variant. |
420
+ | `type?` | `'single' \| 'multiple'` | — | — |
421
+ | `value?` | `string \| string[]` | — | — |
422
+ | `defaultValue?` | `string \| string[]` | — | — |
423
+ | `onValueChange?` | `((value: string) => void) \| ((value: string[]) => void)` | — | — |
424
+ | `items?` | `SelectItemData[]` | — | — |
425
+ | `isOpen?` | `boolean` | — | — |
311
426
  | `defaultOpen?` | `boolean` | — | — |
312
427
  | `onOpenChange?` | `((open: boolean) => void)` | — | — |
313
428
  | `disabled?` | `boolean` | — | — |
314
429
  | `isRequired?` | `boolean` | — | — |
315
430
  | `isInvalid?` | `boolean` | — | — |
316
431
  | `isReadOnly?` | `boolean` | — | — |
317
- | `native?` | `boolean` | — | Prevents `Select.Content` from rendering. Reserved for native picker integration. |
432
+ | `native?` | `boolean` | — | — |
433
+ | `inputValue?` | `string` | — | — |
434
+ | `defaultInputValue?` | `string` | — | — |
435
+ | `onInputChange?` | `((inputValue: string) => void)` | — | — |
436
+ | `filter?` | `SelectFilter` | — | — |
318
437
 
319
438
  Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
320
439
 
@@ -361,12 +480,12 @@ Inherits all [`PressableProps`](https://reactnative.dev/docs/pressable#props).
361
480
 
362
481
  ### Select.Value
363
482
 
364
- Displays the label from `items` that matches the selected value, or the placeholder when no value is selected.
483
+ Displays the label from `items` that matches the selected value, or the placeholder when no value is selected. In multiple mode it shows the selected labels joined with commas, truncated to one line. A function child receives `{ isPlaceholder, selectedItems, selectedText }`.
365
484
 
366
485
  | Prop | Type | Default | Description |
367
486
  | --- | --- | --- | --- |
368
487
  | `className?` | `string` | — | — |
369
- | `children?` | `ReactNode` | — | — |
488
+ | `children?` | `ReactNode \| ((state: SelectValueRenderProps) => ReactNode)` | — | Replaces the default display. A function child receives the selection state; return `null` or `undefined` to fall back to the default (placeholder or `selectedText`). |
370
489
  | `asChild?` | `boolean` | — | — |
371
490
  | `placeholder?` | `string` | — | — |
372
491
 
@@ -394,7 +513,7 @@ Inherits all `ForgeIconProps` (react-native-svg `SvgProps` plus `filled` and `si
394
513
 
395
514
  ### Select.Content
396
515
 
397
- Dropdown panel that positions itself around the trigger, scrolls long option lists, and closes when dismissed.
516
+ Dropdown panel that positions itself around the trigger, scrolls long option lists, and closes when dismissed. Dismissing never changes the value.
398
517
 
399
518
  | Prop | Type | Default | Description |
400
519
  | --- | --- | --- | --- |
@@ -406,12 +525,65 @@ Dropdown panel that positions itself around the trigger, scrolls long option lis
406
525
  | `crossOffset?` | `number` | — | Cross-axis adjustment from the aligned dropdown position in pixels. Defaults to 0. |
407
526
  | `enteringAnimation?` | `EntryOrExitLayoutType` | — | Enter animation for the dropdown. Defaults to a 150ms fade. |
408
527
  | `exitingAnimation?` | `EntryOrExitLayoutType` | — | Exit animation for the dropdown. Defaults to a 100ms fade. |
528
+ | `dismissAccessibilityLabel?` | `string` | `'Dismiss'` | Accessible name of the visually hidden button that lets screen-reader users close the popup on web. |
409
529
 
410
530
  Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
411
531
 
532
+ ### Select.BottomSheet
533
+
534
+ Presents the options in a bottom sheet on iOS and Android, in place of `Select.Content`. Compose exactly one of `Select.Content` or `Select.BottomSheet` per select. It sorts its direct children into the same regions as `Select.Content`: `Select.Search` stays above the scrolling options and `Select.Empty` follows them. On web it renders the `Select.Content` dropdown, and `dismissAccessibilityLabel` names its hidden dismiss button. With `Select.Search`, keyboard avoidance applies to the default search input; an `asChild` input does not move the sheet.
535
+
536
+ | Prop | Type | Default | Description |
537
+ | --- | --- | --- | --- |
538
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the sheet body's own styles. |
539
+ | `children?` | `ReactNode` | — | `Select.Search`, `Select.Item` elements, and `Select.Empty`, as in `Select.Content`. |
540
+ | `asChild?` | `boolean` | — | Render the consumer's child as the sheet body, skipping the region split. |
541
+ | `dismissAccessibilityLabel?` | `string` | `'Dismiss'` | Accessible name of the visually hidden dismiss button on web, where the surface is the `Select.Content` dropdown. |
542
+
543
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
544
+
545
+ | Attribute | Values | Description |
546
+ | ------------ | --------------------- | ------------------------------- |
547
+ | `data-slot` | `select-bottom-sheet` | Sheet body on native |
548
+ | `data-state` | `open` | Present while the sheet is open |
549
+
550
+ ### Select.Search
551
+
552
+ A search input for the top of `Select.Content` or `Select.BottomSheet`, with a leading search icon. Its query filters the options through the root `filter`, and it takes focus when the dropdown opens on web. Place it as a direct child of the surface; a wrapped `Select.Search` is not recognized and renders among the options.
553
+
554
+ | Prop | Type | Default | Description |
555
+ | --- | --- | --- | --- |
556
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the search container's own styles. |
557
+ | `children?` | `ReactNode` | — | The text input to render when `asChild` is set. Otherwise not rendered. |
558
+ | `asChild?` | `boolean` | — | Merge the search wiring onto the single child text input instead of rendering the default `Input` with a search icon. |
559
+ | `variant?` | `'outline' \| 'subtle'` | `'subtle'` | `Input` variant of the default search field. Ignored when `asChild` is set. |
560
+ | `getResultCountLabel?` | `((count: number) => string)` | — | Text the polite live region announces when the result count changes, such as `"3 results"` (the English default). When nothing matches, the `Select.Empty` text is announced instead. |
561
+
562
+ Inherits [`TextInputProps`](https://reactnative.dev/docs/textinput#props) except `value`, `defaultValue`, and `onChangeText`, which the root owns through `inputValue`, `defaultInputValue`, and `onInputChange`.
563
+
564
+ | Attribute | Values | Description |
565
+ | ----------- | --------------- | ---------------- |
566
+ | `data-slot` | `select-search` | Search container |
567
+
568
+ ### Select.Empty
569
+
570
+ Content shown below the options only when no option matches the query. Plain text children are styled to match the options. Place it as a direct child of `Select.Content` or `Select.BottomSheet`.
571
+
572
+ | Prop | Type | Default | Description |
573
+ | --- | --- | --- | --- |
574
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the empty state's own styles. |
575
+ | `children?` | `ReactNode` | — | — |
576
+ | `asChild?` | `boolean` | — | — |
577
+
578
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
579
+
580
+ | Attribute | Values | Description |
581
+ | ----------- | -------------- | ----------- |
582
+ | `data-slot` | `select-empty` | Empty state |
583
+
412
584
  ### Select.Item
413
585
 
414
- An individual option. Its `value` must match an entry in the root `items` array. The `indicator` prop replaces the default selected-item checkmark, and `indicator={null}` suppresses it.
586
+ An individual option. Its `value` must match an entry in the root `items` array. Disable an option with `disabled` here or on its `items` entry. The `indicator` prop replaces the default selected-item checkmark, and `indicator={null}` suppresses it. The default checkmark is omitted when the item contains a `Select.ItemControl`.
415
587
 
416
588
  | Prop | Type | Default | Description |
417
589
  | --- | --- | --- | --- |
@@ -419,7 +591,7 @@ An individual option. Its `value` must match an entry in the root `items` array.
419
591
  | `children?` | `ReactNode` | — | Either children or a render prop that receives a boolean reflecting whether the component is currently pressed. |
420
592
  | `disabled?` | `boolean` | — | Whether the press behavior is disabled. |
421
593
  | `asChild?` | `boolean` | — | — |
422
- | `indicator?` | `ReactNode` | — | Replaces the default selected-item checkmark. The replacement is rendered only while the item is selected. Pass `null` to suppress the indicator. |
594
+ | `indicator?` | `ReactNode` | — | Replaces the default selected-item checkmark. The replacement is rendered only while the item is selected. Pass `null` to suppress the indicator. The default checkmark is omitted when the item contains a `Select.ItemControl`. |
423
595
  | `value` | `string` | — | — |
424
596
  | `isHovered?` | `boolean` | — | — |
425
597
  | `isActive?` | `boolean` | — | — |
@@ -452,22 +624,49 @@ Text label rendered inside a `Select.Item`.
452
624
 
453
625
  Inherits all [`TextProps`](https://reactnative.dev/docs/text#props).
454
626
 
627
+ ### Select.ItemControl
628
+
629
+ A leading checkbox that mirrors the item's selected state. It is presentational: the option itself carries the selected state for assistive technology.
630
+
631
+ | Prop | Type | Default | Description |
632
+ | --- | --- | --- | --- |
633
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the control's own styles. |
634
+ | `children?` | `ReactNode` | — | The element to render when `asChild` is set. Otherwise not rendered. |
635
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default checkbox glyph. |
636
+
637
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
638
+
639
+ | Attribute | Values | Description |
640
+ | -------------- | --------------------- | --------------- |
641
+ | `data-slot` | `select-item-control` | Checkbox glyph |
642
+ | `data-checked` | `true` \| `false` | Selection state |
643
+
455
644
  ## Anatomy
456
645
 
457
- | `data-slot` | Element |
458
- | ------------------- | ----------------------------- |
459
- | `select` | Root container |
460
- | `select-trigger` | Dropdown trigger |
461
- | `select-value` | Selected label or placeholder |
462
- | `select-icon` | Dropdown indicator |
463
- | `select-content` | Dropdown panel |
464
- | `select-item` | Option |
465
- | `select-item-label` | Option label |
646
+ | `data-slot` | Element |
647
+ | --------------------- | ----------------------------- |
648
+ | `select` | Root container |
649
+ | `select-trigger` | Dropdown trigger |
650
+ | `select-value` | Selected label or placeholder |
651
+ | `select-icon` | Dropdown indicator |
652
+ | `select-content` | Dropdown panel |
653
+ | `select-bottom-sheet` | Sheet body (native) |
654
+ | `select-search` | Search input |
655
+ | `select-empty` | No-results message |
656
+ | `select-item` | Option |
657
+ | `select-item-label` | Option label |
658
+ | `select-item-control` | Option checkbox |
466
659
 
467
660
  ## Accessibility
468
661
 
469
- The trigger uses `role="combobox"` with `aria-haspopup="listbox"`, reflects open, required, invalid, and read-only state, and points to the dropdown content. The content uses `role="listbox"` and each item uses `role="option"` with selected and disabled state.
662
+ The trigger uses `role="combobox"` with `aria-haspopup="listbox"`, reflects open, required, invalid, and read-only state, and points to the dropdown content. The content uses `role="listbox"` and each item uses `role="option"` with selected and disabled state. In multiple mode the listbox sets `aria-multiselectable="true"`, and `Select.ItemControl` is hidden from assistive technology.
470
663
 
471
664
  Compose with `Field.Label` for a visible associated label. When no visible label is present, provide `accessibilityLabel` on `Select`.
472
665
 
473
- On web, Enter, Space, Arrow Down, or Arrow Up opens the dropdown. Arrow keys, Home, and End move through enabled options; Enter or Space selects; Escape closes; and Tab closes while continuing focus navigation.
666
+ With `Select.Search`, the dropdown becomes a `role="dialog"` named by the field label that contains the search input (`role="searchbox"`) and the `role="listbox"`. The trigger keeps `aria-haspopup="listbox"`. On web, focus stays in the search input while Arrow Up and Arrow Down move the highlight through the visible, enabled options, announced through `aria-activedescendant`. Enter selects and closes, or toggles in multiple mode, and Space types a space. The highlight moves to the first match each time the query changes, and a polite live region announces the number of results, or the `Select.Empty` text when nothing matches. Escape and Tab close the dropdown, keep the value, and clear the query. Inside a `Drawer`, closing returns focus to the trigger and leaves the drawer open.
667
+
668
+ On web, Enter, Space, Arrow Down, or Arrow Up opens the dropdown. Arrow keys, Home, and End move through enabled options; Enter or Space selects and closes, or toggles in multiple mode; Escape closes; and Tab closes while continuing focus navigation. Focus returns to the trigger when the dropdown closes.
669
+
670
+ Screen-reader users can close the dropdown without a keyboard. On web, a visually hidden button labelled by `dismissAccessibilityLabel` on `Select.Content` closes it. On iOS, the VoiceOver escape gesture closes it, and on Android the back button does. Closing never changes the value.
671
+
672
+ In `Select.BottomSheet` on iOS and Android, VoiceOver and TalkBack move through the search input and each option directly, with no virtual focus, and each option announces its selected state. The sheet takes the same role and name as the dropdown. The VoiceOver escape gesture, Android back, a swipe down, and the backdrop close it, keep the value, and clear the query.
@@ -124,6 +124,103 @@ export default function TextItalicDemo() {
124
124
  }
125
125
  ```
126
126
 
127
+ ## Nested
128
+
129
+ Place a `Text` inside another text element to style part of a sentence. A nested `Text` inherits `color`, `size`, `weight` and the font family from its parent, and each prop it sets overrides only that one.
130
+
131
+ ```tsx
132
+ import { Box, Text } from '@cdx-ui/components';
133
+
134
+ export default function TextNestedDemo() {
135
+ return (
136
+ <Box className="gap-4">
137
+ <Text color="secondary" size="lg" weight="medium">
138
+ Inherits everything: <Text>Your statement is ready to download.</Text>
139
+ </Text>
140
+ <Text color="secondary" size="lg" weight="medium">
141
+ Overrides color only: <Text color="danger">Your payment is past due.</Text>
142
+ </Text>
143
+ <Text color="secondary" size="lg" weight="medium">
144
+ Overrides size and weight:{' '}
145
+ <Text size="sm" weight="bold">
146
+ Your statement is ready to download.
147
+ </Text>
148
+ </Text>
149
+ <Text color="secondary" size="lg" weight="bold">
150
+ Overrides size only, keeps bold: <Text size="sm">Your statement is ready to download.</Text>
151
+ </Text>
152
+ </Box>
153
+ );
154
+ }
155
+ ```
156
+
157
+ ### Inside text slots
158
+
159
+ The same inheritance applies inside `Heading` and text slots such as `Button.Label` and `Alert.Description`, so emphasis follows the surrounding component's styles.
160
+
161
+ ```tsx
162
+ import { Alert, Box, Button, Heading, Text } from '@cdx-ui/components';
163
+
164
+ export default function TextNestedSlotsDemo() {
165
+ return (
166
+ <Box className="gap-4">
167
+ <Heading size="sm">
168
+ Inside a Heading: <Text color="action">Savings goals</Text>
169
+ </Heading>
170
+ <Button>
171
+ <Button.Label>
172
+ Inside Button.Label: <Text weight="bold">Transfer</Text>
173
+ </Button.Label>
174
+ </Button>
175
+ <Alert severity="info">
176
+ <Alert.Description>
177
+ Inside Alert.Description: <Text weight="semibold">Transfers settle by 5 p.m.</Text>
178
+ </Alert.Description>
179
+ </Alert>
180
+ </Box>
181
+ );
182
+ }
183
+ ```
184
+
185
+ ### Inline emphasis
186
+
187
+ Combine nested `Text` elements to highlight amounts, statuses and links within running copy.
188
+
189
+ ```tsx
190
+ import { Box, Text } from '@cdx-ui/components';
191
+
192
+ export default function TextNestedInlineDemo() {
193
+ return (
194
+ <Box className="gap-3">
195
+ <Text>
196
+ Your available balance is <Text weight="bold">$2,481.16</Text>, up{' '}
197
+ <Text color="success" weight="semibold">
198
+ $120.00
199
+ </Text>{' '}
200
+ since yesterday.
201
+ </Text>
202
+ <Text color="secondary" size="sm">
203
+ Posted <Text weight="medium">Oct 14</Text> · Pending{' '}
204
+ <Text color="warning" italic>
205
+ 2 transactions
206
+ </Text>
207
+ </Text>
208
+ <Text size="sm" color="tertiary">
209
+ By continuing you agree to the{' '}
210
+ <Text color="action" weight="medium">
211
+ Terms of Service
212
+ </Text>{' '}
213
+ and{' '}
214
+ <Text color="action" weight="medium">
215
+ Privacy Policy
216
+ </Text>
217
+ .
218
+ </Text>
219
+ </Box>
220
+ );
221
+ }
222
+ ```
223
+
127
224
  ## API
128
225
 
129
226
  ### Text