@bitrise/bitkit 13.273.0 → 13.275.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bitrise/bitkit",
3
3
  "description": "Bitrise React component library",
4
- "version": "13.273.0",
4
+ "version": "13.275.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+ssh://git@github.com/bitrise-io/bitkit.git"
@@ -12,7 +12,8 @@ const BreadcrumbTheme: SystemStyleObject = {
12
12
  _hover: {
13
13
  textDecoration: 'none',
14
14
  },
15
- color: 'neutral.10',
15
+ color: 'text/body',
16
+ textStyle: 'body/md/semibold',
16
17
  },
17
18
  _hover: {
18
19
  textDecoration: 'underline',
@@ -29,6 +30,7 @@ const BreadcrumbTheme: SystemStyleObject = {
29
30
  separator: {
30
31
  color: 'neutral.80',
31
32
  display: 'flex',
33
+ marginInline: '4',
32
34
  },
33
35
  },
34
36
  };
@@ -146,7 +146,10 @@ function findOption<T>(
146
146
  return null;
147
147
  }
148
148
 
149
- const DropdownLabelCacheContext = createContext<Map<unknown, ReactNode> | null>(null);
149
+ const DropdownLabelCacheContext = createContext<{
150
+ map: Map<unknown, ReactNode>;
151
+ fallback?: (value: unknown) => ReactNode;
152
+ } | null>(null);
150
153
 
151
154
  const useLabelCache = () => {
152
155
  const contextCache = useContext(DropdownLabelCacheContext);
@@ -157,11 +160,18 @@ const useLabelCache = () => {
157
160
  if (!localCache.current) {
158
161
  localCache.current = new Map();
159
162
  }
160
- return localCache.current;
163
+ return { map: localCache.current };
161
164
  };
162
165
 
163
- export const DropdownLabelCache = ({ children }: { children: ReactNode }) => {
164
- const value = useMemo(() => new Map(), []);
166
+ export const DropdownLabelCache = ({
167
+ children,
168
+ fallback,
169
+ }: {
170
+ children: ReactNode;
171
+ fallback?: (value: unknown) => ReactNode;
172
+ }) => {
173
+ const map = useMemo(() => new Map<unknown, ReactNode>(), []);
174
+ const value = useMemo(() => ({ map, fallback }), [map, fallback]);
165
175
 
166
176
  return <DropdownLabelCacheContext.Provider value={value}>{children}</DropdownLabelCacheContext.Provider>;
167
177
  };
@@ -228,7 +238,7 @@ function useDropdown<T>({
228
238
  onSearch: () => setActiveIndex(null),
229
239
  });
230
240
 
231
- const labelCache = useLabelCache();
241
+ const { map: labelCache, fallback } = useLabelCache();
232
242
 
233
243
  // clear map when value is changed from the outside
234
244
  useEffect(() => {
@@ -330,7 +340,7 @@ function useDropdown<T>({
330
340
  }}
331
341
  size="sm"
332
342
  >
333
- {labelCache.get(formValueItem) || formValueItem}
343
+ {labelCache.get(formValueItem) || fallback?.(formValueItem) || formValueItem}
334
344
  </Tag>
335
345
  );
336
346
  })}
@@ -49,6 +49,7 @@ export const FILTER_STORY_DATA: FilterData = {
49
49
  type: 'tag',
50
50
  },
51
51
  cache_type: {
52
+ allOptionLabel: 'All items',
52
53
  iconsMap: {
53
54
  bazel: 'Bazel',
54
55
  gradle: 'Gradle',
@@ -36,11 +36,19 @@ export type FilterCategoryProps = {
36
36
  dayTooltip?: DatePickerProps['dayTooltip'];
37
37
  isMultiple: true;
38
38
  }
39
+ | {
40
+ type: 'switch';
41
+ allOptionLabel?: string;
42
+ }
39
43
  | {
40
44
  type?: Exclude<FilterType, 'dateRange'>;
41
45
  selectable?: never;
42
46
  dayTooltip?: never;
43
47
  }
48
+ | {
49
+ type?: Exclude<FilterType, 'switch'>;
50
+ allOptionLabel?: never;
51
+ }
44
52
  );
45
53
 
46
54
  export type FilterData = Record<string, FilterCategoryProps>;
@@ -3,11 +3,12 @@ import { useEffect } from 'react';
3
3
  import { useDisclosure, useMultiStyleConfig } from '@chakra-ui/react';
4
4
  import { DateTime } from 'luxon';
5
5
  import Box from '../../Box/Box';
6
- import DatePicker, { DateRange, useDateRange } from '../../DatePicker/DatePicker';
6
+ import DatePicker, { DatePickerProps, DateRange, useDateRange } from '../../DatePicker/DatePicker';
7
7
  import Icon from '../../Icon/Icon';
8
8
  import Text from '../../Text/Text';
9
9
  import { useFilterContext } from '../Filter.context';
10
10
  import { FilterStyle } from '../Filter.theme';
11
+ import { FilterCategoryProps } from '../Filter.types';
11
12
 
12
13
  export type FilterDateProps = {
13
14
  category: string;
@@ -19,6 +20,11 @@ const FilterDate = (props: FilterDateProps) => {
19
20
 
20
21
  const { isLoading, onFilterChange, onFilterClear, setPopoverOpen, state, data } = useFilterContext();
21
22
 
23
+ const dateRangeData = data.date_range as FilterCategoryProps & {
24
+ dayTooltip?: DatePickerProps['dayTooltip'];
25
+ selectable?: DateRange;
26
+ };
27
+
22
28
  const { isOpen, onClose, onToggle } = useDisclosure();
23
29
 
24
30
  const value = state[category];
@@ -54,11 +60,11 @@ const FilterDate = (props: FilterDateProps) => {
54
60
 
55
61
  return (
56
62
  <DatePicker
57
- selectable={data.date_range.selectable || selectable}
63
+ selectable={dateRangeData.selectable || selectable}
58
64
  onApply={onDateRangeApply}
59
65
  onClear={value?.length ? onClearClick : undefined}
60
66
  onClose={onClose}
61
- dayTooltip={data.date_range.dayTooltip}
67
+ dayTooltip={dateRangeData.dayTooltip}
62
68
  selected={selectedRange}
63
69
  visible={isOpen}
64
70
  mode="range"
@@ -1,43 +1,44 @@
1
1
  import { rem } from '../../../utils/utils';
2
2
 
3
3
  const FilterSwitch = {
4
- baseStyle: () => {
4
+ baseStyle: ({ isChecked }: { isChecked: boolean }) => {
5
5
  return {
6
6
  container: {
7
7
  background: 'neutral.95',
8
- border: '1px solid',
9
- borderColor: 'neutral.80',
10
8
  borderRadius: '4',
11
9
  color: 'neutral.40',
12
10
  display: 'flex',
13
11
  },
12
+ icon: {
13
+ color: isChecked ? 'icon/secondary' : 'icon/tertiary',
14
+ },
14
15
  item: {
16
+ borderRadius: '4',
17
+ maxW: '20rem',
18
+ borderColor: isChecked ? 'border/strong' : 'transparent',
19
+ borderWidth: '1px',
20
+ borderStyle: 'solid',
21
+ _hover: {
22
+ background: 'background/hover',
23
+ color: 'text/primary',
24
+
25
+ '> svg': {
26
+ color: 'icon/secondary',
27
+ },
28
+ },
15
29
  _active: {
16
- background: 'neutral.100',
17
- color: 'purple.10',
30
+ background: 'background/active',
18
31
  },
19
32
  _checked: {
20
33
  background: 'neutral.100',
21
34
  color: 'purple.10',
22
35
  cursor: 'default',
23
36
  },
24
- _first: {
25
- borderLeft: 'none',
26
- borderLeftRadius: '4',
27
- },
28
37
  _focusVisible: {
29
38
  boxShadow: 'outline',
30
39
  zIndex: 1,
31
40
  },
32
- _hover: {
33
- background: 'neutral.100',
34
- },
35
- _last: {
36
- borderRightRadius: '4',
37
- },
38
41
  alignItems: 'center',
39
- borderLeft: '1px solid',
40
- borderLeftColor: 'neutral.80',
41
42
  cursor: 'pointer',
42
43
  display: 'flex',
43
44
  fontSize: rem(14),
@@ -1,3 +1,4 @@
1
+ import { useMemo } from 'react';
1
2
  import {
2
3
  chakra,
3
4
  forwardRef,
@@ -7,33 +8,47 @@ import {
7
8
  useRadioGroupContext,
8
9
  } from '@chakra-ui/react';
9
10
  import { omitThemingProps } from '@chakra-ui/styled-system';
11
+ import { createContext } from '@chakra-ui/react-utils';
12
+ import Divider from '../../Divider/Divider';
13
+ import Text from '../../Text/Text';
14
+ import Box from '../../Box/Box';
15
+ import Icon, { TypeIconName } from '../../Icon/Icon';
10
16
 
11
17
  type RadioInputProps = ChakraRadioProps['inputProps'] & {
12
18
  'data-testid'?: string;
13
19
  };
14
20
 
15
21
  export interface FilterSwitchProps extends Omit<ChakraRadioProps, 'inputProps'> {
22
+ iconName?: TypeIconName;
16
23
  inputProps?: RadioInputProps;
17
24
  }
18
25
 
26
+ type FilterSwicthContext = {
27
+ selectedOption: string;
28
+ options: string[];
29
+ };
30
+
31
+ export const [FilterSwitchContextProvider, useFilterSwitchContext] = createContext<FilterSwicthContext>();
32
+
19
33
  const FilterSwitch = forwardRef<FilterSwitchProps, 'input'>((props, ref) => {
20
34
  const group = useRadioGroupContext();
21
35
  const { value: valueProp } = props;
22
36
 
23
- const styles = useMultiStyleConfig('FilterSwitch');
37
+ const isChecked = group.value === valueProp;
38
+
39
+ const styles = useMultiStyleConfig('FilterSwitch', { isChecked });
24
40
 
25
41
  const ownProps = omitThemingProps(props);
26
42
 
27
43
  const {
28
44
  children,
45
+ iconName,
29
46
  inputProps: htmlInputProps,
30
47
  isDisabled = group?.isDisabled,
31
48
  isFocusable = group?.isFocusable,
32
49
  ...rest
33
50
  } = ownProps;
34
51
 
35
- const isChecked = group.value === valueProp;
36
-
37
52
  const { name } = group;
38
53
 
39
54
  const { getRadioProps, getInputProps, getLabelProps } = useRadio({
@@ -45,12 +60,43 @@ const FilterSwitch = forwardRef<FilterSwitchProps, 'input'>((props, ref) => {
45
60
  onChange: group.onChange,
46
61
  });
47
62
 
48
- return (
63
+ const { options, selectedOption } = useFilterSwitchContext();
64
+
65
+ const showDivider = useMemo(() => {
66
+ const optionCount = options.length;
67
+ const index = options.indexOf(valueProp || '');
68
+
69
+ const selectedIndex = options.indexOf(selectedOption);
70
+
71
+ return index < optionCount - 1 && selectedIndex !== index && selectedIndex !== index + 1;
72
+ }, [options, selectedOption, valueProp]);
73
+
74
+ const component = (
49
75
  <chakra.label {...getLabelProps()} {...getRadioProps()} __css={styles.item}>
50
76
  <chakra.input {...getInputProps(htmlInputProps, ref)} />
51
- {children}
77
+ {iconName && <Icon __css={styles.icon} name={iconName} size="16" />}
78
+ <Text as="span" display="block" overflow="hidden" textOverflow="ellipsis">
79
+ {children}
80
+ </Text>
52
81
  </chakra.label>
53
82
  );
83
+
84
+ return (
85
+ <Box height="fit-content" width="fit-content" position="relative">
86
+ {component}
87
+ {showDivider && (
88
+ <Divider
89
+ background="boder/regular"
90
+ height="16"
91
+ orientation="vertical"
92
+ marginTop="8"
93
+ position="absolute"
94
+ right="0"
95
+ top="0"
96
+ />
97
+ )}
98
+ </Box>
99
+ );
54
100
  });
55
101
 
56
102
  export default FilterSwitch;
@@ -1,11 +1,21 @@
1
1
  import { RadioGroup as ChakraRadioGroup, RadioGroupProps, useMultiStyleConfig } from '@chakra-ui/react';
2
+ import { FilterSwitchContextProvider } from './FilterSwitch';
2
3
 
3
- export type { RadioGroupProps };
4
+ export type FilterSwitchGroupProps = { options: string[] } & RadioGroupProps;
4
5
 
5
- const FilterSwitchGroup = (props: RadioGroupProps) => {
6
+ const FilterSwitchGroup = ({ options, value, ...rest }: FilterSwitchGroupProps) => {
6
7
  const { container } = useMultiStyleConfig('FilterSwitch');
7
8
 
8
- return <ChakraRadioGroup sx={container} {...props} />;
9
+ return (
10
+ <FilterSwitchContextProvider
11
+ value={{
12
+ options,
13
+ selectedOption: value || '',
14
+ }}
15
+ >
16
+ <ChakraRadioGroup sx={container} {...rest} value={value} />
17
+ </FilterSwitchContextProvider>
18
+ );
9
19
  };
10
20
 
11
21
  export default FilterSwitchGroup;
@@ -1,8 +1,9 @@
1
+ import { useMemo } from 'react';
1
2
  import { useFilterContext } from '../Filter.context';
2
3
  import { getOptionLabel } from '../Filter.utils';
3
- import Icon from '../../Icon/Icon';
4
4
  import FilterSwitch from '../FilterSwitch/FilterSwitch';
5
5
  import FilterSwitchGroup from '../FilterSwitch/FilterSwitchGroup';
6
+ import { FilterCategoryProps } from '../Filter.types';
6
7
 
7
8
  type FilterSwitchAdapterProps = {
8
9
  category: string;
@@ -11,7 +12,11 @@ type FilterSwitchAdapterProps = {
11
12
  const FilterSwitchAdapter = (props: FilterSwitchAdapterProps) => {
12
13
  const { category } = props;
13
14
  const { data, onFilterChange, state } = useFilterContext();
14
- const { iconsMap, options, optionsMap } = data[category];
15
+ const { allOptionLabel, iconsMap, options, optionsMap } = data[category] as FilterCategoryProps & {
16
+ allOptionLabel?: string;
17
+ };
18
+
19
+ const optionsWithAll = useMemo(() => ['all', ...(options || [])], [options]);
15
20
 
16
21
  if (!options?.length) {
17
22
  return null;
@@ -20,10 +25,16 @@ const FilterSwitchAdapter = (props: FilterSwitchAdapterProps) => {
20
25
  const value = state[category]?.[0] || '';
21
26
 
22
27
  return (
23
- <FilterSwitchGroup onChange={(newValue) => onFilterChange(category, [newValue])} value={value}>
28
+ <FilterSwitchGroup
29
+ onChange={(newValue) => onFilterChange(category, [newValue])}
30
+ options={optionsWithAll}
31
+ value={value}
32
+ >
33
+ <FilterSwitch key="all" value="all">
34
+ {allOptionLabel || 'All items'}
35
+ </FilterSwitch>
24
36
  {options.map((opt) => (
25
- <FilterSwitch key={opt} value={opt}>
26
- {iconsMap && iconsMap[opt] && <Icon color="icon/tertiary" name={iconsMap[opt]} size="16" />}
37
+ <FilterSwitch key={opt} value={opt} iconName={iconsMap && iconsMap[opt]}>
27
38
  {getOptionLabel(opt, optionsMap)}
28
39
  </FilterSwitch>
29
40
  ))}
@@ -60,13 +60,13 @@ const Pagination = ({
60
60
  ))}
61
61
  </Dropdown>
62
62
  </Box>
63
- <Divider color="pal.neutral.90" height="3rem" orientation="vertical" />
63
+ <Divider color="pal.neutral.90" height="2.5rem" orientation="vertical" />
64
64
  </>
65
65
  )}
66
66
  <Text as="span" color={textColor} marginRight="auto" textStyle="body/md/regular">
67
67
  {itemsStartIndex}-{itemsEndIndex} of {totalCount} items
68
68
  </Text>
69
- <Divider color="pal.neutral.90" height="3rem" orientation="vertical" />
69
+ <Divider color="pal.neutral.90" height="2.5rem" orientation="vertical" />
70
70
  <Box alignItems="center" display="flex" gap="0.5rem">
71
71
  <Dropdown
72
72
  onChange={({ target }) => setPage(Number(target.value))}
@@ -66,9 +66,9 @@ const schemeMap: Record<TagColors, MappedColor> = {
66
66
  };
67
67
 
68
68
  const baseStyle = definePartsStyle((props) => {
69
- const { colorScheme = 'neutral' } = props;
69
+ const { colorScheme = 'neutral', isDisabled, isLoading } = props;
70
70
  const { color, isFilled } = schemeMap[colorScheme as TagColors];
71
- const normalTextColor = color === 'neutral' ? 'text.primary' : `sys.${color}.strong`;
71
+ const normalTextColor = color === 'neutral' ? 'text.body' : `sys.${color}.strong`;
72
72
  const scheme = {
73
73
  backgroundColor: `sys.${color}.${isFilled ? 'bold' : 'subtle'}`,
74
74
  borderColor: `sys.${color}.${isFilled ? 'bold' : 'muted'}`,
@@ -108,6 +108,7 @@ const baseStyle = definePartsStyle((props) => {
108
108
  },
109
109
  },
110
110
  icon: {
111
+ color: colorScheme === 'neutral' && !isDisabled && !isLoading ? 'icon.secondary' : undefined,
111
112
  marginInlineEnd: '4',
112
113
  },
113
114
  label: {