@bitrise/bitkit 13.224.0 → 13.226.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/package.json +1 -1
- package/src/Components/Dropdown/Dropdown.context.tsx +1 -0
- package/src/Components/Dropdown/Dropdown.theme.ts +37 -24
- package/src/Components/Dropdown/Dropdown.tsx +115 -9
- package/src/Components/Dropdown/DropdownOption.tsx +15 -3
- package/src/Components/IconButton/IconButton.tsx +2 -2
- package/src/Components/Tag/Tag.tsx +2 -2
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ export type DropdownEventArgs<T> = { value: T; index: number | undefined; label:
|
|
|
9
9
|
|
|
10
10
|
type DropdownContext<T> = {
|
|
11
11
|
formValue: T;
|
|
12
|
+
isMultiSelect: boolean;
|
|
12
13
|
onOptionSelected: (arg: DropdownEventArgs<T>) => void;
|
|
13
14
|
listRef: React.RefObject<(HTMLElement | null)[]>;
|
|
14
15
|
searchValue: string;
|
|
@@ -4,6 +4,7 @@ import { DropdownProps } from './DropdownProps';
|
|
|
4
4
|
type DropdownStateProps = Pick<DropdownProps<string | null>, 'disabled' | 'readOnly' | 'isWarning' | 'isError'> & {
|
|
5
5
|
placeholder: boolean;
|
|
6
6
|
isOpen: boolean;
|
|
7
|
+
isMultiSelect: boolean;
|
|
7
8
|
hasAvatar: boolean;
|
|
8
9
|
};
|
|
9
10
|
|
|
@@ -69,7 +70,7 @@ const getButtonContentColor = ({ disabled, placeholder }: DropdownStateProps) =>
|
|
|
69
70
|
|
|
70
71
|
const DropdownTheme = {
|
|
71
72
|
baseStyle: (props: DropdownStateProps) => {
|
|
72
|
-
const { disabled, readOnly, hasAvatar, isError } = props;
|
|
73
|
+
const { disabled, isMultiSelect, readOnly, hasAvatar, isError } = props;
|
|
73
74
|
return {
|
|
74
75
|
field: {
|
|
75
76
|
_hover:
|
|
@@ -131,7 +132,7 @@ const DropdownTheme = {
|
|
|
131
132
|
cursor: 'pointer',
|
|
132
133
|
paddingLeft: hasAvatar ? '12' : '16',
|
|
133
134
|
paddingRight: '24',
|
|
134
|
-
paddingY: '8',
|
|
135
|
+
paddingY: isMultiSelect ? '12' : '8',
|
|
135
136
|
textAlign: 'start',
|
|
136
137
|
userSelect: 'none',
|
|
137
138
|
w: '100%',
|
|
@@ -164,28 +165,40 @@ const DropdownTheme = {
|
|
|
164
165
|
};
|
|
165
166
|
},
|
|
166
167
|
sizes: {
|
|
167
|
-
lg: ({ hasAvatar }: DropdownStateProps) =>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
168
|
+
lg: ({ hasAvatar, isMultiSelect }: DropdownStateProps) => {
|
|
169
|
+
return {
|
|
170
|
+
field: {
|
|
171
|
+
fontSize: '3',
|
|
172
|
+
gap: rem('8'),
|
|
173
|
+
height: isMultiSelect ? 'auto' : '48',
|
|
174
|
+
minH: isMultiSelect ? '48' : undefined,
|
|
175
|
+
paddingLeft: hasAvatar ? rem('12') : rem('16'),
|
|
176
|
+
paddingRight: rem('16'),
|
|
177
|
+
paddingTop: isMultiSelect ? rem('7') : '0',
|
|
178
|
+
paddingBottom: isMultiSelect ? rem('7') : '0',
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
md: ({ isMultiSelect }: DropdownStateProps) => {
|
|
183
|
+
return {
|
|
184
|
+
field: {
|
|
185
|
+
fontSize: '2',
|
|
186
|
+
gap: rem('8'),
|
|
187
|
+
height: isMultiSelect ? 'auto' : '40',
|
|
188
|
+
minH: isMultiSelect ? '40' : undefined,
|
|
189
|
+
paddingLeft: rem('12'),
|
|
190
|
+
paddingRight: rem('12'),
|
|
191
|
+
paddingTop: isMultiSelect ? rem('8') : '0',
|
|
192
|
+
paddingBottom: isMultiSelect ? rem('8') : '0',
|
|
193
|
+
},
|
|
194
|
+
item: {
|
|
195
|
+
fontSize: '2',
|
|
196
|
+
paddingLeft: rem('16'),
|
|
197
|
+
paddingRight: rem('16'),
|
|
198
|
+
paddingTop: isMultiSelect ? rem('8') : undefined,
|
|
199
|
+
paddingBottom: isMultiSelect ? rem('8') : undefined,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
189
202
|
},
|
|
190
203
|
},
|
|
191
204
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, {
|
|
2
2
|
cloneElement,
|
|
3
|
+
createContext,
|
|
3
4
|
forwardRef,
|
|
4
5
|
ReactNode,
|
|
5
6
|
useCallback,
|
|
7
|
+
useContext,
|
|
6
8
|
useEffect,
|
|
7
9
|
useId,
|
|
8
10
|
useMemo,
|
|
@@ -24,6 +26,9 @@ import SearchInput from '../SearchInput/SearchInput';
|
|
|
24
26
|
import FormLabel from '../Form/FormLabel';
|
|
25
27
|
import { AvatarProps } from '../Avatar/Avatar';
|
|
26
28
|
import { getDataAttributes } from '../../utils/utils';
|
|
29
|
+
import Tag from '../Tag/Tag';
|
|
30
|
+
import Box from '../Box/Box';
|
|
31
|
+
import IconButton from '../IconButton/IconButton';
|
|
27
32
|
import { DropdownEventArgs, DropdownProvider, useDropdownContext, useDropdownStyles } from './Dropdown.context';
|
|
28
33
|
import { DropdownDetailedOption, DropdownGroup, DropdownOption, DropdownOptionProps } from './DropdownOption';
|
|
29
34
|
import DropdownButton from './DropdownButton';
|
|
@@ -32,6 +37,8 @@ import { NoResultsFound, useSimpleSearch } from './hooks/useSimpleSearch';
|
|
|
32
37
|
import { isSearchable } from './isNodeMatch';
|
|
33
38
|
import { DropdownProps } from './DropdownProps';
|
|
34
39
|
|
|
40
|
+
const MultiSelectContext = createContext(false);
|
|
41
|
+
|
|
35
42
|
type DropdownSearchCustomProps = {
|
|
36
43
|
value: string;
|
|
37
44
|
onChange: (newValue: string) => void;
|
|
@@ -141,6 +148,7 @@ function findOption<T>(
|
|
|
141
148
|
function useDropdown<T>({
|
|
142
149
|
children,
|
|
143
150
|
defaultValue,
|
|
151
|
+
disabled,
|
|
144
152
|
dropdownWidth = 'match',
|
|
145
153
|
name,
|
|
146
154
|
onChange,
|
|
@@ -165,8 +173,11 @@ function useDropdown<T>({
|
|
|
165
173
|
setActiveIndex,
|
|
166
174
|
setSelectedIndex,
|
|
167
175
|
} = useFloatingDropdown({ dropdownWidth, enabled: !readOnly, optionsRef, placement });
|
|
176
|
+
|
|
177
|
+
const isMultiSelect = useContext(MultiSelectContext);
|
|
178
|
+
|
|
168
179
|
const [formValue, setFormValue] = useControllableState<T>({
|
|
169
|
-
defaultValue,
|
|
180
|
+
defaultValue: isMultiSelect ? defaultValue || ([] as T) : defaultValue,
|
|
170
181
|
onChange: (newValue) => onChange?.({ target: { name, value: newValue } }),
|
|
171
182
|
value,
|
|
172
183
|
});
|
|
@@ -195,7 +206,17 @@ function useDropdown<T>({
|
|
|
195
206
|
|
|
196
207
|
const onOptionSelected = useCallback(
|
|
197
208
|
(args: DropdownEventArgs<T>) => {
|
|
198
|
-
setFormValue(
|
|
209
|
+
setFormValue((previous) => {
|
|
210
|
+
if (Array.isArray(previous)) {
|
|
211
|
+
if (previous.includes(args.value)) {
|
|
212
|
+
return previous.filter((aPrevious) => aPrevious !== args.value) as T;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return [...previous, args.value] as T;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return args.value;
|
|
219
|
+
});
|
|
199
220
|
close();
|
|
200
221
|
},
|
|
201
222
|
[close, setFormValue],
|
|
@@ -205,6 +226,7 @@ function useDropdown<T>({
|
|
|
205
226
|
activeIndex,
|
|
206
227
|
formValue,
|
|
207
228
|
getItemProps,
|
|
229
|
+
isMultiSelect,
|
|
208
230
|
listRef,
|
|
209
231
|
onOptionSelected,
|
|
210
232
|
searchOnChange,
|
|
@@ -228,15 +250,84 @@ function useDropdown<T>({
|
|
|
228
250
|
);
|
|
229
251
|
|
|
230
252
|
useEffect(() => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
253
|
+
if (Array.isArray(formValue)) {
|
|
254
|
+
setFormLabel(
|
|
255
|
+
formValue.length > 0 ? (
|
|
256
|
+
<Box alignItems="center" display="flex" gap={8}>
|
|
257
|
+
<Box display="flex" flexGrow={1} flexWrap="wrap" gap={8}>
|
|
258
|
+
{formValue
|
|
259
|
+
?.sort((a, b) => {
|
|
260
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
261
|
+
return a.localeCompare(b);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (a === null) {
|
|
265
|
+
return -1;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return 1;
|
|
269
|
+
})
|
|
270
|
+
.map((formValueItem) => {
|
|
271
|
+
if (typeof formValueItem !== 'string' && formValueItem !== null) {
|
|
272
|
+
return <>{formValueItem}</>;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const currentOption = findOption(refdChildren, formValueItem);
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<Tag
|
|
279
|
+
colorScheme="neutral"
|
|
280
|
+
isDisabled={disabled}
|
|
281
|
+
key={formValueItem}
|
|
282
|
+
onClose={(event) => {
|
|
283
|
+
event.stopPropagation();
|
|
284
|
+
|
|
285
|
+
setFormValue((previous: T) => {
|
|
286
|
+
if (!Array.isArray(previous)) {
|
|
287
|
+
return previous;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return previous.filter((aPrevious) => aPrevious !== formValueItem) as T;
|
|
291
|
+
});
|
|
292
|
+
}}
|
|
293
|
+
size="sm"
|
|
294
|
+
>
|
|
295
|
+
{currentOption?.label || formValueItem}
|
|
296
|
+
</Tag>
|
|
297
|
+
);
|
|
298
|
+
})}
|
|
299
|
+
</Box>
|
|
300
|
+
<IconButton
|
|
301
|
+
aria-label="Clear all"
|
|
302
|
+
as="span"
|
|
303
|
+
color="icon/secondary"
|
|
304
|
+
iconName="Cross"
|
|
305
|
+
iconSize="24"
|
|
306
|
+
isDisabled={disabled}
|
|
307
|
+
onClick={(event) => {
|
|
308
|
+
event.stopPropagation();
|
|
309
|
+
|
|
310
|
+
setFormValue([] as T);
|
|
311
|
+
}}
|
|
312
|
+
size="sm"
|
|
313
|
+
variant="tertiary"
|
|
314
|
+
/>
|
|
315
|
+
</Box>
|
|
316
|
+
) : null,
|
|
317
|
+
);
|
|
238
318
|
setSelectedIndex(null);
|
|
239
319
|
setSelectedAvatar(undefined);
|
|
320
|
+
} else {
|
|
321
|
+
const currentOption = findOption(refdChildren, formValue);
|
|
322
|
+
if (currentOption) {
|
|
323
|
+
setFormLabel(currentOption.label);
|
|
324
|
+
setSelectedIndex(currentOption.index);
|
|
325
|
+
setSelectedAvatar(currentOption.avatar);
|
|
326
|
+
} else {
|
|
327
|
+
setFormLabel(null);
|
|
328
|
+
setSelectedIndex(null);
|
|
329
|
+
setSelectedAvatar(undefined);
|
|
330
|
+
}
|
|
240
331
|
}
|
|
241
332
|
}, [refdChildren, formValue]);
|
|
242
333
|
return {
|
|
@@ -311,6 +402,7 @@ const Dropdown = forwardRef<Element, DropdownProps<string | null>>(
|
|
|
311
402
|
...rest
|
|
312
403
|
} = useDropdown({
|
|
313
404
|
defaultValue,
|
|
405
|
+
disabled,
|
|
314
406
|
dropdownWidth,
|
|
315
407
|
isWarning,
|
|
316
408
|
name,
|
|
@@ -323,10 +415,12 @@ const Dropdown = forwardRef<Element, DropdownProps<string | null>>(
|
|
|
323
415
|
value,
|
|
324
416
|
...props,
|
|
325
417
|
});
|
|
418
|
+
|
|
326
419
|
const dropdownStyles = useMultiStyleConfig('Dropdown', {
|
|
327
420
|
hasAvatar: Boolean(avatar),
|
|
328
421
|
disabled,
|
|
329
422
|
isError,
|
|
423
|
+
isMultiSelect: useContext(MultiSelectContext),
|
|
330
424
|
isOpen,
|
|
331
425
|
isWarning,
|
|
332
426
|
placeholder: !formLabel,
|
|
@@ -443,4 +537,16 @@ export function typedDropdown<T>() {
|
|
|
443
537
|
};
|
|
444
538
|
}
|
|
445
539
|
|
|
540
|
+
export const MultiSelectDropdown = (props: DropdownProps<(string | null)[]>) => {
|
|
541
|
+
const { Dropdown: TypedDropdown } = typedDropdown<(string | null)[]>();
|
|
542
|
+
|
|
543
|
+
const { children, ...rest } = props;
|
|
544
|
+
|
|
545
|
+
return (
|
|
546
|
+
<MultiSelectContext.Provider value>
|
|
547
|
+
<TypedDropdown {...rest}>{children}</TypedDropdown>
|
|
548
|
+
</MultiSelectContext.Provider>
|
|
549
|
+
);
|
|
550
|
+
};
|
|
551
|
+
|
|
446
552
|
export default Dropdown;
|
|
@@ -5,6 +5,7 @@ import Text, { TextProps } from '../Text/Text';
|
|
|
5
5
|
import Divider from '../Divider/Divider';
|
|
6
6
|
import Box from '../Box/Box';
|
|
7
7
|
import Icon from '../Icon/Icon';
|
|
8
|
+
import Checkbox from '../Form/Checkbox/Checkbox';
|
|
8
9
|
import { useDropdownContext, useDropdownStyles } from './Dropdown.context';
|
|
9
10
|
|
|
10
11
|
export type DropdownOptionProps<T> = {
|
|
@@ -27,7 +28,9 @@ const DropdownOption = <T = string,>({
|
|
|
27
28
|
const { item } = useDropdownStyles();
|
|
28
29
|
const ctx = useDropdownContext<T | null>();
|
|
29
30
|
const { index } = rest as { index?: number };
|
|
30
|
-
const isSelected =
|
|
31
|
+
const isSelected = !!(
|
|
32
|
+
ctx.formValue && (Array.isArray(ctx.formValue) ? ctx.formValue.includes(value) : ctx.formValue === value)
|
|
33
|
+
);
|
|
31
34
|
|
|
32
35
|
return (
|
|
33
36
|
<chakra.div
|
|
@@ -61,8 +64,17 @@ const DropdownOption = <T = string,>({
|
|
|
61
64
|
>
|
|
62
65
|
<Box display="flex" alignItems="center" gap="8">
|
|
63
66
|
{avatar && <Avatar size={ctx.size === 'lg' ? '32' : '24'} {...avatar} />}
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
{ctx.isMultiSelect ? (
|
|
68
|
+
<>
|
|
69
|
+
<Checkbox isChecked={isSelected} />
|
|
70
|
+
<Box flex="1">{children}</Box>
|
|
71
|
+
</>
|
|
72
|
+
) : (
|
|
73
|
+
<>
|
|
74
|
+
<Box flex="1">{children}</Box>
|
|
75
|
+
{isSelected && <Icon name="Check" color="icon/interactive" />}
|
|
76
|
+
</>
|
|
77
|
+
)}
|
|
66
78
|
</Box>
|
|
67
79
|
</chakra.div>
|
|
68
80
|
);
|
|
@@ -3,7 +3,7 @@ import Icon, { IconProps, TypeIconName } from '../Icon/Icon';
|
|
|
3
3
|
import Tooltip, { TooltipProps } from '../Tooltip/Tooltip';
|
|
4
4
|
|
|
5
5
|
export interface IconButtonProps extends ChakraIconButtonProps {
|
|
6
|
-
as?: 'a' | 'button';
|
|
6
|
+
as?: 'a' | 'button' | 'span';
|
|
7
7
|
disabled?: never;
|
|
8
8
|
iconName: TypeIconName;
|
|
9
9
|
isDanger?: boolean;
|
|
@@ -35,7 +35,7 @@ const IconButton = forwardRef<IconButtonProps, 'button'>((props, ref) => {
|
|
|
35
35
|
...rest
|
|
36
36
|
} = props;
|
|
37
37
|
const properties: ChakraIconButtonProps = {
|
|
38
|
-
as: isDisabled ? 'button' : as,
|
|
38
|
+
as: as === 'a' && isDisabled ? 'button' : as,
|
|
39
39
|
icon: <Icon name={iconName} size={iconSize || (size === 'lg' ? '24' : '16')} />,
|
|
40
40
|
isDisabled,
|
|
41
41
|
size,
|
|
@@ -19,7 +19,7 @@ export interface TagProps extends Omit<ChakraTagProps, 'colorScheme' | 'size' |
|
|
|
19
19
|
iconName?: TypeIconName;
|
|
20
20
|
isDisabled?: boolean;
|
|
21
21
|
isLoading?: boolean;
|
|
22
|
-
onClose?: () => void;
|
|
22
|
+
onClose?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
|
23
23
|
size?: 'sm' | 'md';
|
|
24
24
|
withIcon?: boolean;
|
|
25
25
|
}
|
|
@@ -66,7 +66,7 @@ const Tag = forwardRef<TagProps, 'span'>((props, ref) => {
|
|
|
66
66
|
</Text>
|
|
67
67
|
{!!onClose && (
|
|
68
68
|
<Tooltip isDisabled={!closeButtonTooltip} label={closeButtonTooltip}>
|
|
69
|
-
<TagCloseButton isDisabled={isDisabled || isLoading} onClick={onClose}>
|
|
69
|
+
<TagCloseButton as="span" isDisabled={isDisabled || isLoading} onClick={onClose}>
|
|
70
70
|
<Icon name="Cross" size="16" />
|
|
71
71
|
</TagCloseButton>
|
|
72
72
|
</Tooltip>
|