@bitrise/bitkit 13.324.0 → 13.325.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/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/FolderEmpty.tsx +14 -0
- package/src/Components/Icons/16x16/index.ts +1 -0
- package/src/Components/Icons/24x24/FolderEmpty.tsx +14 -0
- package/src/Components/Icons/24x24/index.ts +1 -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
|
@@ -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,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;
|
|
@@ -108,6 +108,7 @@ export { default as Filter } from './Filter';
|
|
|
108
108
|
export { default as Flag } from './Flag';
|
|
109
109
|
export { default as Flutter } from './Flutter';
|
|
110
110
|
export { default as Folder } from './Folder';
|
|
111
|
+
export { default as FolderEmpty } from './FolderEmpty';
|
|
111
112
|
export { default as Fullscreen } from './Fullscreen';
|
|
112
113
|
export { default as FullscreenExit } from './FullscreenExit';
|
|
113
114
|
export { default as Gauge } from './Gauge';
|
|
@@ -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;
|
|
@@ -106,6 +106,7 @@ export { default as Filter } from './Filter';
|
|
|
106
106
|
export { default as Flag } from './Flag';
|
|
107
107
|
export { default as Flutter } from './Flutter';
|
|
108
108
|
export { default as Folder } from './Folder';
|
|
109
|
+
export { default as FolderEmpty } from './FolderEmpty';
|
|
109
110
|
export { default as Fullscreen } from './Fullscreen';
|
|
110
111
|
export { default as FullscreenExit } from './FullscreenExit';
|
|
111
112
|
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}
|