@usefragments/ui 1.1.1 → 1.2.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 (141) hide show
  1. package/dist/assets/ui.css +327 -274
  2. package/dist/components/Checkbox/Checkbox.module.scss.cjs +11 -11
  3. package/dist/components/Checkbox/Checkbox.module.scss.js +11 -11
  4. package/dist/components/Chip/Chip.module.scss.cjs +15 -15
  5. package/dist/components/Chip/Chip.module.scss.js +15 -15
  6. package/dist/components/Combobox/index.cjs +106 -9
  7. package/dist/components/Combobox/index.d.ts.map +1 -1
  8. package/dist/components/Combobox/index.js +106 -9
  9. package/dist/components/DataTable/DataTable.module.scss.cjs +37 -34
  10. package/dist/components/DataTable/DataTable.module.scss.js +37 -34
  11. package/dist/components/Main/Main.module.scss.cjs +26 -0
  12. package/dist/components/Main/Main.module.scss.js +26 -0
  13. package/dist/components/Main/index.cjs +128 -0
  14. package/dist/components/Main/index.d.ts +22 -0
  15. package/dist/components/Main/index.d.ts.map +1 -0
  16. package/dist/components/Main/index.js +111 -0
  17. package/dist/components/Table/Table.module.scss.cjs +19 -19
  18. package/dist/components/Table/Table.module.scss.js +19 -19
  19. package/dist/components/TableOfContents/TableOfContents.module.scss.cjs +15 -15
  20. package/dist/components/TableOfContents/TableOfContents.module.scss.js +15 -15
  21. package/dist/components/TableOfContents/index.cjs +22 -42
  22. package/dist/components/TableOfContents/index.d.ts +3 -3
  23. package/dist/components/TableOfContents/index.d.ts.map +1 -1
  24. package/dist/components/TableOfContents/index.js +22 -42
  25. package/dist/index.cjs +87 -85
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -0
  29. package/fragments.json +1 -1
  30. package/package.json +7 -2
  31. package/src/components/Checkbox/Checkbox.contract.json +5 -24
  32. package/src/components/Checkbox/Checkbox.module.scss +4 -2
  33. package/src/components/Chip/Chip.contract.json +11 -28
  34. package/src/components/Chip/Chip.module.scss +21 -45
  35. package/src/components/Combobox/Combobox.contract.json +6 -24
  36. package/src/components/Combobox/index.tsx +132 -6
  37. package/src/components/DataTable/DataTable.module.scss +31 -8
  38. package/src/components/Main/Main.contract.json +79 -0
  39. package/src/components/Main/Main.module.scss +31 -0
  40. package/src/components/Main/index.tsx +124 -0
  41. package/src/components/Table/Table.module.scss +22 -5
  42. package/src/components/TableOfContents/TableOfContents.contract.json +6 -16
  43. package/src/components/TableOfContents/TableOfContents.module.scss +10 -13
  44. package/src/components/TableOfContents/index.tsx +27 -38
  45. package/src/index.ts +3 -0
  46. package/src/tokens/_component-properties.scss +1 -0
  47. package/dist/components/Cloud/DashboardLayoutPrototypes.shared.d.ts +0 -33
  48. package/dist/components/Cloud/DashboardLayoutPrototypes.shared.d.ts.map +0 -1
  49. package/dist/components/Cloud/DashboardLayoutPrototypes.shell.d.ts +0 -11
  50. package/dist/components/Cloud/DashboardLayoutPrototypes.shell.d.ts.map +0 -1
  51. package/dist/components/Cloud/PullRequestsPrototypes.content.d.ts +0 -5
  52. package/dist/components/Cloud/PullRequestsPrototypes.content.d.ts.map +0 -1
  53. package/dist/components/Cloud/PullRequestsPrototypes.detail-panels.d.ts +0 -4
  54. package/dist/components/Cloud/PullRequestsPrototypes.detail-panels.d.ts.map +0 -1
  55. package/dist/components/Cloud/PullRequestsPrototypes.detail.d.ts +0 -3
  56. package/dist/components/Cloud/PullRequestsPrototypes.detail.d.ts.map +0 -1
  57. package/dist/components/Cloud/PullRequestsPrototypes.id-data.d.ts +0 -86
  58. package/dist/components/Cloud/PullRequestsPrototypes.id-data.d.ts.map +0 -1
  59. package/dist/components/Cloud/PullRequestsPrototypes.id-page.d.ts +0 -2
  60. package/dist/components/Cloud/PullRequestsPrototypes.id-page.d.ts.map +0 -1
  61. package/src/components/Accordion/Accordion.stories.tsx +0 -106
  62. package/src/components/Alert/Alert.stories.tsx +0 -119
  63. package/src/components/AppShell/AppShell.stories.tsx +0 -156
  64. package/src/components/Avatar/Avatar.stories.tsx +0 -74
  65. package/src/components/Badge/Badge.stories.tsx +0 -92
  66. package/src/components/BentoGrid/BentoGrid.stories.tsx +0 -98
  67. package/src/components/Box/Box.stories.tsx +0 -150
  68. package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +0 -87
  69. package/src/components/Button/Button.stories.tsx +0 -103
  70. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +0 -97
  71. package/src/components/Card/Card.stories.tsx +0 -144
  72. package/src/components/Chart/Chart.stories.tsx +0 -119
  73. package/src/components/Checkbox/Checkbox.stories.tsx +0 -68
  74. package/src/components/Chip/Chip.stories.tsx +0 -70
  75. package/src/components/Cloud/CloudGovernanceShell.module.scss +0 -88
  76. package/src/components/Cloud/CloudGovernanceShell.stories.tsx +0 -446
  77. package/src/components/Cloud/ControlSizing.module.scss +0 -49
  78. package/src/components/Cloud/ControlSizing.stories.tsx +0 -238
  79. package/src/components/Cloud/DashboardLayoutPrototypes.module.scss +0 -462
  80. package/src/components/Cloud/DashboardLayoutPrototypes.shared.tsx +0 -375
  81. package/src/components/Cloud/DashboardLayoutPrototypes.shell.tsx +0 -147
  82. package/src/components/Cloud/DashboardLayoutPrototypes.stories.tsx +0 -328
  83. package/src/components/Cloud/PullRequestsPrototypes.content.tsx +0 -283
  84. package/src/components/Cloud/PullRequestsPrototypes.detail-panels.tsx +0 -381
  85. package/src/components/Cloud/PullRequestsPrototypes.detail.tsx +0 -204
  86. package/src/components/Cloud/PullRequestsPrototypes.id-data.ts +0 -395
  87. package/src/components/Cloud/PullRequestsPrototypes.id-page.tsx +0 -349
  88. package/src/components/Cloud/PullRequestsPrototypes.stories.tsx +0 -81
  89. package/src/components/CodeBlock/CodeBlock.stories.tsx +0 -147
  90. package/src/components/Collapsible/Collapsible.stories.tsx +0 -94
  91. package/src/components/ColorPicker/ColorPicker.stories.tsx +0 -74
  92. package/src/components/Combobox/Combobox.stories.tsx +0 -133
  93. package/src/components/Command/Command.stories.tsx +0 -102
  94. package/src/components/ConversationList/ConversationList.stories.tsx +0 -93
  95. package/src/components/DataTable/DataTable.stories.tsx +0 -129
  96. package/src/components/DatePicker/DatePicker.stories.tsx +0 -129
  97. package/src/components/Dialog/Dialog.stories.tsx +0 -154
  98. package/src/components/Drawer/Drawer.stories.tsx +0 -144
  99. package/src/components/Editor/Editor.stories.tsx +0 -99
  100. package/src/components/EmptyState/EmptyState.stories.tsx +0 -97
  101. package/src/components/Field/Field.stories.tsx +0 -109
  102. package/src/components/Fieldset/Fieldset.stories.tsx +0 -116
  103. package/src/components/Form/Form.stories.tsx +0 -142
  104. package/src/components/Grid/Grid.stories.tsx +0 -125
  105. package/src/components/Header/Header.stories.tsx +0 -133
  106. package/src/components/Icon/Icon.stories.tsx +0 -80
  107. package/src/components/IconButton/IconButton.stories.tsx +0 -78
  108. package/src/components/Image/Image.stories.tsx +0 -109
  109. package/src/components/Input/Input.stories.tsx +0 -104
  110. package/src/components/Link/Link.stories.tsx +0 -67
  111. package/src/components/List/List.stories.tsx +0 -95
  112. package/src/components/Listbox/Listbox.stories.tsx +0 -81
  113. package/src/components/Loading/Loading.stories.tsx +0 -69
  114. package/src/components/Markdown/Markdown.stories.tsx +0 -75
  115. package/src/components/Menu/Menu.stories.tsx +0 -113
  116. package/src/components/Message/Message.stories.tsx +0 -85
  117. package/src/components/NavigationMenu/NavigationMenu.stories.tsx +0 -161
  118. package/src/components/Pagination/Pagination.stories.tsx +0 -81
  119. package/src/components/Popover/Popover.stories.tsx +0 -152
  120. package/src/components/Progress/Progress.stories.tsx +0 -64
  121. package/src/components/Prompt/Prompt.stories.tsx +0 -93
  122. package/src/components/RadioGroup/RadioGroup.stories.tsx +0 -107
  123. package/src/components/ScrollArea/ScrollArea.stories.tsx +0 -97
  124. package/src/components/Select/Select.stories.tsx +0 -127
  125. package/src/components/Separator/Separator.stories.tsx +0 -84
  126. package/src/components/Sidebar/Sidebar.stories.tsx +0 -122
  127. package/src/components/Skeleton/Skeleton.stories.tsx +0 -80
  128. package/src/components/Slider/Slider.stories.tsx +0 -73
  129. package/src/components/Stack/Stack.stories.tsx +0 -73
  130. package/src/components/Switch/Switch.stories.tsx +0 -59
  131. package/src/components/Table/Table.stories.tsx +0 -219
  132. package/src/components/TableOfContents/TableOfContents.stories.tsx +0 -120
  133. package/src/components/Tabs/Tabs.stories.tsx +0 -136
  134. package/src/components/Text/Text.stories.tsx +0 -133
  135. package/src/components/Textarea/Textarea.stories.tsx +0 -107
  136. package/src/components/Theme/Theme.stories.tsx +0 -95
  137. package/src/components/ThinkingIndicator/ThinkingIndicator.stories.tsx +0 -85
  138. package/src/components/Toast/Toast.stories.tsx +0 -91
  139. package/src/components/ToggleGroup/ToggleGroup.stories.tsx +0 -114
  140. package/src/components/Tooltip/Tooltip.stories.tsx +0 -95
  141. package/src/components/VisuallyHidden/VisuallyHidden.stories.tsx +0 -78
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefragments/ui",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Customizable UI components built on Base UI headless primitives",
6
6
  "author": "Conan McNicholl",
@@ -190,10 +190,12 @@
190
190
  "vite": "^6.0.0",
191
191
  "vitest": "^2.1.8",
192
192
  "vitest-axe": "^0.1.0",
193
- "@usefragments/core": "1.2.0"
193
+ "@usefragments/core": "1.3.0"
194
194
  },
195
195
  "files": [
196
196
  "src",
197
+ "!src/**/.fragments/**",
198
+ "!src/**/.impeccable/**",
197
199
  "!src/**/*.test.ts",
198
200
  "!src/**/*.test.tsx",
199
201
  "!src/test/",
@@ -201,7 +203,10 @@
201
203
  "!src/**/*.block.ts",
202
204
  "!src/blocks/",
203
205
  "!src/assets/*.svg",
206
+ "!src/**/*.stories.tsx",
207
+ "!src/components/Cloud/",
204
208
  "dist",
209
+ "!dist/components/Cloud/",
205
210
  "fragments.json"
206
211
  ],
207
212
  "scripts": {
@@ -3,13 +3,7 @@
3
3
  "name": "Checkbox",
4
4
  "description": "Binary toggle for form fields. Use for options that require explicit submission, unlike Switch which takes effect immediately.",
5
5
  "category": "forms",
6
- "tags": [
7
- "checkbox",
8
- "form",
9
- "boolean",
10
- "selection",
11
- "input"
12
- ],
6
+ "tags": ["checkbox", "form", "boolean", "selection", "input"],
13
7
  "status": "stable",
14
8
  "sourcePath": "src/components/Checkbox/index.tsx",
15
9
  "exportName": "Checkbox",
@@ -78,21 +72,14 @@
78
72
  "description": "Checkbox size",
79
73
  "default": "md",
80
74
  "required": false,
81
- "values": [
82
- "sm",
83
- "md",
84
- "lg"
85
- ]
75
+ "values": ["sm", "md", "lg"]
86
76
  },
87
77
  "variant": {
88
78
  "type": "enum",
89
79
  "description": "Visual variant. 'default' is the inline checkbox, 'card' renders as a full-width clickable card with the checkbox tucked inside.",
90
80
  "default": "default",
91
81
  "required": false,
92
- "values": [
93
- "default",
94
- "card"
95
- ]
82
+ "values": ["default", "card"]
96
83
  },
97
84
  "label": {
98
85
  "type": "string",
@@ -235,17 +222,11 @@
235
222
  "description: string - deprecated alias for helperText",
236
223
  "controlClassName/contentClassName - explicit styling targets for control and content"
237
224
  ],
238
- "a11yRules": [
239
- "A11Y_CHECKBOX_LABEL",
240
- "A11Y_CHECKBOX_FOCUS",
241
- "A11Y_TARGET_SIZE_MIN"
242
- ]
225
+ "a11yRules": ["A11Y_CHECKBOX_LABEL", "A11Y_CHECKBOX_FOCUS", "A11Y_TARGET_SIZE_MIN"]
243
226
  },
244
227
  "ai": {
245
228
  "compositionPattern": "compound",
246
- "subComponents": [
247
- "Root"
248
- ]
229
+ "subComponents": ["Root"]
249
230
  },
250
231
  "provenance": {
251
232
  "source": "migrated",
@@ -1,6 +1,8 @@
1
1
  @use "../../tokens/variables" as *;
2
2
  @use "../../tokens/mixins" as *;
3
3
 
4
+ $fui-checkbox-radius: 0.25rem !default;
5
+
4
6
  // Wrapper for checkbox + label
5
7
  .wrapper {
6
8
  display: inline-flex;
@@ -34,7 +36,7 @@
34
36
  margin-top: 0;
35
37
  background-color: var(--fui-field-bg, $fui-bg-elevated);
36
38
  border: 1px solid var(--fui-field-border, $fui-border-strong);
37
- border-radius: var(--fui-radius-sm, $fui-radius-sm);
39
+ border-radius: var(--fui-checkbox-radius, $fui-checkbox-radius);
38
40
  cursor: inherit;
39
41
 
40
42
  &:hover:not([data-disabled]) {
@@ -87,7 +89,7 @@
87
89
  .sm {
88
90
  width: var(--fui-icon-xs, $fui-icon-xs);
89
91
  height: var(--fui-icon-xs, $fui-icon-xs);
90
- border-radius: var(--fui-radius-sm, $fui-radius-sm);
92
+ border-radius: var(--fui-checkbox-radius, $fui-checkbox-radius);
91
93
  }
92
94
 
93
95
  .md {
@@ -3,14 +3,7 @@
3
3
  "name": "Chip",
4
4
  "description": "Interactive pill-shaped element for filtering, selecting, and tagging. Supports single and multi-select via Chip.Group.",
5
5
  "category": "forms",
6
- "tags": [
7
- "chip",
8
- "tag",
9
- "filter",
10
- "selection",
11
- "multi-select",
12
- "action"
13
- ],
6
+ "tags": ["chip", "tag", "filter", "selection", "multi-select", "action"],
14
7
  "status": "stable",
15
8
  "sourcePath": "src/components/Chip/index.tsx",
16
9
  "exportName": "Chip",
@@ -37,24 +30,14 @@
37
30
  "description": "Visual style variant",
38
31
  "default": "filled",
39
32
  "required": false,
40
- "values": [
41
- "filled",
42
- "outlined",
43
- "outline",
44
- "soft"
45
- ]
33
+ "values": ["filled", "outlined", "outline", "soft"]
46
34
  },
47
35
  "size": {
48
36
  "type": "enum",
49
37
  "description": "Chip size",
50
38
  "default": "md",
51
39
  "required": false,
52
- "values": [
53
- "xs",
54
- "sm",
55
- "md",
56
- "lg"
57
- ]
40
+ "values": ["xs", "sm", "md", "lg"]
58
41
  },
59
42
  "selected": {
60
43
  "type": "boolean",
@@ -135,7 +118,7 @@
135
118
  {
136
119
  "name": "Selected",
137
120
  "description": "Chip in selected state across variants",
138
- "code": "<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>\n <Chip selected>Filled</Chip>\n <Chip variant=\"outlined\" selected>Outlined</Chip>\n <Chip variant=\"soft\" selected>Soft</Chip>\n</div>"
121
+ "code": "<Stack direction=\"row\" gap=\"sm\" wrap>\n <Chip selected>Filled</Chip>\n <Chip variant=\"outlined\" selected>Outlined</Chip>\n <Chip variant=\"soft\" selected>Soft</Chip>\n</Stack>"
139
122
  },
140
123
  {
141
124
  "name": "With Avatar",
@@ -147,6 +130,11 @@
147
130
  "description": "Removable chip with dismiss button",
148
131
  "code": "<div style={{ display: 'flex', gap: '8px' }}>\n <Chip onRemove={() => {}}>React</Chip>\n <Chip onRemove={() => {}}>TypeScript</Chip>\n <Chip onRemove={() => {}}>SCSS</Chip>\n</div>"
149
132
  },
133
+ {
134
+ "name": "Selected Removable",
135
+ "description": "Selected removable chips retain the shared selection treatment across variants",
136
+ "code": "<Stack direction=\"row\" gap=\"sm\" wrap>\n <Chip selected onRemove={() => {}}>Filled</Chip>\n <Chip variant=\"outlined\" selected onRemove={() => {}}>Outlined</Chip>\n <Chip variant=\"soft\" selected onRemove={() => {}}>Soft</Chip>\n</Stack>"
137
+ },
150
138
  {
151
139
  "name": "Chip Group",
152
140
  "description": "Multi-select chip set with shared state",
@@ -192,16 +180,11 @@
192
180
  "Chip.Group forwards DOM props (id, role, aria-*, data-*, handlers)",
193
181
  "Chip.Group: non-string chip labels should set an explicit value"
194
182
  ],
195
- "a11yRules": [
196
- "A11Y_CHIP_SELECTION",
197
- "A11Y_CHIP_DISMISS"
198
- ]
183
+ "a11yRules": ["A11Y_CHIP_SELECTION", "A11Y_CHIP_DISMISS"]
199
184
  },
200
185
  "ai": {
201
186
  "compositionPattern": "compound",
202
- "subComponents": [
203
- "Group"
204
- ]
187
+ "subComponents": ["Group"]
205
188
  },
206
189
  "provenance": {
207
190
  "source": "migrated",
@@ -1,5 +1,19 @@
1
- @use '../../tokens/variables' as *;
2
- @use '../../tokens/mixins' as *;
1
+ @use "../../tokens/variables" as *;
2
+ @use "../../tokens/mixins" as *;
3
+
4
+ @mixin selected-surface {
5
+ background-color: var(--fui-field-selection-bg, $fui-bg-hover);
6
+ color: var(--fui-field-selection-color, $fui-text-primary);
7
+
8
+ &:hover:not(:disabled) {
9
+ background-color: var(--fui-field-selection-bg-hover, $fui-bg-hover);
10
+ }
11
+
12
+ &:active:not(:disabled) {
13
+ background-color: var(--fui-field-selection-bg-hover, $fui-bg-hover);
14
+ box-shadow: inset 0 0 0 1px var(--fui-field-selection-border, $fui-border-strong);
15
+ }
16
+ }
3
17
 
4
18
  .chip {
5
19
  @include button-reset;
@@ -61,16 +75,7 @@
61
75
  }
62
76
 
63
77
  &.selected {
64
- background-color: var(--fui-color-accent, $fui-color-accent);
65
- color: var(--fui-color-on-accent, $fui-color-on-accent);
66
-
67
- &:hover:not(:disabled) {
68
- background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
69
- }
70
-
71
- &:active:not(:disabled) {
72
- background-color: var(--fui-color-accent-active, $fui-color-accent-active);
73
- }
78
+ @include selected-surface;
74
79
  }
75
80
  }
76
81
 
@@ -89,19 +94,8 @@
89
94
  }
90
95
 
91
96
  &.selected {
92
- background-color: var(--fui-color-accent, $fui-color-accent);
93
- color: var(--fui-color-on-accent, $fui-color-on-accent);
94
- border-color: var(--fui-color-accent, $fui-color-accent);
95
-
96
- &:hover:not(:disabled) {
97
- background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
98
- border-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
99
- }
100
-
101
- &:active:not(:disabled) {
102
- background-color: var(--fui-color-accent-active, $fui-color-accent-active);
103
- border-color: var(--fui-color-accent-active, $fui-color-accent-active);
104
- }
97
+ @include selected-surface;
98
+ border-color: var(--fui-field-selection-border, $fui-border-strong);
105
99
  }
106
100
  }
107
101
 
@@ -118,16 +112,7 @@
118
112
  }
119
113
 
120
114
  &.selected {
121
- background-color: var(--fui-color-accent, $fui-color-accent);
122
- color: var(--fui-color-on-accent, $fui-color-on-accent);
123
-
124
- &:hover:not(:disabled) {
125
- background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
126
- }
127
-
128
- &:active:not(:disabled) {
129
- background-color: var(--fui-color-accent-active, $fui-color-accent-active);
130
- }
115
+ @include selected-surface;
131
116
  }
132
117
  }
133
118
 
@@ -244,17 +229,8 @@
244
229
  }
245
230
 
246
231
  .selected + .remove {
247
- background-color: var(--fui-color-accent, $fui-color-accent);
248
- color: var(--fui-color-on-accent, $fui-color-on-accent);
232
+ @include selected-surface;
249
233
  border-left-color: transparent;
250
-
251
- &:hover:not(:disabled) {
252
- background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
253
- }
254
-
255
- &:active:not(:disabled) {
256
- background-color: var(--fui-color-accent-active, $fui-color-accent-active);
257
- }
258
234
  }
259
235
 
260
236
  // Chip.Group
@@ -3,15 +3,7 @@
3
3
  "name": "Combobox",
4
4
  "description": "Searchable select input that filters a dropdown list of options as you type. Supports single and multiple selection with chips.",
5
5
  "category": "forms",
6
- "tags": [
7
- "combobox",
8
- "autocomplete",
9
- "search",
10
- "select",
11
- "typeahead",
12
- "form",
13
- "multiselect"
14
- ],
6
+ "tags": ["combobox", "autocomplete", "search", "select", "typeahead", "form", "multiselect"],
15
7
  "status": "stable",
16
8
  "sourcePath": "src/components/Combobox/index.tsx",
17
9
  "exportName": "Combobox",
@@ -110,11 +102,7 @@
110
102
  "description": "Size variant",
111
103
  "default": "md",
112
104
  "required": false,
113
- "values": [
114
- "sm",
115
- "md",
116
- "lg"
117
- ]
105
+ "values": ["sm", "md", "lg"]
118
106
  },
119
107
  "className": {
120
108
  "type": "string",
@@ -163,6 +151,7 @@
163
151
  "Use helperText for guidance and error for validation messages",
164
152
  "Provide an empty state message when no results match",
165
153
  "Group related options with Combobox.Group for large lists",
154
+ "Static items may be composed through fragments and groups; filtering preserves wrapper composition",
166
155
  "Keep option text concise and searchable",
167
156
  "Use multiple prop for multi-select with chip display"
168
157
  ],
@@ -198,7 +187,7 @@
198
187
  {
199
188
  "name": "With Groups",
200
189
  "description": "Options organized into groups",
201
- "code": "<StatefulCombobox placeholder=\"Search countries...\">\n <Combobox.Input />\n <Combobox.Content>\n <Combobox.Group>\n <Combobox.GroupLabel>North America</Combobox.GroupLabel>\n <Combobox.Item value=\"us\">United States</Combobox.Item>\n <Combobox.Item value=\"ca\">Canada</Combobox.Item>\n <Combobox.Item value=\"mx\">Mexico</Combobox.Item>\n </Combobox.Group>\n <Combobox.Group>\n <Combobox.GroupLabel>Europe</Combobox.GroupLabel>\n <Combobox.Item value=\"uk\">United Kingdom</Combobox.Item>\n <Combobox.Item value=\"de\">Germany</Combobox.Item>\n <Combobox.Item value=\"fr\">France</Combobox.Item>\n </Combobox.Group>\n </Combobox.Content>\n</StatefulCombobox>"
190
+ "code": "<StatefulCombobox placeholder=\"Search countries...\">\n <Combobox.Input />\n <Combobox.Content>\n <>\n <Combobox.Group>\n <Combobox.GroupLabel>North America</Combobox.GroupLabel>\n <Combobox.Item value=\"us\">United States</Combobox.Item>\n <Combobox.Item value=\"ca\">Canada</Combobox.Item>\n <Combobox.Item value=\"mx\">Mexico</Combobox.Item>\n </Combobox.Group>\n <Combobox.Group>\n <Combobox.GroupLabel>Europe</Combobox.GroupLabel>\n <Combobox.Item value=\"uk\">United Kingdom</Combobox.Item>\n <Combobox.Item value=\"de\">Germany</Combobox.Item>\n <Combobox.Item value=\"fr\">France</Combobox.Item>\n </Combobox.Group>\n <Combobox.Empty>No matching country</Combobox.Empty>\n </>\n </Combobox.Content>\n</StatefulCombobox>"
202
191
  },
203
192
  {
204
193
  "name": "With Empty State",
@@ -260,11 +249,7 @@
260
249
  "Combobox.Input showTrigger: boolean - hide built-in trigger when using Combobox.Trigger explicitly",
261
250
  "maxVisibleItems: number - max visible options before scrolling (default 4)"
262
251
  ],
263
- "a11yRules": [
264
- "A11Y_COMBOBOX_KEYBOARD",
265
- "A11Y_COMBOBOX_LABEL",
266
- "A11Y_TARGET_SIZE_MIN"
267
- ]
252
+ "a11yRules": ["A11Y_COMBOBOX_KEYBOARD", "A11Y_COMBOBOX_LABEL", "A11Y_TARGET_SIZE_MIN"]
268
253
  },
269
254
  "ai": {
270
255
  "compositionPattern": "compound",
@@ -278,10 +263,7 @@
278
263
  "Group",
279
264
  "GroupLabel"
280
265
  ],
281
- "requiredChildren": [
282
- "Input",
283
- "Content"
284
- ],
266
+ "requiredChildren": ["Input", "Content"],
285
267
  "commonPatterns": [
286
268
  "<Combobox placeholder=\"Search...\"><Combobox.Input /><Combobox.Content><Combobox.Item value=\"opt1\">{label1}</Combobox.Item><Combobox.Item value=\"opt2\">{label2}</Combobox.Item></Combobox.Content></Combobox>",
287
269
  "<Combobox multiple placeholder=\"Select items...\"><Combobox.Input /><Combobox.Content><Combobox.Item value=\"opt1\">{label1}</Combobox.Item><Combobox.Item value=\"opt2\">{label2}</Combobox.Item></Combobox.Content></Combobox>",
@@ -168,6 +168,7 @@ interface ComboboxContextValue {
168
168
  placeholder?: string;
169
169
  multiple?: boolean;
170
170
  selectedValues: string[];
171
+ staticLabels: ReadonlyMap<string, string>;
171
172
  itemsRef: React.MutableRefObject<Map<string, string>>;
172
173
  itemsVersion: number;
173
174
  incrementItemsVersion: () => void;
@@ -178,8 +179,11 @@ interface ComboboxContextValue {
178
179
  inputId?: string;
179
180
  }
180
181
 
182
+ const EMPTY_STATIC_LABELS = new Map<string, string>();
183
+
181
184
  const ComboboxContext = React.createContext<ComboboxContextValue>({
182
185
  selectedValues: [],
186
+ staticLabels: EMPTY_STATIC_LABELS,
183
187
  itemsRef: { current: new Map() },
184
188
  itemsVersion: 0,
185
189
  incrementItemsVersion: () => {},
@@ -188,6 +192,16 @@ const ComboboxContext = React.createContext<ComboboxContextValue>({
188
192
  size: "md",
189
193
  });
190
194
 
195
+ const EMPTY_FILTERED_ITEM_INDICES = new Map<string, number>();
196
+ const ComboboxFilteredItemIndexContext = React.createContext<ReadonlyMap<string, number>>(
197
+ EMPTY_FILTERED_ITEM_INDICES
198
+ );
199
+
200
+ function itemIndicesFromSerializedValues(serializedValues: string): ReadonlyMap<string, number> {
201
+ const values = JSON.parse(serializedValues) as string[];
202
+ return new Map(values.map((value, index) => [value, index]));
203
+ }
204
+
191
205
  function getNodeText(node: React.ReactNode): string {
192
206
  if (node == null || typeof node === "boolean") return "";
193
207
  if (typeof node === "string" || typeof node === "number") return String(node);
@@ -197,6 +211,84 @@ function getNodeText(node: React.ReactNode): string {
197
211
  return "";
198
212
  }
199
213
 
214
+ interface ComboboxStaticItem {
215
+ label: string;
216
+ value: string;
217
+ }
218
+
219
+ function collectStaticItems(node: React.ReactNode): ComboboxStaticItem[] {
220
+ const items: ComboboxStaticItem[] = [];
221
+
222
+ React.Children.forEach(node, (child) => {
223
+ if (!React.isValidElement(child)) return;
224
+ if (child.type === ComboboxItem) {
225
+ const props = child.props as ComboboxItemProps;
226
+ items.push({
227
+ label: getNodeText(props.children).trim() || props.value,
228
+ value: props.value,
229
+ });
230
+ return;
231
+ }
232
+ collectStaticItems((child.props as { children?: React.ReactNode }).children).forEach((item) =>
233
+ items.push(item)
234
+ );
235
+ });
236
+
237
+ return items;
238
+ }
239
+
240
+ interface FilterStaticChildrenResult {
241
+ children: React.ReactNode;
242
+ visibleItemCount: number;
243
+ changed: boolean;
244
+ }
245
+
246
+ function filterStaticChildren(
247
+ node: React.ReactNode,
248
+ visibleValues: ReadonlySet<string>
249
+ ): FilterStaticChildrenResult {
250
+ let visibleItemCount = 0;
251
+ let changed = false;
252
+
253
+ const filteredChildren = React.Children.map(node, (child) => {
254
+ if (!React.isValidElement(child)) return child;
255
+ if (child.type === ComboboxItem) {
256
+ const props = child.props as ComboboxItemProps;
257
+ if (visibleValues.has(props.value)) {
258
+ visibleItemCount += 1;
259
+ return child;
260
+ }
261
+ changed = true;
262
+ return null;
263
+ }
264
+
265
+ const props = child.props as { children?: React.ReactNode };
266
+ if (props.children === undefined) return child;
267
+
268
+ const nested = filterStaticChildren(props.children, visibleValues);
269
+ visibleItemCount += nested.visibleItemCount;
270
+
271
+ if (child.type === ComboboxGroup && nested.visibleItemCount === 0) {
272
+ changed = true;
273
+ return null;
274
+ }
275
+ if (!nested.changed) return child;
276
+
277
+ changed = true;
278
+ return React.cloneElement(
279
+ child as React.ReactElement<{ children?: React.ReactNode }>,
280
+ undefined,
281
+ nested.children
282
+ );
283
+ });
284
+
285
+ return {
286
+ children: changed ? filteredChildren : node,
287
+ visibleItemCount,
288
+ changed,
289
+ };
290
+ }
291
+
200
292
  // ============================================
201
293
  // Components
202
294
  // ============================================
@@ -230,6 +322,12 @@ const ComboboxRoot = React.forwardRef<HTMLDivElement, ComboboxProps>(function Co
230
322
  ref
231
323
  ) {
232
324
  const size = useResolvedControlSize(sizeProp);
325
+ const staticItems = React.useMemo(() => collectStaticItems(children), [children]);
326
+ const staticValues = React.useMemo(() => staticItems.map((item) => item.value), [staticItems]);
327
+ const staticLabels = React.useMemo(
328
+ () => new Map(staticItems.map((item) => [item.value, item.label])),
329
+ [staticItems]
330
+ );
233
331
  // Track selected values for chip rendering
234
332
  const [internalValue, setInternalValue] = React.useState<string | string[] | null>(
235
333
  value ?? defaultValue ?? (multiple ? [] : null)
@@ -262,9 +360,11 @@ const ComboboxRoot = React.forwardRef<HTMLDivElement, ComboboxProps>(function Co
262
360
  );
263
361
 
264
362
  // Convert value → label for input display
265
- const itemToStringLabel = React.useCallback((itemValue: string) => {
266
- return itemsRef.current.get(itemValue) ?? itemValue;
267
- }, []);
363
+ const itemToStringLabel = React.useCallback(
364
+ (itemValue: string) =>
365
+ staticLabels.get(itemValue) ?? itemsRef.current.get(itemValue) ?? itemValue,
366
+ [staticLabels]
367
+ );
268
368
 
269
369
  // Derive selected values array for chip rendering
270
370
  const currentValue = value !== undefined ? value : internalValue;
@@ -292,6 +392,7 @@ const ComboboxRoot = React.forwardRef<HTMLDivElement, ComboboxProps>(function Co
292
392
  placeholder,
293
393
  multiple,
294
394
  selectedValues,
395
+ staticLabels,
295
396
  itemsRef,
296
397
  itemsVersion,
297
398
  incrementItemsVersion,
@@ -304,6 +405,7 @@ const ComboboxRoot = React.forwardRef<HTMLDivElement, ComboboxProps>(function Co
304
405
  placeholder,
305
406
  multiple,
306
407
  selectedValues,
408
+ staticLabels,
307
409
  itemsVersion,
308
410
  incrementItemsVersion,
309
411
  explicitTriggerCount,
@@ -347,6 +449,7 @@ const ComboboxRoot = React.forwardRef<HTMLDivElement, ComboboxProps>(function Co
347
449
  <ComboboxContext.Provider value={contextValue}>
348
450
  {wrapperContent(
349
451
  <BaseCombobox.Root<string, true>
452
+ items={staticValues}
350
453
  value={controlledValue}
351
454
  defaultValue={uncontrolledValue}
352
455
  onValueChange={handleValueChange}
@@ -391,6 +494,7 @@ const ComboboxRoot = React.forwardRef<HTMLDivElement, ComboboxProps>(function Co
391
494
  <ComboboxContext.Provider value={contextValue}>
392
495
  {wrapperContent(
393
496
  <BaseCombobox.Root<string, false>
497
+ items={staticValues}
394
498
  value={controlledValue}
395
499
  defaultValue={uncontrolledValue ?? null}
396
500
  onValueChange={handleValueChange}
@@ -450,7 +554,9 @@ function ComboboxInput({
450
554
  {context.selectedValues.map((chipValue) => (
451
555
  <BaseCombobox.Chip key={chipValue} className={styles.chip}>
452
556
  <span className={styles.chipLabel}>
453
- {context.itemsRef.current.get(chipValue) ?? chipValue}
557
+ {context.staticLabels.get(chipValue) ??
558
+ context.itemsRef.current.get(chipValue) ??
559
+ chipValue}
454
560
  </span>
455
561
  <BaseCombobox.ChipRemove className={styles.chipRemove}>
456
562
  <XIcon />
@@ -512,6 +618,17 @@ function ComboboxContent({
512
618
  ...htmlProps
513
619
  }: ComboboxContentProps) {
514
620
  const popupClasses = [styles.popup, className].filter(Boolean).join(" ");
621
+ const filteredItems = BaseCombobox.useFilteredItems() as string[];
622
+ const serializedFilteredItems = JSON.stringify(filteredItems);
623
+ const filteredItemIndices = React.useMemo(
624
+ () => itemIndicesFromSerializedValues(serializedFilteredItems),
625
+ [serializedFilteredItems]
626
+ );
627
+ const visibleValues = React.useMemo(() => new Set(filteredItems), [filteredItems]);
628
+ const filteredContent = React.useMemo(
629
+ () => filterStaticChildren(children, visibleValues),
630
+ [children, visibleValues]
631
+ );
515
632
 
516
633
  const popupStyle =
517
634
  maxVisibleItems != null
@@ -530,7 +647,9 @@ function ComboboxContent({
530
647
  className={styles.positioner}
531
648
  >
532
649
  <BaseCombobox.Popup {...htmlProps} className={popupClasses} style={popupStyle}>
533
- {children}
650
+ <ComboboxFilteredItemIndexContext.Provider value={filteredItemIndices}>
651
+ {filteredContent.children}
652
+ </ComboboxFilteredItemIndexContext.Provider>
534
653
  </BaseCombobox.Popup>
535
654
  </BaseCombobox.Positioner>
536
655
  </BaseCombobox.Portal>
@@ -539,6 +658,7 @@ function ComboboxContent({
539
658
 
540
659
  function ComboboxItem({ children, value, disabled, className, ...htmlProps }: ComboboxItemProps) {
541
660
  const { itemsRef, incrementItemsVersion } = React.useContext(ComboboxContext);
661
+ const filteredItemIndices = React.useContext(ComboboxFilteredItemIndexContext);
542
662
  const classes = [styles.item, className].filter(Boolean).join(" ");
543
663
 
544
664
  // Register this item's label in the registry so the input can display it
@@ -555,7 +675,13 @@ function ComboboxItem({ children, value, disabled, className, ...htmlProps }: Co
555
675
  }, [itemsRef, incrementItemsVersion, value, label]);
556
676
 
557
677
  return (
558
- <BaseCombobox.Item {...htmlProps} value={value} disabled={disabled} className={classes}>
678
+ <BaseCombobox.Item
679
+ {...htmlProps}
680
+ value={value}
681
+ index={filteredItemIndices.get(value)}
682
+ disabled={disabled}
683
+ className={classes}
684
+ >
559
685
  {children}
560
686
  <BaseCombobox.ItemIndicator className={styles.itemIndicator}>
561
687
  <CheckIcon />