@bitrise/bitkit 13.324.0 → 13.326.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 +14 -14
- package/src/Components/Badge/Badge.theme.ts +48 -1
- package/src/Components/Badge/Badge.tsx +15 -4
- package/src/Components/Filter/Filter.storyData.ts +1 -1
- package/src/Components/Filter/Mobile/Filter.tsx +30 -5
- package/src/Components/Filter/Mobile/FilterForm.tsx +7 -8
- package/src/Components/Filter/Mobile/FilterItem.tsx +7 -2
- package/src/Components/Filter/Mobile/FilterSearch.tsx +36 -0
- package/src/Components/Icons/16x16/BuildEnvSetup.tsx +24 -0
- package/src/Components/Icons/16x16/FolderEmpty.tsx +14 -0
- package/src/Components/Icons/16x16/index.ts +2 -0
- package/src/Components/Icons/24x24/BuildEnvSetup.tsx +24 -0
- package/src/Components/Icons/24x24/FolderEmpty.tsx +14 -0
- package/src/Components/Icons/24x24/index.ts +2 -0
- package/src/Components/TreeView/TreeView.context.tsx +13 -4
- package/src/Components/TreeView/TreeView.theme.ts +70 -1
- package/src/Components/TreeView/TreeView.tsx +9 -54
- package/src/Components/TreeView/TreeView.types.ts +72 -5
- package/src/Components/TreeView/TreeViewNode.tsx +139 -25
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.
|
|
4
|
+
"version": "13.326.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+ssh://git@github.com/bitrise-io/bitkit.git"
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"chakra-ui-2--theme": "npm:@chakra-ui/theme@3.4.9",
|
|
39
39
|
"chakra-ui-2--theme-tools": "npm:@chakra-ui/theme-tools@2.2.9",
|
|
40
40
|
"clsx": "^2.1.1",
|
|
41
|
-
"framer-motion": "^12.
|
|
41
|
+
"framer-motion": "^12.25.0",
|
|
42
42
|
"luxon": "^3.7.2",
|
|
43
|
-
"react": "^18.
|
|
44
|
-
"react-dom": "^18.
|
|
43
|
+
"react": "^18.2.0",
|
|
44
|
+
"react-dom": "^18.2.0",
|
|
45
45
|
"react-focus-lock": "2.13.7",
|
|
46
46
|
"react-imask": "^7.6.1",
|
|
47
47
|
"react-markdown": "^10.1.0"
|
|
@@ -56,14 +56,14 @@
|
|
|
56
56
|
"@babel/preset-react": "^7.28.5",
|
|
57
57
|
"@babel/preset-typescript": "^7.28.5",
|
|
58
58
|
"@bitrise/eslint-plugin": "^2.12.0",
|
|
59
|
-
"@google-cloud/storage": "^7.
|
|
60
|
-
"@storybook/addon-docs": "^9.1.
|
|
61
|
-
"@storybook/addon-links": "^9.1.
|
|
59
|
+
"@google-cloud/storage": "^7.18.0",
|
|
60
|
+
"@storybook/addon-docs": "^9.1.17",
|
|
61
|
+
"@storybook/addon-links": "^9.1.17",
|
|
62
62
|
"@storybook/addon-webpack5-compiler-swc": "^3.0.0",
|
|
63
|
-
"@storybook/react-webpack5": "^9.1.
|
|
63
|
+
"@storybook/react-webpack5": "^9.1.17",
|
|
64
64
|
"@testing-library/dom": "^10.4.1",
|
|
65
65
|
"@testing-library/jest-dom": "6.9.1",
|
|
66
|
-
"@testing-library/react": "16.3.
|
|
66
|
+
"@testing-library/react": "16.3.1",
|
|
67
67
|
"@testing-library/user-event": "^14.6.1",
|
|
68
68
|
"@types/jest": "^29.5.14",
|
|
69
69
|
"@types/luxon": "^3.7.1",
|
|
@@ -79,11 +79,11 @@
|
|
|
79
79
|
"jest-environment-jsdom": "^29.7.0",
|
|
80
80
|
"jsdom": "26.1.0",
|
|
81
81
|
"lodash": "^4.17.21",
|
|
82
|
-
"prettier": "^3.7.
|
|
83
|
-
"react-hook-form": "^7.
|
|
84
|
-
"release-it": "^19.
|
|
85
|
-
"storybook": "^9.1.
|
|
86
|
-
"ts-jest": "^29.4.
|
|
82
|
+
"prettier": "^3.7.4",
|
|
83
|
+
"react-hook-form": "^7.70.0",
|
|
84
|
+
"release-it": "^19.2.3",
|
|
85
|
+
"storybook": "^9.1.17",
|
|
86
|
+
"ts-jest": "^29.4.6",
|
|
87
87
|
"typescript": "^5.9.3"
|
|
88
88
|
},
|
|
89
89
|
"files": [
|
|
@@ -4,8 +4,8 @@ import { BadgeColorScheme, BadgeProps } from './Badge';
|
|
|
4
4
|
const baseStyle: SystemStyleObject = {
|
|
5
5
|
borderRadius: '4',
|
|
6
6
|
display: 'inline-flex',
|
|
7
|
+
alignItems: 'center',
|
|
7
8
|
gap: '4',
|
|
8
|
-
padding: '4',
|
|
9
9
|
textStyle: 'comp/badge/sm',
|
|
10
10
|
};
|
|
11
11
|
|
|
@@ -87,15 +87,62 @@ const getVariant = (props: BadgeProps) => {
|
|
|
87
87
|
return colors[variant || 'subtle'][colorScheme || 'neutral'];
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
const sizes: Record<'xs' | 'xxs', SystemStyleObject> = {
|
|
91
|
+
xs: {
|
|
92
|
+
height: 'fit-content',
|
|
93
|
+
width: 'fit-content',
|
|
94
|
+
padding: '4px',
|
|
95
|
+
display: 'flex',
|
|
96
|
+
justifyContent: 'center',
|
|
97
|
+
alignItems: 'center',
|
|
98
|
+
'&.has-children': {
|
|
99
|
+
paddingInlineStart: '8px',
|
|
100
|
+
paddingInlineEnd: '8px',
|
|
101
|
+
},
|
|
102
|
+
'&.has-icon': {
|
|
103
|
+
paddingInlineStart: '4px',
|
|
104
|
+
},
|
|
105
|
+
'&.has-icon:not(.has-children)': {
|
|
106
|
+
paddingInlineEnd: '4px',
|
|
107
|
+
},
|
|
108
|
+
'&.single-char': {
|
|
109
|
+
width: '24px',
|
|
110
|
+
height: '24px',
|
|
111
|
+
padding: '4px 0',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
xxs: {
|
|
115
|
+
height: 'fit-content',
|
|
116
|
+
width: 'fit-content',
|
|
117
|
+
display: 'flex',
|
|
118
|
+
justifyContent: 'center',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
padding: '2px 6px 2px 4px',
|
|
121
|
+
'&.single-char:not(.has-icon)': {
|
|
122
|
+
width: '20px',
|
|
123
|
+
height: '20px',
|
|
124
|
+
padding: '2px 0',
|
|
125
|
+
},
|
|
126
|
+
'&.has-icon:not(.has-children)': {
|
|
127
|
+
padding: '2px',
|
|
128
|
+
},
|
|
129
|
+
'&.has-icon.has-children': {
|
|
130
|
+
padding: '2px 6px 2px 4px',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
90
135
|
const BadgeTheme = {
|
|
91
136
|
baseStyle,
|
|
92
137
|
variants: {
|
|
93
138
|
bold: getVariant,
|
|
94
139
|
subtle: getVariant,
|
|
95
140
|
},
|
|
141
|
+
sizes,
|
|
96
142
|
defaultProps: {
|
|
97
143
|
variant: 'subtle',
|
|
98
144
|
colorScheme: 'neutral',
|
|
145
|
+
size: 'xs',
|
|
99
146
|
},
|
|
100
147
|
};
|
|
101
148
|
|
|
@@ -11,22 +11,33 @@ export type BadgeColorScheme =
|
|
|
11
11
|
| 'progress'
|
|
12
12
|
| 'orange'
|
|
13
13
|
| 'turquoise';
|
|
14
|
+
|
|
14
15
|
export interface BadgeProps extends ChakraBadgeProps {
|
|
15
16
|
children?: string | number;
|
|
16
17
|
colorScheme?: BadgeColorScheme;
|
|
17
18
|
iconName?: TypeIconName;
|
|
18
19
|
variant?: 'bold' | 'subtle';
|
|
20
|
+
size?: 'xs' | 'xxs';
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Badges are used to highlight an item's status for quick recognition.
|
|
23
25
|
*/
|
|
24
26
|
const Badge = forwardRef<BadgeProps, 'span'>((props, ref) => {
|
|
25
|
-
const { children, iconName, ...rest } = props;
|
|
27
|
+
const { children, iconName, size = 'xs', className, ...rest } = props;
|
|
28
|
+
|
|
29
|
+
const hasIcon = !!iconName;
|
|
30
|
+
const hasChildren = !!children;
|
|
31
|
+
const isSingleChar = hasChildren && String(children).length === 1;
|
|
32
|
+
|
|
33
|
+
const classNames = [className, hasIcon && 'has-icon', hasChildren && 'has-children', isSingleChar && 'single-char']
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join(' ');
|
|
36
|
+
|
|
26
37
|
return (
|
|
27
|
-
<ChakraBadge {...rest}
|
|
28
|
-
{
|
|
29
|
-
{
|
|
38
|
+
<ChakraBadge {...rest} size={size} ref={ref} className={classNames || undefined}>
|
|
39
|
+
{hasIcon && <Icon size="16" name={iconName} />}
|
|
40
|
+
{hasChildren && (
|
|
30
41
|
<Text hasEllipsis as="span">
|
|
31
42
|
{children}
|
|
32
43
|
</Text>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Divider from '../../Divider/Divider';
|
|
1
2
|
import Badge from '../../Badge/Badge';
|
|
2
3
|
import Text from '../../Text/Text';
|
|
3
4
|
import Button from '../../Button/Button';
|
|
@@ -6,12 +7,29 @@ import { useFilterContext } from '../Filter.context';
|
|
|
6
7
|
import { FilterProps } from '../Filter.types';
|
|
7
8
|
import FilterDrawer from './FilterDrawer';
|
|
8
9
|
import FilterForm from './FilterForm';
|
|
10
|
+
import FilterSearch from './FilterSearch';
|
|
11
|
+
|
|
12
|
+
const Filter = (props: FilterProps) => {
|
|
13
|
+
const {
|
|
14
|
+
data: initialData,
|
|
15
|
+
defaultState,
|
|
16
|
+
filtersDependOn,
|
|
17
|
+
isLoading,
|
|
18
|
+
isMobile,
|
|
19
|
+
onChange,
|
|
20
|
+
searchTooltip,
|
|
21
|
+
showAdd = true,
|
|
22
|
+
showFilterIcon = true,
|
|
23
|
+
showClearFilters = true,
|
|
24
|
+
showSearch,
|
|
25
|
+
...rest
|
|
26
|
+
} = props;
|
|
9
27
|
|
|
10
|
-
const Filter = ({ isLoading, showAdd = true }: FilterProps) => {
|
|
11
28
|
const {
|
|
12
29
|
data,
|
|
13
30
|
isPopoverOpen,
|
|
14
31
|
onClearFilters,
|
|
32
|
+
onFilterChange,
|
|
15
33
|
selectedCategory,
|
|
16
34
|
setSelectedCategory,
|
|
17
35
|
setPopoverOpen,
|
|
@@ -20,14 +38,14 @@ const Filter = ({ isLoading, showAdd = true }: FilterProps) => {
|
|
|
20
38
|
} = useFilterContext();
|
|
21
39
|
|
|
22
40
|
const isAllOptionForSwitch = (category: string) => {
|
|
23
|
-
return data[category]
|
|
41
|
+
return data[category]?.type === 'switch' && state[category]?.[0] === 'all';
|
|
24
42
|
};
|
|
25
43
|
|
|
26
44
|
const count = Object.entries(state).filter(([name, item]) => item?.length > 0 && !isAllOptionForSwitch(name)).length;
|
|
27
45
|
|
|
28
46
|
return (
|
|
29
|
-
|
|
30
|
-
<Box alignItems="center" display="flex" justifyContent="space-between">
|
|
47
|
+
<Box display="flex" flexDirection="column" {...rest}>
|
|
48
|
+
<Box alignItems="center" display="flex" justifyContent="space-between" padding="12">
|
|
31
49
|
<Button leftIconName="Filter" onClick={() => setPopoverOpen(true)} size="sm" variant="secondary">
|
|
32
50
|
<Text alignItems="center" display="flex" gap="8">
|
|
33
51
|
Filters
|
|
@@ -49,13 +67,20 @@ const Filter = ({ isLoading, showAdd = true }: FilterProps) => {
|
|
|
49
67
|
</Button>
|
|
50
68
|
)}
|
|
51
69
|
</Box>
|
|
70
|
+
{showSearch && (
|
|
71
|
+
<>
|
|
72
|
+
<Divider color="border/minimal" />
|
|
73
|
+
<FilterSearch onChange={onFilterChange} value={state.search?.length ? state.search[0] : ''} />
|
|
74
|
+
<Divider color="border/minimal" />
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
52
77
|
<FilterDrawer isOpen={isPopoverOpen} onClose={() => setPopoverOpen(false)} showAdd={showAdd} />
|
|
53
78
|
<FilterForm
|
|
54
79
|
isOpen={Boolean(selectedCategory)}
|
|
55
80
|
onClose={() => setSelectedCategory(undefined)}
|
|
56
81
|
selectedCategory={selectedCategory || ''}
|
|
57
82
|
/>
|
|
58
|
-
|
|
83
|
+
</Box>
|
|
59
84
|
);
|
|
60
85
|
};
|
|
61
86
|
|
|
@@ -97,8 +97,8 @@ const FilterForm = ({ isOpen, onClose, selectedCategory }: FilterFormProps) => {
|
|
|
97
97
|
bodyPadding="0"
|
|
98
98
|
bodyProps={{ overflowY: 'auto' }}
|
|
99
99
|
bodyRef={bodyRef}
|
|
100
|
-
contentProps={{ zIndex: 'dialog', top: '0' }}
|
|
101
|
-
headerPadding=
|
|
100
|
+
contentProps={{ gap: '0', zIndex: 'dialog', top: '0' }}
|
|
101
|
+
headerPadding="12"
|
|
102
102
|
hideCloseButton
|
|
103
103
|
isOpen={isOpen}
|
|
104
104
|
maxWidth="100%"
|
|
@@ -118,7 +118,7 @@ const FilterForm = ({ isOpen, onClose, selectedCategory }: FilterFormProps) => {
|
|
|
118
118
|
<Text as="h6" color="text/tertiary" textStyle="heading/h6">
|
|
119
119
|
Filter
|
|
120
120
|
</Text>
|
|
121
|
-
<Text as="h3" color="text/primary" textStyle="heading/mobile/h3">
|
|
121
|
+
<Text as="h3" color="text/primary" textStyle="heading/mobile/h3" textTransform="initial">
|
|
122
122
|
{name}
|
|
123
123
|
</Text>
|
|
124
124
|
</Box>
|
|
@@ -162,21 +162,20 @@ const FilterForm = ({ isOpen, onClose, selectedCategory }: FilterFormProps) => {
|
|
|
162
162
|
) : null
|
|
163
163
|
}
|
|
164
164
|
>
|
|
165
|
-
<Box display="flex" flexDirection="column" gap="
|
|
165
|
+
<Box display="flex" flexDirection="column" gap="8">
|
|
166
166
|
<Box
|
|
167
167
|
backgroundColor="background/primary"
|
|
168
168
|
display="flex"
|
|
169
169
|
flexDirection="column"
|
|
170
170
|
gap="8"
|
|
171
|
-
marginBottom="8"
|
|
172
171
|
position="sticky"
|
|
173
172
|
top="0"
|
|
174
173
|
>
|
|
175
174
|
{(withSearch || isAsync) && (
|
|
176
175
|
<>
|
|
177
|
-
<Divider />
|
|
176
|
+
<Divider color="border/minimal" />
|
|
178
177
|
<SearchInput
|
|
179
|
-
marginInline="
|
|
178
|
+
marginInline="8"
|
|
180
179
|
onChange={onSearchChange}
|
|
181
180
|
placeholder="Search for options"
|
|
182
181
|
value={searchValue}
|
|
@@ -184,7 +183,7 @@ const FilterForm = ({ isOpen, onClose, selectedCategory }: FilterFormProps) => {
|
|
|
184
183
|
/>
|
|
185
184
|
</>
|
|
186
185
|
)}
|
|
187
|
-
<Divider />
|
|
186
|
+
<Divider color="border/minimal" />
|
|
188
187
|
</Box>
|
|
189
188
|
<Box display="flex" flexDirection="column" gap="8">
|
|
190
189
|
{isLoading && (
|
|
@@ -4,6 +4,7 @@ import FormLabel from '../../Form/FormLabel';
|
|
|
4
4
|
import Button from '../../Button/Button';
|
|
5
5
|
import Box from '../../Box/Box';
|
|
6
6
|
import Icon from '../../Icon/Icon';
|
|
7
|
+
import Text from '../../Text/Text';
|
|
7
8
|
import IconButton from '../../IconButton/IconButton';
|
|
8
9
|
import { useFilterContext } from '../Filter.context';
|
|
9
10
|
import { getDateRangeLabel, getOptionLabel } from '../Filter.utils';
|
|
@@ -59,7 +60,9 @@ const FilterItem = ({ category }: FilterItemProps) => {
|
|
|
59
60
|
variant="secondary"
|
|
60
61
|
width="100%"
|
|
61
62
|
>
|
|
62
|
-
|
|
63
|
+
<Text overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" width="100%" textAlign="left" minW="0">
|
|
64
|
+
{getText()}
|
|
65
|
+
</Text>
|
|
63
66
|
</Button>
|
|
64
67
|
<IconButton
|
|
65
68
|
aria-label="Clear"
|
|
@@ -88,7 +91,9 @@ const FilterItem = ({ category }: FilterItemProps) => {
|
|
|
88
91
|
{getText()}
|
|
89
92
|
</Box>
|
|
90
93
|
) : (
|
|
91
|
-
|
|
94
|
+
<Text overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" minW="0">
|
|
95
|
+
{getText()}
|
|
96
|
+
</Text>
|
|
92
97
|
)}
|
|
93
98
|
</Button>
|
|
94
99
|
)}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { InputProps } from 'chakra-ui-2--react';
|
|
3
|
+
import { useDebounce } from '../../../utils/utils';
|
|
4
|
+
import SearchInput from '../../SearchInput/SearchInput';
|
|
5
|
+
import { FilterValue } from '../Filter.types';
|
|
6
|
+
|
|
7
|
+
export interface FilterSearchProps extends Omit<InputProps, 'onChange' | 'value'> {
|
|
8
|
+
onChange: (category: string, selected: FilterValue) => void;
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const FilterSearch = (props: FilterSearchProps) => {
|
|
13
|
+
const { onChange, value } = props;
|
|
14
|
+
|
|
15
|
+
const [searchValue, setSearchValue] = useState(value);
|
|
16
|
+
|
|
17
|
+
const onInputChange = (newSearchValue: string) => {
|
|
18
|
+
setSearchValue(newSearchValue);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const debouncedSearchValue = useDebounce<string>(searchValue, 500);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
onChange('search', debouncedSearchValue.length ? [debouncedSearchValue] : []);
|
|
25
|
+
}, [debouncedSearchValue]);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setSearchValue(value);
|
|
29
|
+
}, [value]);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<SearchInput onChange={onInputChange} paddingBlock="4" placeholder="Search" value={searchValue} variant="mobile" />
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default FilterSearch;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { forwardRef, Icon, IconProps } from 'chakra-ui-2--react';
|
|
2
|
+
|
|
3
|
+
const BuildEnvSetup = forwardRef<IconProps, 'svg'>((props, ref) => (
|
|
4
|
+
<Icon ref={ref} viewBox="0 0 16 16" {...props}>
|
|
5
|
+
<path
|
|
6
|
+
fillRule="evenodd"
|
|
7
|
+
clipRule="evenodd"
|
|
8
|
+
d="M12.3721 6.71387C12.9165 6.88799 13.4161 7.16252 13.8486 7.5166L14.0918 7.71484L13.8047 8.91211C13.9455 9.10106 14.0663 9.3049 14.167 9.52051L15.3682 9.87891L15.4238 10.1797C15.4725 10.4434 15.5 10.7176 15.5 11C15.5 11.281 15.4735 11.5549 15.4248 11.8193L15.3691 12.1211L14.168 12.4756C14.0672 12.692 13.9448 12.8954 13.8037 13.085L14.0918 14.2842L13.8486 14.4824C13.4164 14.8364 12.9171 15.1118 12.3721 15.2861L12.0898 15.376L11.876 15.1719L11.1621 14.4951L11 14.5C10.8755 14.5 10.7532 14.4913 10.6328 14.4785L9.96094 15.1162L9.73535 15.3291L9.44434 15.2217C8.88338 15.0149 8.37465 14.7011 7.94434 14.3027L7.72949 14.1035L8.03027 12.8477C7.97055 12.7524 7.91507 12.6537 7.86426 12.5518L6.93652 12.2764L6.64844 12.1904L6.58887 11.8955C6.53026 11.6054 6.5 11.3058 6.5 11C6.5 10.6938 6.52983 10.3929 6.58887 10.1016L6.64844 9.80762L7.86523 9.44531C7.91585 9.34408 7.97091 9.246 8.03027 9.15137L7.79785 8.17871L7.72949 7.89453L7.94434 7.69531C8.37475 7.29715 8.88402 6.98386 9.44434 6.77734L9.73535 6.66992L9.96094 6.88281L10.6328 7.51953C10.7537 7.50693 10.8762 7.5 11 7.5L11.1602 7.50391L12.0908 6.62402L12.3721 6.71387ZM11 9.75C10.3097 9.75006 9.75 10.3097 9.75 11C9.75 11.6903 10.3097 12.2499 11 12.25C11.6904 12.25 12.25 11.6904 12.25 11C12.25 10.3096 11.6904 9.75 11 9.75Z"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
/>
|
|
11
|
+
<path
|
|
12
|
+
fillRule="evenodd"
|
|
13
|
+
clipRule="evenodd"
|
|
14
|
+
d="M13.5 1C14.3284 1 15 1.67157 15 2.5V7.23047C14.57 6.77408 14.0635 6.39196 13.5 6.10352V5.25H2.5V12H5.59473C5.69238 12.5309 5.8651 13.0343 6.10352 13.5H2.5C1.67157 13.5 1 12.8284 1 12V2.5C1 1.67157 1.67157 1 2.5 1H13.5ZM2.5 3.75H13.5V2.5H2.5V3.75Z"
|
|
15
|
+
fill="currentColor"
|
|
16
|
+
/>
|
|
17
|
+
<path
|
|
18
|
+
d="M6.45801 7.89746C6.04014 8.50802 5.74276 9.20722 5.59863 9.96094L4.53027 11.0303L3.46973 9.96973L4.93945 8.5L3.46973 7.03027L4.53027 5.96973L6.45801 7.89746Z"
|
|
19
|
+
fill="currentColor"
|
|
20
|
+
/>
|
|
21
|
+
</Icon>
|
|
22
|
+
));
|
|
23
|
+
|
|
24
|
+
export default BuildEnvSetup;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { forwardRef, Icon, IconProps } from 'chakra-ui-2--react';
|
|
2
|
+
|
|
3
|
+
const FolderEmpty = forwardRef<IconProps, 'svg'>((props, ref) => (
|
|
4
|
+
<Icon ref={ref} viewBox="0 0 16 16" {...props}>
|
|
5
|
+
<path
|
|
6
|
+
fillRule="evenodd"
|
|
7
|
+
clipRule="evenodd"
|
|
8
|
+
d="M6.37868 2.5C6.7765 2.5 7.15804 2.65804 7.43934 2.93934L8 3.5H13.5C14.3284 3.5 15 4.17157 15 5V12C15 12.8284 14.3284 13.5 13.5 13.5H2.5C1.67157 13.5 1 12.8284 1 12V4C1 3.17157 1.67157 2.5 2.5 2.5H6.37868ZM7.37868 5L6.37868 4H2.5V12H13.5V5L7.37868 5Z"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
/>
|
|
11
|
+
</Icon>
|
|
12
|
+
));
|
|
13
|
+
|
|
14
|
+
export default FolderEmpty;
|
|
@@ -46,6 +46,7 @@ export { default as Bug } from './Bug';
|
|
|
46
46
|
export { default as Build } from './Build';
|
|
47
47
|
export { default as BuildCache } from './BuildCache';
|
|
48
48
|
export { default as BuildCacheSolid } from './BuildCacheSolid';
|
|
49
|
+
export { default as BuildEnvSetup } from './BuildEnvSetup';
|
|
49
50
|
export { default as AbortCircle } from './AbortCircle';
|
|
50
51
|
export { default as AbortCircleFilled } from './AbortCircleFilled';
|
|
51
52
|
export { default as CrossCircleFilled } from './CrossCircleFilled';
|
|
@@ -108,6 +109,7 @@ export { default as Filter } from './Filter';
|
|
|
108
109
|
export { default as Flag } from './Flag';
|
|
109
110
|
export { default as Flutter } from './Flutter';
|
|
110
111
|
export { default as Folder } from './Folder';
|
|
112
|
+
export { default as FolderEmpty } from './FolderEmpty';
|
|
111
113
|
export { default as Fullscreen } from './Fullscreen';
|
|
112
114
|
export { default as FullscreenExit } from './FullscreenExit';
|
|
113
115
|
export { default as Gauge } from './Gauge';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { forwardRef, Icon, IconProps } from 'chakra-ui-2--react';
|
|
2
|
+
|
|
3
|
+
const BuildEnvSetup = forwardRef<IconProps, 'svg'>((props, ref) => (
|
|
4
|
+
<Icon ref={ref} viewBox="0 0 24 24" {...props}>
|
|
5
|
+
<path
|
|
6
|
+
fillRule="evenodd"
|
|
7
|
+
clipRule="evenodd"
|
|
8
|
+
d="M19.165 12.2578C19.9325 12.5013 20.6274 12.9074 21.208 13.4375L21.4258 13.6367L21.3574 13.9238L21.0566 15.1729C21.1424 15.3038 21.2209 15.4397 21.292 15.5801L22.8076 16.0303L22.8711 16.3184C22.9543 16.6981 23 17.0936 23 17.5C23 17.9061 22.954 18.3006 22.8711 18.6797L22.8086 18.9688L21.292 19.418C21.2212 19.5581 21.1432 19.6942 21.0576 19.8252L21.4258 21.3623L21.208 21.5615C20.6276 22.0916 19.933 22.4985 19.165 22.7422L18.8838 22.8311L18.6699 22.6289L17.7344 21.7422L17.501 21.75C17.4213 21.75 17.3428 21.7468 17.2656 21.7422L16.3311 22.6289L16.1172 22.8311L15.8359 22.7422C15.068 22.4985 14.3728 22.0921 13.792 21.5615L13.5742 21.3623L13.9414 19.8242C13.856 19.6937 13.7779 19.558 13.707 19.418L12.1914 18.9678L12.1289 18.6787C12.0462 18.3002 12 17.9059 12 17.5C12 17.0939 12.046 16.6993 12.1289 16.3203L12.1914 16.0312L12.4746 15.9473L13.707 15.5801C13.7779 15.4402 13.856 15.3044 13.9414 15.1738L13.5742 13.6367L13.792 13.4375C14.3729 12.9069 15.0685 12.5013 15.8359 12.2578L16.1172 12.1689L17.2656 13.2559C17.3433 13.2515 17.4218 13.25 17.501 13.25C17.5795 13.25 17.6574 13.2515 17.7344 13.2559L18.8838 12.1689L19.165 12.2578ZM17.501 16C16.6725 16 16.001 16.6716 16.001 17.5C16.001 18.3284 16.6725 19 17.501 19C18.3292 18.9997 19.001 18.3283 19.001 17.5C19.001 16.6717 18.3292 16.0003 17.501 16Z"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
/>
|
|
11
|
+
<path
|
|
12
|
+
fillRule="evenodd"
|
|
13
|
+
clipRule="evenodd"
|
|
14
|
+
d="M20 3C21.1046 3 22 3.89543 22 5V12.8115C21.4243 12.2588 20.748 11.81 20 11.498V9H4V19H11.1758C11.3461 19.7207 11.6364 20.3943 12.0244 21H4C2.89543 21 2 20.1046 2 19V5C2 3.89543 2.89543 3 4 3H20ZM4 7H20V5H4V7Z"
|
|
15
|
+
fill="currentColor"
|
|
16
|
+
/>
|
|
17
|
+
<path
|
|
18
|
+
d="M11.4141 14L7.70703 17.707L6.29297 16.293L8.58594 14L6.29297 11.707L7.70703 10.293L11.4141 14Z"
|
|
19
|
+
fill="currentColor"
|
|
20
|
+
/>
|
|
21
|
+
</Icon>
|
|
22
|
+
));
|
|
23
|
+
|
|
24
|
+
export default BuildEnvSetup;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { forwardRef, Icon, IconProps } from 'chakra-ui-2--react';
|
|
2
|
+
|
|
3
|
+
const FolderEmpty = forwardRef<IconProps, 'svg'>((props, ref) => (
|
|
4
|
+
<Icon ref={ref} viewBox="0 0 24 24" {...props}>
|
|
5
|
+
<path
|
|
6
|
+
fillRule="evenodd"
|
|
7
|
+
clipRule="evenodd"
|
|
8
|
+
d="M10.5858 4.58579C10.2107 4.21071 9.70201 4 9.17157 4H4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 20H20C21.1046 20 22 19.1046 22 18V8C22 6.89543 21.1046 6 20 6H12L10.5858 4.58579ZM4 6V18H20V8H11.1716L9.17157 6H4Z"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
/>
|
|
11
|
+
</Icon>
|
|
12
|
+
));
|
|
13
|
+
|
|
14
|
+
export default FolderEmpty;
|
|
@@ -46,6 +46,7 @@ export { default as Bug } from './Bug';
|
|
|
46
46
|
export { default as Build } from './Build';
|
|
47
47
|
export { default as BuildCache } from './BuildCache';
|
|
48
48
|
export { default as BuildCacheSolid } from './BuildCacheSolid';
|
|
49
|
+
export { default as BuildEnvSetup } from './BuildEnvSetup';
|
|
49
50
|
export { default as AbortCircle } from './AbortCircle';
|
|
50
51
|
export { default as AbortCircleFilled } from './AbortCircleFilled';
|
|
51
52
|
export { default as DoubleCircle } from './DoubleCircle';
|
|
@@ -106,6 +107,7 @@ export { default as Filter } from './Filter';
|
|
|
106
107
|
export { default as Flag } from './Flag';
|
|
107
108
|
export { default as Flutter } from './Flutter';
|
|
108
109
|
export { default as Folder } from './Folder';
|
|
110
|
+
export { default as FolderEmpty } from './FolderEmpty';
|
|
109
111
|
export { default as Fullscreen } from './Fullscreen';
|
|
110
112
|
export { default as FullscreenExit } from './FullscreenExit';
|
|
111
113
|
export { default as Gauge } from './Gauge';
|
|
@@ -3,6 +3,7 @@ import { useControllableState } from 'chakra-ui-2--react';
|
|
|
3
3
|
import { NodeCallback, TreeViewAction, TreeViewState } from './TreeView.types';
|
|
4
4
|
|
|
5
5
|
const TreeViewContext = createContext<TreeViewState & TreeViewAction>({
|
|
6
|
+
variant: undefined,
|
|
6
7
|
selectedId: undefined,
|
|
7
8
|
expandedIds: new Set<string>(),
|
|
8
9
|
disabledIds: new Set<string>(),
|
|
@@ -10,7 +11,7 @@ const TreeViewContext = createContext<TreeViewState & TreeViewAction>({
|
|
|
10
11
|
onExpandedChange: () => undefined,
|
|
11
12
|
});
|
|
12
13
|
|
|
13
|
-
export type
|
|
14
|
+
export type TreeViewContextProviderProps = PropsWithChildren<
|
|
14
15
|
Partial<TreeViewState> &
|
|
15
16
|
Partial<TreeViewAction> & {
|
|
16
17
|
defaultSelectedId?: string;
|
|
@@ -19,6 +20,7 @@ export type TreeViewContenxtProviderProps = PropsWithChildren<
|
|
|
19
20
|
}
|
|
20
21
|
>;
|
|
21
22
|
export const TreeViewContextProvider = ({
|
|
23
|
+
variant = 'navigation',
|
|
22
24
|
selectedId: controlledSelectedId,
|
|
23
25
|
defaultSelectedId,
|
|
24
26
|
expandedIds: controlledExpandedIds,
|
|
@@ -28,7 +30,7 @@ export const TreeViewContextProvider = ({
|
|
|
28
30
|
onSelectionChange,
|
|
29
31
|
onExpandedChange,
|
|
30
32
|
children,
|
|
31
|
-
}:
|
|
33
|
+
}: TreeViewContextProviderProps) => {
|
|
32
34
|
const [selectedId, setSelectedId] = useControllableState({
|
|
33
35
|
value: controlledSelectedId,
|
|
34
36
|
defaultValue: defaultSelectedId,
|
|
@@ -71,8 +73,15 @@ export const TreeViewContextProvider = ({
|
|
|
71
73
|
);
|
|
72
74
|
|
|
73
75
|
const contextValue = useMemo(
|
|
74
|
-
() => ({
|
|
75
|
-
|
|
76
|
+
() => ({
|
|
77
|
+
variant,
|
|
78
|
+
selectedId,
|
|
79
|
+
expandedIds,
|
|
80
|
+
disabledIds,
|
|
81
|
+
onSelectionChange: handleSelect,
|
|
82
|
+
onExpandedChange: handleExpand,
|
|
83
|
+
}),
|
|
84
|
+
[variant, selectedId, expandedIds, disabledIds, handleSelect, handleExpand],
|
|
76
85
|
);
|
|
77
86
|
|
|
78
87
|
return <TreeViewContext.Provider value={contextValue}>{children}</TreeViewContext.Provider>;
|
|
@@ -1,28 +1,63 @@
|
|
|
1
1
|
import { createMultiStyleConfigHelpers } from 'chakra-ui-2--styled-system';
|
|
2
2
|
|
|
3
3
|
const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers([
|
|
4
|
+
'listItem',
|
|
4
5
|
'icon',
|
|
6
|
+
'iconWrapper',
|
|
5
7
|
'button',
|
|
6
8
|
'buttonContent',
|
|
7
9
|
'textBlock',
|
|
8
10
|
'suffixBlock',
|
|
9
11
|
'selectionIndicator',
|
|
12
|
+
'collapseContent',
|
|
13
|
+
'hoverActionsWrapper',
|
|
14
|
+
'hoverActionButton',
|
|
15
|
+
'leafBadge',
|
|
10
16
|
]);
|
|
11
17
|
|
|
12
18
|
const baseStyle = definePartsStyle({
|
|
19
|
+
listItem: {
|
|
20
|
+
overflow: 'hidden',
|
|
21
|
+
},
|
|
13
22
|
icon: {
|
|
14
23
|
marginTop: '10px',
|
|
15
24
|
marginBottom: '2px',
|
|
16
25
|
color: 'icon/primary',
|
|
17
26
|
},
|
|
27
|
+
leafBadge: {
|
|
28
|
+
marginTop: '10px',
|
|
29
|
+
marginBottom: '2px',
|
|
30
|
+
},
|
|
31
|
+
iconWrapper: {
|
|
32
|
+
position: 'relative',
|
|
33
|
+
display: 'flex',
|
|
34
|
+
alignSelf: 'flex-start',
|
|
35
|
+
zIndex: 1,
|
|
36
|
+
'&.show-connector::after': {
|
|
37
|
+
content: '""',
|
|
38
|
+
position: 'absolute',
|
|
39
|
+
left: '50%',
|
|
40
|
+
top: '100%',
|
|
41
|
+
bottom: '-100px',
|
|
42
|
+
width: '1px',
|
|
43
|
+
backgroundColor: 'border/regular',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
18
46
|
button: {
|
|
19
47
|
pos: 'relative',
|
|
20
48
|
py: '0',
|
|
21
49
|
px: '16',
|
|
22
50
|
pl: 'calc(16px + 24px * var(--level))',
|
|
23
51
|
w: '100%',
|
|
52
|
+
overflow: 'hidden',
|
|
24
53
|
_hover: {
|
|
25
54
|
bgColor: 'background/secondary',
|
|
55
|
+
'& .hover-actions': {
|
|
56
|
+
display: 'flex',
|
|
57
|
+
},
|
|
58
|
+
'& .hide-on-hover': {
|
|
59
|
+
display: 'none',
|
|
60
|
+
},
|
|
26
61
|
},
|
|
27
62
|
_active: {
|
|
28
63
|
bgColor: 'background/tertiary',
|
|
@@ -37,6 +72,15 @@ const baseStyle = definePartsStyle({
|
|
|
37
72
|
bgColor: 'background/selected-hover',
|
|
38
73
|
},
|
|
39
74
|
},
|
|
75
|
+
'&.show-ancestor-connectors::before': {
|
|
76
|
+
content: '""',
|
|
77
|
+
position: 'absolute',
|
|
78
|
+
top: 0,
|
|
79
|
+
bottom: 0,
|
|
80
|
+
left: '24px',
|
|
81
|
+
width: 'calc(var(--level) * 24px)',
|
|
82
|
+
zIndex: 1,
|
|
83
|
+
},
|
|
40
84
|
},
|
|
41
85
|
buttonContent: {
|
|
42
86
|
gap: '8',
|
|
@@ -63,9 +107,17 @@ const baseStyle = definePartsStyle({
|
|
|
63
107
|
display: 'flex',
|
|
64
108
|
alignItems: 'center',
|
|
65
109
|
justifyContent: 'flex-end',
|
|
66
|
-
paddingTop: '8',
|
|
67
110
|
paddingLeft: '16',
|
|
68
111
|
gap: '8',
|
|
112
|
+
'& > .hide-on-hover': {
|
|
113
|
+
display: 'flex',
|
|
114
|
+
},
|
|
115
|
+
'button:hover > div > div > &': {
|
|
116
|
+
pt: '0',
|
|
117
|
+
},
|
|
118
|
+
'button:hover > div > div > & > .hide-on-hover': {
|
|
119
|
+
display: 'none',
|
|
120
|
+
},
|
|
69
121
|
},
|
|
70
122
|
selectionIndicator: {
|
|
71
123
|
width: '3px',
|
|
@@ -75,6 +127,23 @@ const baseStyle = definePartsStyle({
|
|
|
75
127
|
bgColor: 'border/selected',
|
|
76
128
|
borderLeftRadius: '2',
|
|
77
129
|
},
|
|
130
|
+
collapseContent: {
|
|
131
|
+
position: 'relative',
|
|
132
|
+
'&.show-connector::before': {
|
|
133
|
+
content: '""',
|
|
134
|
+
position: 'absolute',
|
|
135
|
+
top: 0,
|
|
136
|
+
bottom: 0,
|
|
137
|
+
left: 'calc(16px + var(--level) * 24px + 8px)',
|
|
138
|
+
width: '1px',
|
|
139
|
+
backgroundColor: 'border/regular',
|
|
140
|
+
zIndex: 1,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
hoverActionsWrapper: {
|
|
144
|
+
display: 'none',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
},
|
|
78
147
|
});
|
|
79
148
|
|
|
80
149
|
const TreeViewTheme = defineMultiStyleConfig({
|
|
@@ -1,81 +1,36 @@
|
|
|
1
1
|
import { PropsWithChildren } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { TreeViewContextProviderProps, TreeViewContextProvider } from './TreeView.context';
|
|
3
3
|
import TreeViewGroup from './TreeViewGroup';
|
|
4
4
|
import TreeViewNode from './TreeViewNode';
|
|
5
|
-
import { NodeData } from './TreeView.types';
|
|
5
|
+
import { NodeData, TreeViewVariant, BranchNodeData, LeafNodeData } from './TreeView.types';
|
|
6
6
|
|
|
7
|
-
const TreeViewLeaf = (props: Omit<
|
|
7
|
+
const TreeViewLeaf = (props: Omit<LeafNodeData, 'type' | 'children'>) => (
|
|
8
8
|
<TreeViewNode type="leaf" level={-1} indexPath={[]} titlePath={[]} {...props} />
|
|
9
9
|
);
|
|
10
|
-
const TreeViewBranch = (props: PropsWithChildren<Omit<
|
|
10
|
+
const TreeViewBranch = (props: PropsWithChildren<Omit<BranchNodeData, 'type' | 'children'>>) => (
|
|
11
11
|
<TreeViewNode type="branch" level={-1} indexPath={[]} titlePath={[]} {...props} />
|
|
12
12
|
);
|
|
13
13
|
|
|
14
14
|
const renderTreeNodes = (nodes: NodeData[]) => {
|
|
15
15
|
return nodes.map((node) => {
|
|
16
|
-
const {
|
|
17
|
-
id,
|
|
18
|
-
type,
|
|
19
|
-
title,
|
|
20
|
-
description,
|
|
21
|
-
iconName,
|
|
22
|
-
iconColor,
|
|
23
|
-
label,
|
|
24
|
-
labelIconName,
|
|
25
|
-
labelIconColor,
|
|
26
|
-
children,
|
|
27
|
-
onClick,
|
|
28
|
-
} = node;
|
|
16
|
+
const { id, type, children, ...rest } = node;
|
|
29
17
|
|
|
30
18
|
if (type === 'branch') {
|
|
31
19
|
return (
|
|
32
|
-
<TreeViewNode
|
|
33
|
-
key={id}
|
|
34
|
-
id={id}
|
|
35
|
-
type="branch"
|
|
36
|
-
title={title}
|
|
37
|
-
description={description}
|
|
38
|
-
iconName={iconName}
|
|
39
|
-
iconColor={iconColor}
|
|
40
|
-
label={label}
|
|
41
|
-
labelIconName={labelIconName}
|
|
42
|
-
labelIconColor={labelIconColor}
|
|
43
|
-
// Thes props will be overridden by the TreeViewGroup
|
|
44
|
-
level={-1}
|
|
45
|
-
indexPath={[]}
|
|
46
|
-
titlePath={[]}
|
|
47
|
-
onClick={onClick}
|
|
48
|
-
>
|
|
20
|
+
<TreeViewNode key={id} id={id} type="branch" level={-1} indexPath={[]} titlePath={[]} {...rest}>
|
|
49
21
|
{children && renderTreeNodes(children)}
|
|
50
22
|
</TreeViewNode>
|
|
51
23
|
);
|
|
52
24
|
}
|
|
53
25
|
|
|
54
|
-
return
|
|
55
|
-
<TreeViewNode
|
|
56
|
-
key={id}
|
|
57
|
-
id={id}
|
|
58
|
-
type="leaf"
|
|
59
|
-
title={title}
|
|
60
|
-
description={description}
|
|
61
|
-
iconName={iconName}
|
|
62
|
-
iconColor={iconColor}
|
|
63
|
-
label={label}
|
|
64
|
-
labelIconName={labelIconName}
|
|
65
|
-
labelIconColor={labelIconColor}
|
|
66
|
-
// Thes props will be overridden by the TreeViewGroup
|
|
67
|
-
level={-1}
|
|
68
|
-
indexPath={[]}
|
|
69
|
-
titlePath={[]}
|
|
70
|
-
onClick={onClick}
|
|
71
|
-
/>
|
|
72
|
-
);
|
|
26
|
+
return <TreeViewNode key={id} id={id} type="leaf" level={-1} indexPath={[]} titlePath={[]} {...rest} />;
|
|
73
27
|
});
|
|
74
28
|
};
|
|
75
29
|
|
|
76
|
-
export type TreeViewProps =
|
|
30
|
+
export type TreeViewProps = TreeViewContextProviderProps & {
|
|
77
31
|
label: string;
|
|
78
32
|
data?: NodeData[];
|
|
33
|
+
variant?: TreeViewVariant;
|
|
79
34
|
};
|
|
80
35
|
|
|
81
36
|
const TreeView = ({ data, label, children, ...rest }: PropsWithChildren<TreeViewProps>) => {
|
|
@@ -1,21 +1,87 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { UseFloatingProps } from '@floating-ui/react-dom-interactions';
|
|
1
3
|
import { TypeIconName } from '../Icon/Icon';
|
|
4
|
+
import { BadgeColorScheme, BadgeProps } from '../Badge/Badge';
|
|
2
5
|
|
|
3
6
|
export type NodeType = 'branch' | 'leaf';
|
|
4
7
|
|
|
5
|
-
export type
|
|
8
|
+
export type TreeViewVariant = 'navigation' | 'files';
|
|
9
|
+
|
|
10
|
+
export type HoverControlButton = {
|
|
11
|
+
iconName: TypeIconName;
|
|
12
|
+
label: string;
|
|
13
|
+
isDanger?: boolean;
|
|
14
|
+
onClick: (e: React.MouseEvent) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type LeafIndicator =
|
|
18
|
+
| {
|
|
19
|
+
iconName?: TypeIconName;
|
|
20
|
+
iconColor?: string;
|
|
21
|
+
badgeLabel?: never;
|
|
22
|
+
badgeVariant?: never;
|
|
23
|
+
badgeColorScheme?: never;
|
|
24
|
+
badgeTooltip?: never;
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
iconName?: never;
|
|
28
|
+
iconColor?: never;
|
|
29
|
+
badgeLabel?: string;
|
|
30
|
+
badgeVariant?: BadgeProps['variant'];
|
|
31
|
+
badgeColorScheme?: BadgeColorScheme;
|
|
32
|
+
badgeTooltip?: {
|
|
33
|
+
label: string;
|
|
34
|
+
placement?: UseFloatingProps['placement'];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type NodeBase = {
|
|
6
39
|
id: string;
|
|
7
|
-
type: NodeType;
|
|
8
40
|
title: string;
|
|
9
41
|
description?: string;
|
|
10
|
-
|
|
11
|
-
iconColor?: string;
|
|
42
|
+
hasWarning?: boolean;
|
|
12
43
|
label?: string;
|
|
13
44
|
labelIconName?: TypeIconName;
|
|
14
45
|
labelIconColor?: string;
|
|
15
|
-
children?: NodeData[];
|
|
16
46
|
onClick?: VoidFunction;
|
|
47
|
+
hoverActions?: [HoverControlButton] | [HoverControlButton, HoverControlButton];
|
|
48
|
+
children?: NodeData[];
|
|
17
49
|
};
|
|
18
50
|
|
|
51
|
+
type LeafNodeData = NodeBase & { type: 'leaf' } & LeafIndicator;
|
|
52
|
+
|
|
53
|
+
type BranchNodeData = NodeBase & {
|
|
54
|
+
type: 'branch';
|
|
55
|
+
iconName?: TypeIconName;
|
|
56
|
+
iconColor?: string;
|
|
57
|
+
branchIconTooltip?: {
|
|
58
|
+
label: string;
|
|
59
|
+
placement?: UseFloatingProps['placement'];
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type NodeData = LeafNodeData | BranchNodeData;
|
|
64
|
+
export type { LeafNodeData, BranchNodeData };
|
|
65
|
+
|
|
66
|
+
type AllNodeProps = NodeBase & {
|
|
67
|
+
type: NodeType;
|
|
68
|
+
iconName?: TypeIconName;
|
|
69
|
+
iconColor?: string;
|
|
70
|
+
badgeLabel?: string;
|
|
71
|
+
badgeVariant?: BadgeProps['variant'];
|
|
72
|
+
badgeColorScheme?: BadgeColorScheme;
|
|
73
|
+
badgeTooltip?: {
|
|
74
|
+
label: string;
|
|
75
|
+
placement?: UseFloatingProps['placement'];
|
|
76
|
+
};
|
|
77
|
+
branchIconTooltip?: {
|
|
78
|
+
label: string;
|
|
79
|
+
placement?: UseFloatingProps['placement'];
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type TreeViewNodeProps = PropsWithChildren<Omit<AllNodeProps, 'children'> & NodePath>;
|
|
84
|
+
|
|
19
85
|
export type NodePath = {
|
|
20
86
|
level: number;
|
|
21
87
|
indexPath: number[];
|
|
@@ -32,6 +98,7 @@ export type NodeState = {
|
|
|
32
98
|
};
|
|
33
99
|
|
|
34
100
|
export type TreeViewState = {
|
|
101
|
+
variant?: TreeViewVariant;
|
|
35
102
|
selectedId?: string;
|
|
36
103
|
expandedIds: Set<string>;
|
|
37
104
|
disabledIds: Set<string>;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { memo,
|
|
1
|
+
import { memo, useCallback, useMemo, MouseEvent } from 'react';
|
|
2
2
|
import { useMultiStyleConfig } from 'chakra-ui-2--react';
|
|
3
3
|
import Icon from '../Icon/Icon';
|
|
4
|
+
import Badge from '../Badge/Badge';
|
|
4
5
|
import Box from '../Box/Box';
|
|
5
6
|
import Text from '../Text/Text';
|
|
6
7
|
import Collapse from '../Collapse/Collapse';
|
|
8
|
+
import ControlButton from '../ControlButton/ControlButton';
|
|
9
|
+
import Tooltip from '../Tooltip/Tooltip';
|
|
7
10
|
import TreeViewGroup from './TreeViewGroup';
|
|
8
11
|
import { useTreeViewContext } from './TreeView.context';
|
|
9
|
-
import {
|
|
12
|
+
import { HoverControlButton, TreeViewNodeProps } from './TreeView.types';
|
|
10
13
|
|
|
11
14
|
type TreeViewNodeContentProps = TreeViewNodeProps & {
|
|
12
15
|
isBranch: boolean;
|
|
@@ -22,11 +25,18 @@ const TreeViewNodeContent = memo(
|
|
|
22
25
|
id,
|
|
23
26
|
title,
|
|
24
27
|
description,
|
|
28
|
+
hasWarning,
|
|
25
29
|
iconName,
|
|
26
30
|
iconColor,
|
|
31
|
+
badgeLabel,
|
|
32
|
+
badgeVariant,
|
|
33
|
+
badgeColorScheme,
|
|
34
|
+
badgeTooltip,
|
|
27
35
|
label,
|
|
28
36
|
labelIconName,
|
|
29
37
|
labelIconColor,
|
|
38
|
+
hoverActions,
|
|
39
|
+
branchIconTooltip,
|
|
30
40
|
// NodePath
|
|
31
41
|
level,
|
|
32
42
|
indexPath,
|
|
@@ -40,6 +50,29 @@ const TreeViewNodeContent = memo(
|
|
|
40
50
|
children,
|
|
41
51
|
}: TreeViewNodeContentProps) => {
|
|
42
52
|
const styles = useMultiStyleConfig('TreeView', {});
|
|
53
|
+
const { variant } = useTreeViewContext();
|
|
54
|
+
|
|
55
|
+
const isFilesVariant = variant === 'files';
|
|
56
|
+
const hasChildren = Boolean(children);
|
|
57
|
+
const isSelectable = !(isBranch && !hasChildren);
|
|
58
|
+
const isEmptyBranch = isBranch && !hasChildren;
|
|
59
|
+
const chevronIcon = isExpanded ? 'ChevronDown' : 'ChevronRight';
|
|
60
|
+
const primaryIconName = isFilesVariant ? 'FolderEmpty' : chevronIcon;
|
|
61
|
+
|
|
62
|
+
const showAncestorConnectors = isFilesVariant && level > 1;
|
|
63
|
+
const showOwnConnector = isFilesVariant && isBranch && isExpanded;
|
|
64
|
+
|
|
65
|
+
const hasHoverActions = hoverActions && hoverActions.length > 0;
|
|
66
|
+
const hasLabelContent = label || labelIconName || hasWarning;
|
|
67
|
+
|
|
68
|
+
const showIcon = !isBranch && iconName;
|
|
69
|
+
const showBadge = !isBranch && !iconName && badgeLabel;
|
|
70
|
+
|
|
71
|
+
const handleActionClick = useCallback((e: MouseEvent, action: HoverControlButton) => {
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
action.onClick(e);
|
|
75
|
+
}, []);
|
|
43
76
|
|
|
44
77
|
return (
|
|
45
78
|
<Box
|
|
@@ -47,27 +80,59 @@ const TreeViewNodeContent = memo(
|
|
|
47
80
|
id={id}
|
|
48
81
|
role="treeitem"
|
|
49
82
|
aria-level={level}
|
|
50
|
-
aria-disabled={isDisabled}
|
|
83
|
+
aria-disabled={isDisabled || !isSelectable}
|
|
51
84
|
aria-selected={isSelected}
|
|
52
|
-
{...(isBranch ? { 'aria-expanded': isExpanded } : {})}
|
|
85
|
+
{...(isBranch ? { 'aria-expanded': hasChildren ? isExpanded : undefined } : {})}
|
|
86
|
+
sx={styles.listItem}
|
|
53
87
|
>
|
|
54
88
|
<Box
|
|
55
89
|
as="button"
|
|
56
|
-
tabIndex={isSelected ?
|
|
57
|
-
data-selected={isSelected ? '' : undefined}
|
|
58
|
-
|
|
90
|
+
tabIndex={!isSelectable || !isSelected ? -1 : 0}
|
|
91
|
+
data-selected={isSelectable && isSelected ? '' : undefined}
|
|
92
|
+
className={`${showAncestorConnectors ? 'show-ancestor-connectors' : ''}`}
|
|
93
|
+
sx={{
|
|
94
|
+
'--level': `${level - 1}`,
|
|
95
|
+
'--connector-color': 'var(--chakra-colors-border-regular)',
|
|
96
|
+
cursor: isEmptyBranch ? 'default' : 'pointer',
|
|
97
|
+
...styles.button,
|
|
98
|
+
}}
|
|
59
99
|
onClick={onClick}
|
|
60
100
|
>
|
|
61
101
|
<Box sx={{ ...styles.buttonContent }}>
|
|
62
|
-
{isBranch &&
|
|
102
|
+
{isBranch && (
|
|
103
|
+
<Box sx={styles.iconWrapper} className={showOwnConnector ? 'show-connector' : ''}>
|
|
104
|
+
{isFilesVariant && isEmptyBranch ? (
|
|
105
|
+
<Tooltip
|
|
106
|
+
label={branchIconTooltip?.label || 'Empty branch'}
|
|
107
|
+
placement={branchIconTooltip?.placement || 'top'}
|
|
108
|
+
>
|
|
109
|
+
<Icon size="16" name={primaryIconName} sx={styles.icon} />
|
|
110
|
+
</Tooltip>
|
|
111
|
+
) : (
|
|
112
|
+
<Icon size="16" name={primaryIconName} sx={styles.icon} />
|
|
113
|
+
)}
|
|
114
|
+
</Box>
|
|
115
|
+
)}
|
|
63
116
|
<Box sx={styles.borderBox}>
|
|
64
|
-
{
|
|
117
|
+
{showIcon && (
|
|
65
118
|
<Icon
|
|
66
119
|
size="16"
|
|
67
120
|
name={iconName}
|
|
68
|
-
sx={{
|
|
121
|
+
sx={{
|
|
122
|
+
...styles.icon,
|
|
123
|
+
color: iconColor || (isSelected ? 'icon/primary' : 'icon/secondary'),
|
|
124
|
+
}}
|
|
69
125
|
/>
|
|
70
126
|
)}
|
|
127
|
+
|
|
128
|
+
{showBadge && (
|
|
129
|
+
<Tooltip shouldWrapChildren label={badgeTooltip?.label} placement={badgeTooltip?.placement || 'top'}>
|
|
130
|
+
<Badge variant={badgeVariant} colorScheme={badgeColorScheme} size="xxs" sx={styles.leafBadge}>
|
|
131
|
+
{badgeLabel}
|
|
132
|
+
</Badge>
|
|
133
|
+
</Tooltip>
|
|
134
|
+
)}
|
|
135
|
+
|
|
71
136
|
<Box sx={styles.textBlock}>
|
|
72
137
|
<Text fontWeight={isExpanded ? 'semibold' : 'normal'} overflowWrap="break-word" wordBreak="break-word">
|
|
73
138
|
{title}
|
|
@@ -83,25 +148,60 @@ const TreeViewNodeContent = memo(
|
|
|
83
148
|
</Text>
|
|
84
149
|
)}
|
|
85
150
|
</Box>
|
|
86
|
-
|
|
151
|
+
|
|
152
|
+
{(hasLabelContent || hasHoverActions) && (
|
|
87
153
|
<Box sx={styles.suffixBlock} color={isSelected || isExpanded ? 'text/primary' : 'text/secondary'}>
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
154
|
+
{hasHoverActions && (
|
|
155
|
+
<Box sx={styles.hoverActionsWrapper} className="hover-actions">
|
|
156
|
+
{hoverActions.map((action, index) => (
|
|
157
|
+
<ControlButton
|
|
158
|
+
/* eslint-disable-next-line react/no-array-index-key */
|
|
159
|
+
key={index}
|
|
160
|
+
aria-label={action.label}
|
|
161
|
+
iconName={action.iconName}
|
|
162
|
+
isDanger={action.isDanger}
|
|
163
|
+
onClick={(e: MouseEvent) => handleActionClick(e, action)}
|
|
164
|
+
size="xs"
|
|
165
|
+
/>
|
|
166
|
+
))}
|
|
167
|
+
</Box>
|
|
168
|
+
)}
|
|
169
|
+
{hasLabelContent && (
|
|
170
|
+
<Box
|
|
171
|
+
display="flex"
|
|
172
|
+
alignItems="center"
|
|
173
|
+
gap="8"
|
|
174
|
+
pt="8px"
|
|
175
|
+
className={hasHoverActions ? 'hide-on-hover' : undefined}
|
|
176
|
+
>
|
|
177
|
+
{hasWarning && <Icon name="WarningYellow" size="16" />}
|
|
178
|
+
{labelIconName && <Icon name={labelIconName} size="16" color={labelIconColor} />}
|
|
179
|
+
{label && (
|
|
180
|
+
<Text textStyle="body/md/regular" textAlign="right">
|
|
181
|
+
{label}
|
|
182
|
+
</Text>
|
|
183
|
+
)}
|
|
184
|
+
</Box>
|
|
93
185
|
)}
|
|
94
186
|
</Box>
|
|
95
187
|
)}
|
|
96
188
|
</Box>
|
|
97
|
-
{isSelected && <Box sx={styles.selectionIndicator} />}
|
|
189
|
+
{!isFilesVariant && isSelected && <Box sx={styles.selectionIndicator} />}
|
|
98
190
|
</Box>
|
|
99
191
|
</Box>
|
|
100
192
|
{isBranch && children && (
|
|
101
193
|
<Collapse in={isExpanded} unmountOnExit>
|
|
102
|
-
<
|
|
103
|
-
{
|
|
104
|
-
|
|
194
|
+
<Box
|
|
195
|
+
className={showOwnConnector ? 'show-connector' : undefined}
|
|
196
|
+
sx={{
|
|
197
|
+
'--level': `${level - 1}`,
|
|
198
|
+
...styles.collapseContent,
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
<TreeViewGroup role="group" level={level + 1} indexPath={indexPath} titlePath={titlePath}>
|
|
202
|
+
{children}
|
|
203
|
+
</TreeViewGroup>
|
|
204
|
+
</Box>
|
|
105
205
|
</Collapse>
|
|
106
206
|
)}
|
|
107
207
|
</Box>
|
|
@@ -109,19 +209,24 @@ const TreeViewNodeContent = memo(
|
|
|
109
209
|
},
|
|
110
210
|
);
|
|
111
211
|
|
|
112
|
-
export type TreeViewNodeProps = PropsWithChildren<Omit<NodeData, 'children'> & NodePath>;
|
|
113
|
-
|
|
114
212
|
const TreeViewNode = ({
|
|
115
213
|
// NodeData
|
|
116
214
|
id,
|
|
117
215
|
type,
|
|
118
216
|
title,
|
|
119
217
|
description,
|
|
218
|
+
hasWarning,
|
|
120
219
|
iconName,
|
|
121
220
|
iconColor,
|
|
221
|
+
badgeLabel,
|
|
222
|
+
badgeVariant,
|
|
223
|
+
badgeColorScheme,
|
|
224
|
+
badgeTooltip,
|
|
122
225
|
label,
|
|
123
226
|
labelIconName,
|
|
124
227
|
labelIconColor,
|
|
228
|
+
hoverActions,
|
|
229
|
+
branchIconTooltip,
|
|
125
230
|
onClick,
|
|
126
231
|
// NodePath
|
|
127
232
|
level,
|
|
@@ -133,12 +238,14 @@ const TreeViewNode = ({
|
|
|
133
238
|
const { selectedId, disabledIds, expandedIds, onSelectionChange, onExpandedChange } = useTreeViewContext();
|
|
134
239
|
|
|
135
240
|
const isBranch = type === 'branch';
|
|
241
|
+
const hasChildren = Boolean(children);
|
|
242
|
+
const isSelectable = !(isBranch && !hasChildren);
|
|
136
243
|
const isDisabled = useMemo(() => disabledIds.has(id), [id, disabledIds]);
|
|
137
244
|
const isExpanded = useMemo(() => (isBranch ? expandedIds.has(id) : undefined), [id, isBranch, expandedIds]);
|
|
138
|
-
const isSelected = useMemo(() => id === selectedId, [id, selectedId]);
|
|
245
|
+
const isSelected = useMemo(() => (isSelectable ? id === selectedId : false), [id, selectedId, isSelectable]);
|
|
139
246
|
|
|
140
247
|
const handleClick = useCallback(() => {
|
|
141
|
-
if (isDisabled) {
|
|
248
|
+
if (isDisabled || !isSelectable) {
|
|
142
249
|
return;
|
|
143
250
|
}
|
|
144
251
|
|
|
@@ -158,7 +265,7 @@ const TreeViewNode = ({
|
|
|
158
265
|
});
|
|
159
266
|
}
|
|
160
267
|
|
|
161
|
-
if (isBranch) {
|
|
268
|
+
if (isBranch && hasChildren) {
|
|
162
269
|
onExpandedChange({
|
|
163
270
|
node: {
|
|
164
271
|
id,
|
|
@@ -198,11 +305,18 @@ const TreeViewNode = ({
|
|
|
198
305
|
type={type}
|
|
199
306
|
title={title}
|
|
200
307
|
description={description}
|
|
308
|
+
hasWarning={hasWarning}
|
|
201
309
|
iconName={iconName}
|
|
202
310
|
iconColor={iconColor}
|
|
311
|
+
badgeLabel={badgeLabel}
|
|
312
|
+
badgeVariant={badgeVariant}
|
|
313
|
+
badgeColorScheme={badgeColorScheme}
|
|
314
|
+
badgeTooltip={badgeTooltip}
|
|
203
315
|
label={label}
|
|
204
316
|
labelIconName={labelIconName}
|
|
205
317
|
labelIconColor={labelIconColor}
|
|
318
|
+
hoverActions={hoverActions}
|
|
319
|
+
branchIconTooltip={branchIconTooltip}
|
|
206
320
|
level={level}
|
|
207
321
|
indexPath={indexPath}
|
|
208
322
|
titlePath={titlePath}
|