@usefragments/ui 1.1.1 → 1.3.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 (148) 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/DataTable/index.d.ts.map +1 -1
  12. package/dist/components/Main/Main.module.scss.cjs +26 -0
  13. package/dist/components/Main/Main.module.scss.js +26 -0
  14. package/dist/components/Main/index.cjs +128 -0
  15. package/dist/components/Main/index.d.ts +22 -0
  16. package/dist/components/Main/index.d.ts.map +1 -0
  17. package/dist/components/Main/index.js +111 -0
  18. package/dist/components/Table/Table.module.scss.cjs +19 -19
  19. package/dist/components/Table/Table.module.scss.js +19 -19
  20. package/dist/components/TableOfContents/TableOfContents.module.scss.cjs +15 -15
  21. package/dist/components/TableOfContents/TableOfContents.module.scss.js +15 -15
  22. package/dist/components/TableOfContents/index.cjs +22 -42
  23. package/dist/components/TableOfContents/index.d.ts +3 -3
  24. package/dist/components/TableOfContents/index.d.ts.map +1 -1
  25. package/dist/components/TableOfContents/index.js +22 -42
  26. package/dist/{components/DataTable/DataTable.virtual.cjs → data-table-virtual.cjs} +1 -1
  27. package/dist/{components/DataTable/DataTable.virtual.js → data-table-virtual.js} +1 -1
  28. package/dist/data-table.cjs +27 -9
  29. package/dist/data-table.js +19 -1
  30. package/dist/index.cjs +87 -88
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -3
  34. package/fragments.json +1 -1
  35. package/package.json +57 -2
  36. package/src/components/Checkbox/Checkbox.contract.json +5 -24
  37. package/src/components/Checkbox/Checkbox.module.scss +4 -2
  38. package/src/components/Chip/Chip.contract.json +11 -28
  39. package/src/components/Chip/Chip.module.scss +21 -45
  40. package/src/components/Combobox/Combobox.contract.json +6 -24
  41. package/src/components/Combobox/index.tsx +132 -6
  42. package/src/components/DataTable/DataTable.module.scss +31 -8
  43. package/src/components/DataTable/index.tsx +44 -16
  44. package/src/components/Main/Main.contract.json +79 -0
  45. package/src/components/Main/Main.module.scss +31 -0
  46. package/src/components/Main/index.tsx +124 -0
  47. package/src/components/Table/Table.module.scss +22 -5
  48. package/src/components/TableOfContents/TableOfContents.contract.json +6 -16
  49. package/src/components/TableOfContents/TableOfContents.module.scss +10 -13
  50. package/src/components/TableOfContents/index.tsx +27 -38
  51. package/src/index.ts +11 -9
  52. package/src/styles/styles.d.ts +8 -0
  53. package/src/tokens/_component-properties.scss +1 -0
  54. package/dist/components/Cloud/DashboardLayoutPrototypes.shared.d.ts +0 -33
  55. package/dist/components/Cloud/DashboardLayoutPrototypes.shared.d.ts.map +0 -1
  56. package/dist/components/Cloud/DashboardLayoutPrototypes.shell.d.ts +0 -11
  57. package/dist/components/Cloud/DashboardLayoutPrototypes.shell.d.ts.map +0 -1
  58. package/dist/components/Cloud/PullRequestsPrototypes.content.d.ts +0 -5
  59. package/dist/components/Cloud/PullRequestsPrototypes.content.d.ts.map +0 -1
  60. package/dist/components/Cloud/PullRequestsPrototypes.detail-panels.d.ts +0 -4
  61. package/dist/components/Cloud/PullRequestsPrototypes.detail-panels.d.ts.map +0 -1
  62. package/dist/components/Cloud/PullRequestsPrototypes.detail.d.ts +0 -3
  63. package/dist/components/Cloud/PullRequestsPrototypes.detail.d.ts.map +0 -1
  64. package/dist/components/Cloud/PullRequestsPrototypes.id-data.d.ts +0 -86
  65. package/dist/components/Cloud/PullRequestsPrototypes.id-data.d.ts.map +0 -1
  66. package/dist/components/Cloud/PullRequestsPrototypes.id-page.d.ts +0 -2
  67. package/dist/components/Cloud/PullRequestsPrototypes.id-page.d.ts.map +0 -1
  68. package/src/components/Accordion/Accordion.stories.tsx +0 -106
  69. package/src/components/Alert/Alert.stories.tsx +0 -119
  70. package/src/components/AppShell/AppShell.stories.tsx +0 -156
  71. package/src/components/Avatar/Avatar.stories.tsx +0 -74
  72. package/src/components/Badge/Badge.stories.tsx +0 -92
  73. package/src/components/BentoGrid/BentoGrid.stories.tsx +0 -98
  74. package/src/components/Box/Box.stories.tsx +0 -150
  75. package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +0 -87
  76. package/src/components/Button/Button.stories.tsx +0 -103
  77. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +0 -97
  78. package/src/components/Card/Card.stories.tsx +0 -144
  79. package/src/components/Chart/Chart.stories.tsx +0 -119
  80. package/src/components/Checkbox/Checkbox.stories.tsx +0 -68
  81. package/src/components/Chip/Chip.stories.tsx +0 -70
  82. package/src/components/Cloud/CloudGovernanceShell.module.scss +0 -88
  83. package/src/components/Cloud/CloudGovernanceShell.stories.tsx +0 -446
  84. package/src/components/Cloud/ControlSizing.module.scss +0 -49
  85. package/src/components/Cloud/ControlSizing.stories.tsx +0 -238
  86. package/src/components/Cloud/DashboardLayoutPrototypes.module.scss +0 -462
  87. package/src/components/Cloud/DashboardLayoutPrototypes.shared.tsx +0 -375
  88. package/src/components/Cloud/DashboardLayoutPrototypes.shell.tsx +0 -147
  89. package/src/components/Cloud/DashboardLayoutPrototypes.stories.tsx +0 -328
  90. package/src/components/Cloud/PullRequestsPrototypes.content.tsx +0 -283
  91. package/src/components/Cloud/PullRequestsPrototypes.detail-panels.tsx +0 -381
  92. package/src/components/Cloud/PullRequestsPrototypes.detail.tsx +0 -204
  93. package/src/components/Cloud/PullRequestsPrototypes.id-data.ts +0 -395
  94. package/src/components/Cloud/PullRequestsPrototypes.id-page.tsx +0 -349
  95. package/src/components/Cloud/PullRequestsPrototypes.stories.tsx +0 -81
  96. package/src/components/CodeBlock/CodeBlock.stories.tsx +0 -147
  97. package/src/components/Collapsible/Collapsible.stories.tsx +0 -94
  98. package/src/components/ColorPicker/ColorPicker.stories.tsx +0 -74
  99. package/src/components/Combobox/Combobox.stories.tsx +0 -133
  100. package/src/components/Command/Command.stories.tsx +0 -102
  101. package/src/components/ConversationList/ConversationList.stories.tsx +0 -93
  102. package/src/components/DataTable/DataTable.stories.tsx +0 -129
  103. package/src/components/DatePicker/DatePicker.stories.tsx +0 -129
  104. package/src/components/Dialog/Dialog.stories.tsx +0 -154
  105. package/src/components/Drawer/Drawer.stories.tsx +0 -144
  106. package/src/components/Editor/Editor.stories.tsx +0 -99
  107. package/src/components/EmptyState/EmptyState.stories.tsx +0 -97
  108. package/src/components/Field/Field.stories.tsx +0 -109
  109. package/src/components/Fieldset/Fieldset.stories.tsx +0 -116
  110. package/src/components/Form/Form.stories.tsx +0 -142
  111. package/src/components/Grid/Grid.stories.tsx +0 -125
  112. package/src/components/Header/Header.stories.tsx +0 -133
  113. package/src/components/Icon/Icon.stories.tsx +0 -80
  114. package/src/components/IconButton/IconButton.stories.tsx +0 -78
  115. package/src/components/Image/Image.stories.tsx +0 -109
  116. package/src/components/Input/Input.stories.tsx +0 -104
  117. package/src/components/Link/Link.stories.tsx +0 -67
  118. package/src/components/List/List.stories.tsx +0 -95
  119. package/src/components/Listbox/Listbox.stories.tsx +0 -81
  120. package/src/components/Loading/Loading.stories.tsx +0 -69
  121. package/src/components/Markdown/Markdown.stories.tsx +0 -75
  122. package/src/components/Menu/Menu.stories.tsx +0 -113
  123. package/src/components/Message/Message.stories.tsx +0 -85
  124. package/src/components/NavigationMenu/NavigationMenu.stories.tsx +0 -161
  125. package/src/components/Pagination/Pagination.stories.tsx +0 -81
  126. package/src/components/Popover/Popover.stories.tsx +0 -152
  127. package/src/components/Progress/Progress.stories.tsx +0 -64
  128. package/src/components/Prompt/Prompt.stories.tsx +0 -93
  129. package/src/components/RadioGroup/RadioGroup.stories.tsx +0 -107
  130. package/src/components/ScrollArea/ScrollArea.stories.tsx +0 -97
  131. package/src/components/Select/Select.stories.tsx +0 -127
  132. package/src/components/Separator/Separator.stories.tsx +0 -84
  133. package/src/components/Sidebar/Sidebar.stories.tsx +0 -122
  134. package/src/components/Skeleton/Skeleton.stories.tsx +0 -80
  135. package/src/components/Slider/Slider.stories.tsx +0 -73
  136. package/src/components/Stack/Stack.stories.tsx +0 -73
  137. package/src/components/Switch/Switch.stories.tsx +0 -59
  138. package/src/components/Table/Table.stories.tsx +0 -219
  139. package/src/components/TableOfContents/TableOfContents.stories.tsx +0 -120
  140. package/src/components/Tabs/Tabs.stories.tsx +0 -136
  141. package/src/components/Text/Text.stories.tsx +0 -133
  142. package/src/components/Textarea/Textarea.stories.tsx +0 -107
  143. package/src/components/Theme/Theme.stories.tsx +0 -95
  144. package/src/components/ThinkingIndicator/ThinkingIndicator.stories.tsx +0 -85
  145. package/src/components/Toast/Toast.stories.tsx +0 -91
  146. package/src/components/ToggleGroup/ToggleGroup.stories.tsx +0 -114
  147. package/src/components/Tooltip/Tooltip.stories.tsx +0 -95
  148. 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.3.0",
4
4
  "license": "MIT",
5
5
  "description": "Customizable UI components built on Base UI headless primitives",
6
6
  "author": "Conan McNicholl",
@@ -99,6 +99,16 @@
99
99
  "default": "./dist/data-table.cjs"
100
100
  }
101
101
  },
102
+ "./data-table-virtual": {
103
+ "import": {
104
+ "types": "./dist/components/DataTable/DataTable.virtual.d.ts",
105
+ "default": "./dist/data-table-virtual.js"
106
+ },
107
+ "require": {
108
+ "types": "./dist/components/DataTable/DataTable.virtual.d.ts",
109
+ "default": "./dist/data-table-virtual.cjs"
110
+ }
111
+ },
102
112
  "./editor": {
103
113
  "import": {
104
114
  "types": "./dist/components/Editor/index.d.ts",
@@ -110,10 +120,12 @@
110
120
  }
111
121
  },
112
122
  "./styles": {
123
+ "types": "./src/styles/styles.d.ts",
113
124
  "sass": "./src/styles/globals.scss",
114
125
  "default": "./dist/assets/ui.css"
115
126
  },
116
127
  "./globals": {
128
+ "types": "./src/styles/styles.d.ts",
117
129
  "sass": "./src/styles/globals.scss",
118
130
  "default": "./dist/assets/ui.css"
119
131
  },
@@ -155,6 +167,44 @@
155
167
  "remark-gfm": ">=4.0.0",
156
168
  "shiki": ">=1.0.0"
157
169
  },
170
+ "peerDependenciesMeta": {
171
+ "@tanstack/react-table": {
172
+ "optional": true
173
+ },
174
+ "@tanstack/react-virtual": {
175
+ "optional": true
176
+ },
177
+ "@tiptap/extension-link": {
178
+ "optional": true
179
+ },
180
+ "@tiptap/react": {
181
+ "optional": true
182
+ },
183
+ "@tiptap/starter-kit": {
184
+ "optional": true
185
+ },
186
+ "date-fns": {
187
+ "optional": true
188
+ },
189
+ "react-colorful": {
190
+ "optional": true
191
+ },
192
+ "react-day-picker": {
193
+ "optional": true
194
+ },
195
+ "react-markdown": {
196
+ "optional": true
197
+ },
198
+ "recharts": {
199
+ "optional": true
200
+ },
201
+ "remark-gfm": {
202
+ "optional": true
203
+ },
204
+ "shiki": {
205
+ "optional": true
206
+ }
207
+ },
158
208
  "dependencies": {
159
209
  "@base-ui/react": "1.6.0",
160
210
  "@phosphor-icons/react": "^2.1.10"
@@ -190,10 +240,12 @@
190
240
  "vite": "^6.0.0",
191
241
  "vitest": "^2.1.8",
192
242
  "vitest-axe": "^0.1.0",
193
- "@usefragments/core": "1.2.0"
243
+ "@usefragments/core": "1.4.0"
194
244
  },
195
245
  "files": [
196
246
  "src",
247
+ "!src/**/.fragments/**",
248
+ "!src/**/.impeccable/**",
197
249
  "!src/**/*.test.ts",
198
250
  "!src/**/*.test.tsx",
199
251
  "!src/test/",
@@ -201,7 +253,10 @@
201
253
  "!src/**/*.block.ts",
202
254
  "!src/blocks/",
203
255
  "!src/assets/*.svg",
256
+ "!src/**/*.stories.tsx",
257
+ "!src/components/Cloud/",
204
258
  "dist",
259
+ "!dist/components/Cloud/",
205
260
  "fragments.json"
206
261
  ],
207
262
  "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 />