Skip to content

Commit 6af1c86

Browse files
Merge pull request #530 from buildo/select-field-mode
Select field mode
2 parents 9d1a569 + a68f138 commit 6af1c86

File tree

5 files changed

+86
-21
lines changed

5 files changed

+86
-21
lines changed

Diff for: packages/bento-design-system/src/SelectField/Config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ChipProps } from "../Chip/Chip";
12
import { IconProps } from "../Icons";
23
import { BentoSprinkles } from "../internal";
34
import { ListConfig } from "../List/Config";
@@ -11,4 +12,6 @@ export type DropdownConfig = {
1112
defaultMenuSize: "medium" | "large";
1213
openIndicatorIcon: (props: IconProps) => Children;
1314
openIndicatorIconSize: IconProps["size"];
15+
chipColor: ChipProps["color"];
16+
chipSpacing: BentoSprinkles["gap"];
1417
};

Diff for: packages/bento-design-system/src/SelectField/SelectField.tsx

+42-19
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Select, {
44
MultiValueProps,
55
SingleValue as SingleValueT,
66
} from "react-select";
7-
import { Body, ListSize, LocalizedString } from "..";
7+
import { Body, Chip, ListSize, LocalizedString } from "..";
88
import { useField } from "@react-aria/label";
99
import { useEffect, useMemo } from "react";
1010
import { FieldProps } from "../Field/FieldProps";
@@ -24,11 +24,20 @@ export type SelectOption<A> = Omit<
2424

2525
type MultiProps<A> = {
2626
isMulti: true;
27-
multiValueMessage?: (numberOfSelectedOptions: number) => LocalizedString;
2827
showMultiSelectBulkActions?: boolean;
2928
selectAllButtonLabel?: LocalizedString;
3029
clearAllButtonLabel?: LocalizedString;
31-
} & FieldProps<A[]>;
30+
} & FieldProps<A[]> &
31+
(
32+
| {
33+
multiSelectMode?: "summary";
34+
multiValueMessage?: (numberOfSelectedOptions: number) => LocalizedString;
35+
}
36+
| {
37+
multiSelectMode: "chips";
38+
multiValueMessage?: never;
39+
}
40+
);
3241

3342
type SingleProps<A> = {
3443
isMulti?: false;
@@ -43,6 +52,8 @@ type Props<A> = {
4352
searchable?: boolean;
4453
} & (SingleProps<A> | MultiProps<A>);
4554

55+
export type { Props as SelectFieldProps };
56+
4657
declare module "react-select/dist/declarations/src/Select" {
4758
export interface Props<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
4859
menuSize?: ListSize;
@@ -52,6 +63,7 @@ declare module "react-select/dist/declarations/src/Select" {
5263
showMultiSelectBulkActions?: boolean;
5364
selectAllButtonLabel?: LocalizedString;
5465
clearAllButtonLabel?: LocalizedString;
66+
multiSelectMode?: "summary" | "chips";
5567
}
5668
}
5769

@@ -165,7 +177,7 @@ export function SelectField<A>(props: Props<A>) {
165177
isClearable={false}
166178
noOptionsMessage={() => noOptionsMessage ?? defaultMessages.SelectField.noOptionsMessage}
167179
multiValueMessage={
168-
isMulti
180+
props.isMulti && (!props.multiSelectMode || props.multiSelectMode === "summary")
169181
? props.multiValueMessage ?? defaultMessages.SelectField.multiOptionsSelected
170182
: undefined
171183
}
@@ -185,6 +197,7 @@ export function SelectField<A>(props: Props<A>) {
185197
? props.selectAllButtonLabel ?? defaultMessages.SelectField.selectAllButtonLabel
186198
: undefined
187199
}
200+
multiSelectMode={isMulti ? props.multiSelectMode : undefined}
188201
/>
189202
</Field>
190203
</BentoConfigProvider>
@@ -194,23 +207,33 @@ export function SelectField<A>(props: Props<A>) {
194207
// NOTE(gabro): we override MultiValue instead of ValueContainer (which would be more natural)
195208
// because overriding ValueContainer breaks the logic for closing the menu when clicking away.
196209
// See: https://github.com/JedWatson/react-select/issues/2239#issuecomment-861848975
197-
function MultiValue<A>(props: MultiValueProps<A>) {
210+
function MultiValue<A extends SelectOption<unknown>>(props: MultiValueProps<A>) {
198211
const inputConfig = useBentoConfig().input;
199-
const numberOfSelectedOptions = props.getValue().length;
212+
const dropdownConfig = useBentoConfig().dropdown;
213+
switch (props.selectProps.multiSelectMode ?? "summary") {
214+
case "summary":
215+
const numberOfSelectedOptions = props.getValue().length;
200216

201-
if (props.index > 0 || !props.selectProps.multiValueMessage) {
202-
return null;
203-
}
217+
if (props.index > 0 || !props.selectProps.multiValueMessage) {
218+
return null;
219+
}
204220

205-
if (numberOfSelectedOptions === 1) {
206-
return selectComponents.SingleValue(props);
207-
}
221+
if (numberOfSelectedOptions === 1) {
222+
return selectComponents.SingleValue(props);
223+
}
208224

209-
return (
210-
<Body size={inputConfig.fontSize}>
211-
{props.selectProps.multiValueMessage(numberOfSelectedOptions)}
212-
</Body>
213-
);
225+
return (
226+
<Body size={inputConfig.fontSize}>
227+
{props.selectProps.multiValueMessage(numberOfSelectedOptions)}
228+
</Body>
229+
);
230+
case "chips":
231+
return (
232+
<Chip
233+
color={dropdownConfig.chipColor}
234+
label={props.data.label as LocalizedString}
235+
onDismiss={props.removeProps.onClick as () => void}
236+
/>
237+
);
238+
}
214239
}
215-
216-
export type { Props as SelectFieldProps };

Diff for: packages/bento-design-system/src/SelectField/components.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,20 @@ export function Control<A>({
6565

6666
export function ValueContainer<A>(props: ValueContainerProps<A>) {
6767
const sprinkles = useSprinkles();
68+
const dropdownConfig = useBentoConfig().dropdown;
6869
return (
6970
<defaultComponents.ValueContainer
7071
{...props}
7172
className={sprinkles({
72-
gap: 8,
73+
gap: dropdownConfig.chipSpacing,
74+
overflowX: "auto",
75+
flexWrap: "nowrap",
7376
})}
7477
/>
7578
);
7679
}
7780

78-
export function SingleValue<A>({ children, isDisabled, data }: SingleValueProps<A>) {
81+
export function SingleValue<A extends object>({ children, isDisabled, data }: SingleValueProps<A>) {
7982
const inputConfig = useBentoConfig().input;
8083

8184
return (

Diff for: packages/bento-design-system/src/util/defaultConfigs.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,8 @@ export const dropdown: DropdownConfig = {
475475
defaultMenuSize: "medium",
476476
openIndicatorIcon: IconChevronDown,
477477
openIndicatorIconSize: 16,
478+
chipColor: "blue",
479+
chipSpacing: 8,
478480
};
479481

480482
export const table: TableConfig = {

Diff for: packages/storybook/stories/Components/SelectField.stories.tsx

+34
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,40 @@ export const MultiSelectMultipleOptionsSelected = createControlledStory([1, 2],
102102
`${numberOfSelectedOptions} options selected`,
103103
});
104104

105+
const manyColors = [
106+
"red",
107+
"green",
108+
"blue",
109+
"yellow",
110+
"orange",
111+
"purple",
112+
"pink",
113+
"brown",
114+
"black",
115+
"white",
116+
"gray",
117+
"cyan",
118+
"magenta",
119+
"lime",
120+
"maroon",
121+
"navy",
122+
"olive",
123+
"teal",
124+
"aqua",
125+
"fuchsia",
126+
];
127+
128+
export const MultiSelectModeChipsSelected = createControlledStory(manyColors, {
129+
isMulti: true,
130+
multiSelectMode: "chips",
131+
showMultiSelectBulkActions: true,
132+
options: manyColors.map((color) => ({
133+
value: color,
134+
label: color,
135+
kind: "single-line",
136+
})),
137+
});
138+
105139
export const WithIconSelected = createControlledStory(1, {
106140
options: [
107141
{ value: 1, label: "Idea", icon: IconIdea },

0 commit comments

Comments
 (0)