Skip to content

Commit 33b066e

Browse files
committed
feat(combobox refactor): at this point, the functionality and styling are looking good, still needs testing, etc tho
1 parent 312b73e commit 33b066e

16 files changed

+1056
-3582
lines changed

packages/nimbus/src/components/combobox/COMBOBOX_REFACTOR_PLAN.md

Lines changed: 0 additions & 3412 deletions
This file was deleted.

packages/nimbus/src/components/combobox/TESTING_PLAN.md

Lines changed: 487 additions & 0 deletions
Large diffs are not rendered by default.

packages/nimbus/src/components/combobox/combobox.i18n.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,14 @@ export const messages = defineMessages({
1616
description: "aria-label for selected values tag group",
1717
defaultMessage: "Selected values",
1818
},
19+
menu: {
20+
id: "Nimbus.ComboBox.menu",
21+
description: "aria-label for combobox menu popover",
22+
defaultMessage: "Menu",
23+
},
24+
options: {
25+
id: "Nimbus.ComboBox.options",
26+
description: "aria-label for combobox menu options",
27+
defaultMessage: "Options",
28+
},
1929
});

packages/nimbus/src/components/combobox/combobox.recipe.ts

Lines changed: 113 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { defineSlotRecipe } from "@chakra-ui/react/styled-system";
2-
import { selectSlotRecipe } from "../select/select.recipe";
32
import { checkboxSlotRecipe } from "../checkbox/checkbox.recipe";
43

54
/**
@@ -11,6 +10,7 @@ export const comboBoxSlotRecipe = defineSlotRecipe({
1110
"root",
1211
"trigger",
1312
"leadingElement",
13+
"content",
1414
"tagGroup",
1515
"input",
1616
"popover",
@@ -26,9 +26,7 @@ export const comboBoxSlotRecipe = defineSlotRecipe({
2626
// Base styles applied to all instances of the component
2727
base: {
2828
root: {
29-
"--leading-element-width": "sizes.200",
3029
colorPalette: "primary",
31-
focusRing: "outside",
3230
display: "inline-flex",
3331
position: "relative",
3432
alignSelf: "flex-start",
@@ -47,74 +45,139 @@ export const comboBoxSlotRecipe = defineSlotRecipe({
4745
pointerEvents: "none!",
4846
},
4947
},
50-
"& input": {
51-
cursor: "text",
52-
_hover: {
53-
bg: "primary.2",
54-
},
55-
_placeholder: { opacity: 0.5 },
56-
},
5748
"& [data-placeholder]": {
5849
opacity: 0.5,
5950
},
6051
},
6152
leadingElement: {
62-
position: "absolute",
63-
display: "flex",
53+
gridArea: "leadingElement",
54+
display: "contents",
6455
alignItems: "center",
56+
justifyContent: "center",
6557
color: "neutral.11",
58+
"& svg": {
59+
minH: "600",
60+
minW: "600",
61+
pr: "100",
62+
},
6663
},
6764
trigger: {
68-
display: "flex",
65+
display: "grid",
66+
gridTemplateColumns: "auto 1fr auto auto",
67+
gridTemplateAreas: '"leadingElement content toggle clear"',
68+
alignItems: "center",
69+
gap: "100",
70+
width: "100%",
6971
focusRing: "outside",
70-
alignItems: "flex-start",
71-
pr: "1600",
72-
pl: "400",
72+
px: "300",
7373
py: "100",
7474
borderRadius: "200",
7575
color: "neutral.12",
76-
maxWidth: "100%",
7776
textAlign: "left",
78-
whiteSpace: "nowrap",
79-
// overflow: "hidden",
80-
textOverflow: "ellipsis",
8177
boxShadow: "inset 0 0 0 var(--border-width) var(--border-color)",
78+
_focusWithin: {
79+
layerStyle: "focusRing",
80+
},
8281
_disabled: { pointerEvents: "none" },
8382
'[data-invalid="true"] &': {
8483
"--border-width": "sizes.50",
8584
"--border-color": "colors.critical.7",
8685
color: "critical.11",
8786
},
88-
'& button[slot="remove"]': {
87+
'& button[slot="toggle"]': {
88+
gridArea: "toggle",
89+
alignSelf: "center",
90+
},
91+
'& button[slot="clear"]': {
92+
gridArea: "clear",
93+
alignSelf: "center",
8994
_expanded: {
9095
bg: "colorPalette.3",
9196
},
9297
},
9398
},
94-
// buttonGroup: {
95-
// position: "absolute",
96-
// display: "inline-flex",
97-
// top: 0,
98-
// bottom: 0,
99-
// right: 300,
100-
// my: "auto",
101-
// },
99+
content: {
100+
gridArea: "content",
101+
display: "flex",
102+
flexWrap: "wrap",
103+
alignItems: "center",
104+
gap: "100",
105+
minWidth: 0,
106+
},
107+
tagGroup: {
108+
display: "contents",
109+
},
110+
input: {
111+
flex: "0 0 auto",
112+
minWidth: "0",
113+
maxWidth: "100%",
114+
border: "none",
115+
outline: "none",
116+
bg: "transparent",
117+
padding: 0,
118+
cursor: "text",
119+
// If the value & placeholder are falsy, input width should be 1px
120+
// so it doesn't cause a blank line in content box
121+
'&[data-empty="true"]': {
122+
width: "25",
123+
},
124+
_hover: {
125+
bg: "primary.2",
126+
},
127+
_placeholder: { opacity: 0.5 },
128+
},
102129
popover: {
103130
bg: "bg",
104131
borderRadius: "200",
105132
boxShadow: "5",
133+
width: "var(--trigger-width)",
106134
},
107-
108135
listBox: {
109-
...selectSlotRecipe.base?.options,
110136
gap: "100",
111137
},
112138
section: {
113-
...selectSlotRecipe.base?.optionGroup,
139+
textStyle: "xs",
140+
color: "neutral.11",
141+
fontWeight: "600",
142+
lineHeight: "350",
143+
letterSpacing: "25",
144+
textTransform: "uppercase",
145+
p: "200",
146+
borderBottom: "solid-25",
147+
borderColor: "neutral.3",
148+
mx: "-200",
149+
mt: "200",
150+
mb: "300",
114151
},
115152
option: {
116-
...selectSlotRecipe.base?.option,
153+
focusRing: "outside",
154+
cursor: "menuitem",
155+
color: "neutral.12",
156+
textStyle: "sm",
157+
p: "200",
158+
borderRadius: "200",
117159
whiteSpace: "wrap",
160+
overflow: "hidden",
161+
textOverflow: "ellipsis",
162+
'&[aria-selected="true"]': {
163+
bg: "primary.3",
164+
},
165+
'&[data-focused="true"]': {
166+
bg: "primary.2",
167+
},
168+
'& [slot="label"]': {
169+
display: "block",
170+
},
171+
172+
'& [slot="description"]': {
173+
display: "block",
174+
color: "neutral.11",
175+
textStyle: "xs",
176+
},
177+
178+
"&[data-disabled='true']": {
179+
layerStyle: "disabled",
180+
},
118181
},
119182
optionIndicator: {
120183
// make sure option is aligned with the first line of text (using lh units)
@@ -141,48 +204,53 @@ export const comboBoxSlotRecipe = defineSlotRecipe({
141204
size: {
142205
// Small
143206
sm: {
144-
value: {
207+
trigger: {
145208
minH: "800",
146209
textStyle: "sm",
147-
pl: "calc(var(--leading-element-width) + {sizes.100})",
148210
},
149211
leadingElement: {
150212
minH: "800",
151-
pl: "300",
152213
},
153214
},
154215
// Medium
155216
md: {
156-
value: {
217+
trigger: {
157218
minH: "1000",
158219
textStyle: "md",
159-
pl: "calc(var(--leading-element-width) + {sizes.200})",
160220
},
161221
leadingElement: {
162222
minH: "1000",
163-
pl: "400",
164223
},
165224
},
166225
},
167226
// Visual style variants
168227
variant: {
169228
solid: {
170229
root: {
230+
bg: "primary.1",
231+
"&:hover": {
232+
bg: "primary.2",
233+
},
171234
width: "7200",
172235
},
173-
value: {
174-
...selectSlotRecipe.variants?.variant.outline.trigger,
236+
trigger: {
237+
"--border-width": "sizes.25",
238+
"--border-color": "colors.neutral.7",
239+
175240
width: "100%",
176241
},
177242
},
178243
ghost: {
179244
root: {
180-
...selectSlotRecipe.variants?.variant.ghost.root,
245+
bg: "transparent",
246+
"&:hover": {
247+
bg: "primaryAlpha.2",
248+
},
181249
maxW: "7200",
182250
},
183-
184-
value: {
185-
...selectSlotRecipe.variants?.variant.ghost.trigger,
251+
trigger: {
252+
"--border-width": "sizes.25",
253+
"--border-color": "transparent",
186254
bg: "transparent",
187255
},
188256
},
@@ -272,10 +340,6 @@ export const comboBoxSlotRecipe = defineSlotRecipe({
272340
trigger: {
273341
flex: "1 1 auto",
274342
},
275-
listBox: {
276-
minW: "max(var(--trigger-width), {sizes.2000})",
277-
maxW: "max(var(--trigger-width), {sizes.7200})",
278-
},
279343
},
280344
},
281345
],

packages/nimbus/src/components/combobox/combobox.slots.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
ComboBoxRootSlotProps,
55
ComboBoxTriggerSlotProps,
66
ComboBoxLeadingElementSlotProps,
7+
ComboBoxContentSlotProps,
78
ComboBoxTagGroupSlotProps,
89
ComboBoxInputSlotProps,
910
ComboBoxPopoverSlotProps,
@@ -36,6 +37,12 @@ export const ComboBoxLeadingElementSlot = withContext<
3637
ComboBoxLeadingElementSlotProps
3738
>("div", "leadingElement");
3839

40+
// Content slot - wrapper for tags and input (flex container within grid)
41+
export const ComboBoxContentSlot = withContext<
42+
HTMLDivElement,
43+
ComboBoxContentSlotProps
44+
>("div", "content");
45+
3946
// TagGroup slot - container for selected tags (multi-select)
4047
export const ComboBoxTagGroupSlot = withContext<
4148
HTMLDivElement,

packages/nimbus/src/components/combobox/combobox.stories.tsx

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,20 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
1010
// fn,
1111
// waitFor,
1212
// } from "storybook/test";
13-
import {
14-
// Box,
15-
// Button,
16-
// // ComboBox,
17-
// type ComboBoxRootProps,
18-
// Flex,
19-
// FormField,
20-
// Icon,
21-
IconButton,
22-
// RadioInput,
23-
// Stack,
24-
// Text,
25-
} from "@commercetools/nimbus";
26-
import {
27-
// AddReaction,
28-
Close,
29-
// Search,
30-
KeyboardArrowDown,
31-
} from "@commercetools/nimbus-icons";
13+
// import {
14+
// // Box,
15+
// // Button,
16+
// // // ComboBox,
17+
// // type ComboBoxRootProps,
18+
// // Flex,
19+
// // FormField,
20+
// // Icon,
21+
// // IconButton,
22+
// // RadioInput,
23+
// // Stack,
24+
// // Text,
25+
// } from "@commercetools/nimbus";
26+
import { Search } from "@commercetools/nimbus-icons";
3227
import { ComboBox } from "./combobox";
3328
import type { ComboBoxRootProps } from "./combobox.types";
3429
import { defaultGetTextValue } from "./utils/collection";
@@ -62,20 +57,10 @@ const options: SimpleOption[] = [
6257
{ id: 6, name: "Skunk" },
6358
];
6459

65-
function SingleSelectComboBox<T extends object>(props: ComboBoxRootProps<T>) {
66-
console.log(props);
60+
function ComposedComboBox<T extends object>(props: ComboBoxRootProps<T>) {
6761
return (
6862
<ComboBox.Root {...props}>
69-
<ComboBox.Trigger>
70-
<ComboBox.TagGroup />
71-
<ComboBox.Input />
72-
<IconButton slot="toggle">
73-
<KeyboardArrowDown />
74-
</IconButton>
75-
<IconButton slot="clear">
76-
<Close />
77-
</IconButton>
78-
</ComboBox.Trigger>
63+
<ComboBox.Trigger />
7964
<ComboBox.Popover>
8065
<ComboBox.ListBox>
8166
{(item: T) => (
@@ -94,10 +79,15 @@ function SingleSelectComboBox<T extends object>(props: ComboBoxRootProps<T>) {
9479
export const Base: Story = {
9580
render: () => {
9681
return (
97-
<SingleSelectComboBox<SimpleOption>
82+
<ComposedComboBox<SimpleOption>
83+
aria-label="test"
9884
items={options}
85+
allowsEmptyMenu={true}
86+
renderEmptyState={() => "hi"}
9987
selectionMode="multiple"
100-
selectedKeys={[1]}
88+
selectedKeys={[1, 3]}
89+
disabledKeys={[2, 4]}
90+
leadingElement={<Search />}
10191
/>
10292
);
10393
},

0 commit comments

Comments
 (0)