@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.
- package/dist/assets/ui.css +327 -274
- package/dist/components/Checkbox/Checkbox.module.scss.cjs +11 -11
- package/dist/components/Checkbox/Checkbox.module.scss.js +11 -11
- package/dist/components/Chip/Chip.module.scss.cjs +15 -15
- package/dist/components/Chip/Chip.module.scss.js +15 -15
- package/dist/components/Combobox/index.cjs +106 -9
- package/dist/components/Combobox/index.d.ts.map +1 -1
- package/dist/components/Combobox/index.js +106 -9
- package/dist/components/DataTable/DataTable.module.scss.cjs +37 -34
- package/dist/components/DataTable/DataTable.module.scss.js +37 -34
- package/dist/components/Main/Main.module.scss.cjs +26 -0
- package/dist/components/Main/Main.module.scss.js +26 -0
- package/dist/components/Main/index.cjs +128 -0
- package/dist/components/Main/index.d.ts +22 -0
- package/dist/components/Main/index.d.ts.map +1 -0
- package/dist/components/Main/index.js +111 -0
- package/dist/components/Table/Table.module.scss.cjs +19 -19
- package/dist/components/Table/Table.module.scss.js +19 -19
- package/dist/components/TableOfContents/TableOfContents.module.scss.cjs +15 -15
- package/dist/components/TableOfContents/TableOfContents.module.scss.js +15 -15
- package/dist/components/TableOfContents/index.cjs +22 -42
- package/dist/components/TableOfContents/index.d.ts +3 -3
- package/dist/components/TableOfContents/index.d.ts.map +1 -1
- package/dist/components/TableOfContents/index.js +22 -42
- package/dist/index.cjs +87 -85
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/fragments.json +1 -1
- package/package.json +7 -2
- package/src/components/Checkbox/Checkbox.contract.json +5 -24
- package/src/components/Checkbox/Checkbox.module.scss +4 -2
- package/src/components/Chip/Chip.contract.json +11 -28
- package/src/components/Chip/Chip.module.scss +21 -45
- package/src/components/Combobox/Combobox.contract.json +6 -24
- package/src/components/Combobox/index.tsx +132 -6
- package/src/components/DataTable/DataTable.module.scss +31 -8
- package/src/components/Main/Main.contract.json +79 -0
- package/src/components/Main/Main.module.scss +31 -0
- package/src/components/Main/index.tsx +124 -0
- package/src/components/Table/Table.module.scss +22 -5
- package/src/components/TableOfContents/TableOfContents.contract.json +6 -16
- package/src/components/TableOfContents/TableOfContents.module.scss +10 -13
- package/src/components/TableOfContents/index.tsx +27 -38
- package/src/index.ts +3 -0
- package/src/tokens/_component-properties.scss +1 -0
- package/dist/components/Cloud/DashboardLayoutPrototypes.shared.d.ts +0 -33
- package/dist/components/Cloud/DashboardLayoutPrototypes.shared.d.ts.map +0 -1
- package/dist/components/Cloud/DashboardLayoutPrototypes.shell.d.ts +0 -11
- package/dist/components/Cloud/DashboardLayoutPrototypes.shell.d.ts.map +0 -1
- package/dist/components/Cloud/PullRequestsPrototypes.content.d.ts +0 -5
- package/dist/components/Cloud/PullRequestsPrototypes.content.d.ts.map +0 -1
- package/dist/components/Cloud/PullRequestsPrototypes.detail-panels.d.ts +0 -4
- package/dist/components/Cloud/PullRequestsPrototypes.detail-panels.d.ts.map +0 -1
- package/dist/components/Cloud/PullRequestsPrototypes.detail.d.ts +0 -3
- package/dist/components/Cloud/PullRequestsPrototypes.detail.d.ts.map +0 -1
- package/dist/components/Cloud/PullRequestsPrototypes.id-data.d.ts +0 -86
- package/dist/components/Cloud/PullRequestsPrototypes.id-data.d.ts.map +0 -1
- package/dist/components/Cloud/PullRequestsPrototypes.id-page.d.ts +0 -2
- package/dist/components/Cloud/PullRequestsPrototypes.id-page.d.ts.map +0 -1
- package/src/components/Accordion/Accordion.stories.tsx +0 -106
- package/src/components/Alert/Alert.stories.tsx +0 -119
- package/src/components/AppShell/AppShell.stories.tsx +0 -156
- package/src/components/Avatar/Avatar.stories.tsx +0 -74
- package/src/components/Badge/Badge.stories.tsx +0 -92
- package/src/components/BentoGrid/BentoGrid.stories.tsx +0 -98
- package/src/components/Box/Box.stories.tsx +0 -150
- package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +0 -87
- package/src/components/Button/Button.stories.tsx +0 -103
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +0 -97
- package/src/components/Card/Card.stories.tsx +0 -144
- package/src/components/Chart/Chart.stories.tsx +0 -119
- package/src/components/Checkbox/Checkbox.stories.tsx +0 -68
- package/src/components/Chip/Chip.stories.tsx +0 -70
- package/src/components/Cloud/CloudGovernanceShell.module.scss +0 -88
- package/src/components/Cloud/CloudGovernanceShell.stories.tsx +0 -446
- package/src/components/Cloud/ControlSizing.module.scss +0 -49
- package/src/components/Cloud/ControlSizing.stories.tsx +0 -238
- package/src/components/Cloud/DashboardLayoutPrototypes.module.scss +0 -462
- package/src/components/Cloud/DashboardLayoutPrototypes.shared.tsx +0 -375
- package/src/components/Cloud/DashboardLayoutPrototypes.shell.tsx +0 -147
- package/src/components/Cloud/DashboardLayoutPrototypes.stories.tsx +0 -328
- package/src/components/Cloud/PullRequestsPrototypes.content.tsx +0 -283
- package/src/components/Cloud/PullRequestsPrototypes.detail-panels.tsx +0 -381
- package/src/components/Cloud/PullRequestsPrototypes.detail.tsx +0 -204
- package/src/components/Cloud/PullRequestsPrototypes.id-data.ts +0 -395
- package/src/components/Cloud/PullRequestsPrototypes.id-page.tsx +0 -349
- package/src/components/Cloud/PullRequestsPrototypes.stories.tsx +0 -81
- package/src/components/CodeBlock/CodeBlock.stories.tsx +0 -147
- package/src/components/Collapsible/Collapsible.stories.tsx +0 -94
- package/src/components/ColorPicker/ColorPicker.stories.tsx +0 -74
- package/src/components/Combobox/Combobox.stories.tsx +0 -133
- package/src/components/Command/Command.stories.tsx +0 -102
- package/src/components/ConversationList/ConversationList.stories.tsx +0 -93
- package/src/components/DataTable/DataTable.stories.tsx +0 -129
- package/src/components/DatePicker/DatePicker.stories.tsx +0 -129
- package/src/components/Dialog/Dialog.stories.tsx +0 -154
- package/src/components/Drawer/Drawer.stories.tsx +0 -144
- package/src/components/Editor/Editor.stories.tsx +0 -99
- package/src/components/EmptyState/EmptyState.stories.tsx +0 -97
- package/src/components/Field/Field.stories.tsx +0 -109
- package/src/components/Fieldset/Fieldset.stories.tsx +0 -116
- package/src/components/Form/Form.stories.tsx +0 -142
- package/src/components/Grid/Grid.stories.tsx +0 -125
- package/src/components/Header/Header.stories.tsx +0 -133
- package/src/components/Icon/Icon.stories.tsx +0 -80
- package/src/components/IconButton/IconButton.stories.tsx +0 -78
- package/src/components/Image/Image.stories.tsx +0 -109
- package/src/components/Input/Input.stories.tsx +0 -104
- package/src/components/Link/Link.stories.tsx +0 -67
- package/src/components/List/List.stories.tsx +0 -95
- package/src/components/Listbox/Listbox.stories.tsx +0 -81
- package/src/components/Loading/Loading.stories.tsx +0 -69
- package/src/components/Markdown/Markdown.stories.tsx +0 -75
- package/src/components/Menu/Menu.stories.tsx +0 -113
- package/src/components/Message/Message.stories.tsx +0 -85
- package/src/components/NavigationMenu/NavigationMenu.stories.tsx +0 -161
- package/src/components/Pagination/Pagination.stories.tsx +0 -81
- package/src/components/Popover/Popover.stories.tsx +0 -152
- package/src/components/Progress/Progress.stories.tsx +0 -64
- package/src/components/Prompt/Prompt.stories.tsx +0 -93
- package/src/components/RadioGroup/RadioGroup.stories.tsx +0 -107
- package/src/components/ScrollArea/ScrollArea.stories.tsx +0 -97
- package/src/components/Select/Select.stories.tsx +0 -127
- package/src/components/Separator/Separator.stories.tsx +0 -84
- package/src/components/Sidebar/Sidebar.stories.tsx +0 -122
- package/src/components/Skeleton/Skeleton.stories.tsx +0 -80
- package/src/components/Slider/Slider.stories.tsx +0 -73
- package/src/components/Stack/Stack.stories.tsx +0 -73
- package/src/components/Switch/Switch.stories.tsx +0 -59
- package/src/components/Table/Table.stories.tsx +0 -219
- package/src/components/TableOfContents/TableOfContents.stories.tsx +0 -120
- package/src/components/Tabs/Tabs.stories.tsx +0 -136
- package/src/components/Text/Text.stories.tsx +0 -133
- package/src/components/Textarea/Textarea.stories.tsx +0 -107
- package/src/components/Theme/Theme.stories.tsx +0 -95
- package/src/components/ThinkingIndicator/ThinkingIndicator.stories.tsx +0 -85
- package/src/components/Toast/Toast.stories.tsx +0 -91
- package/src/components/ToggleGroup/ToggleGroup.stories.tsx +0 -114
- package/src/components/Tooltip/Tooltip.stories.tsx +0 -95
- 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.
|
|
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.
|
|
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
|
|
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
|
|
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": "<
|
|
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
|
|
2
|
-
@use
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
color: var(--fui-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
266
|
-
|
|
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.
|
|
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
|
-
{
|
|
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
|
|
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 />
|