@bitrise/bitkit 13.259.0 → 13.260.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/TreeView/TreeView.context.tsx +43 -0
- package/src/Components/TreeView/TreeView.theme.ts +79 -0
- package/src/Components/TreeView/TreeView.tsx +74 -0
- package/src/Components/TreeView/TreeView.types.ts +18 -0
- package/src/Components/TreeView/TreeViewGroup.tsx +20 -0
- package/src/Components/TreeView/TreeViewNode.tsx +165 -0
- package/src/Components/components.theme.ts +2 -0
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createContext, PropsWithChildren, useCallback, useContext, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
type TreeViewState = {
|
|
4
|
+
selectedId?: string;
|
|
5
|
+
selectNode: (id: string) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const TreeViewContext = createContext<TreeViewState>({
|
|
9
|
+
selectedId: undefined,
|
|
10
|
+
selectNode: () => {},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
type TreeViewContenxtProviderProps = PropsWithChildren<{
|
|
14
|
+
defaultSelectedId?: string;
|
|
15
|
+
onNodeSelect?: (id: string) => void;
|
|
16
|
+
}>;
|
|
17
|
+
export const TreeViewContextProvider = ({
|
|
18
|
+
defaultSelectedId,
|
|
19
|
+
onNodeSelect,
|
|
20
|
+
children,
|
|
21
|
+
}: TreeViewContenxtProviderProps) => {
|
|
22
|
+
const [selectedId, setSelectedId] = useState(defaultSelectedId);
|
|
23
|
+
|
|
24
|
+
const selectNode = useCallback(
|
|
25
|
+
(id: string) => {
|
|
26
|
+
setSelectedId((prevId) => (prevId === id ? undefined : id));
|
|
27
|
+
onNodeSelect?.(id);
|
|
28
|
+
},
|
|
29
|
+
[onNodeSelect],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const contextValue = useMemo(() => ({ selectedId, selectNode }), [selectedId, selectNode]);
|
|
33
|
+
|
|
34
|
+
return <TreeViewContext.Provider value={contextValue}>{children}</TreeViewContext.Provider>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const useTreeViewContext = () => {
|
|
38
|
+
const context = useContext(TreeViewContext);
|
|
39
|
+
if (!context) {
|
|
40
|
+
throw new Error('useTreeViewContext must be used within a TreeViewProvider');
|
|
41
|
+
}
|
|
42
|
+
return context;
|
|
43
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
|
|
2
|
+
|
|
3
|
+
const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers([
|
|
4
|
+
'icon',
|
|
5
|
+
'button',
|
|
6
|
+
'buttonContent',
|
|
7
|
+
'textBlock',
|
|
8
|
+
'suffixBlock',
|
|
9
|
+
'selectionIndicator',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const baseStyle = definePartsStyle({
|
|
13
|
+
icon: {
|
|
14
|
+
marginTop: '10px',
|
|
15
|
+
marginBottom: '2px',
|
|
16
|
+
color: 'icon/primary',
|
|
17
|
+
},
|
|
18
|
+
button: {
|
|
19
|
+
pos: 'relative',
|
|
20
|
+
py: '0',
|
|
21
|
+
px: '16',
|
|
22
|
+
pl: 'calc(16px + 24px * var(--level))',
|
|
23
|
+
w: '100%',
|
|
24
|
+
_hover: {
|
|
25
|
+
bgColor: 'background/secondary',
|
|
26
|
+
},
|
|
27
|
+
_active: {
|
|
28
|
+
bgColor: 'background/tertiary',
|
|
29
|
+
},
|
|
30
|
+
_selected: {
|
|
31
|
+
tabIndex: 0,
|
|
32
|
+
bgColor: 'background/selected',
|
|
33
|
+
_hover: {
|
|
34
|
+
bgColor: 'background/selected-hover',
|
|
35
|
+
},
|
|
36
|
+
_active: {
|
|
37
|
+
bgColor: 'background/selected-hover',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
buttonContent: {
|
|
42
|
+
w: '100%',
|
|
43
|
+
display: 'flex',
|
|
44
|
+
alignItems: 'flex-start',
|
|
45
|
+
gap: '8',
|
|
46
|
+
borderTop: '1px solid',
|
|
47
|
+
borderColor: 'border/minimal',
|
|
48
|
+
},
|
|
49
|
+
textBlock: {
|
|
50
|
+
display: 'flex',
|
|
51
|
+
flexDir: 'column',
|
|
52
|
+
alignItems: 'flex-start',
|
|
53
|
+
py: '8',
|
|
54
|
+
gap: '4',
|
|
55
|
+
flex: '1',
|
|
56
|
+
},
|
|
57
|
+
suffixBlock: {
|
|
58
|
+
display: 'flex',
|
|
59
|
+
alignItems: 'center',
|
|
60
|
+
justifyContent: 'flex-end',
|
|
61
|
+
paddingTop: '8',
|
|
62
|
+
paddingLeft: '16',
|
|
63
|
+
gap: '8',
|
|
64
|
+
},
|
|
65
|
+
selectionIndicator: {
|
|
66
|
+
width: '3px',
|
|
67
|
+
height: '100%',
|
|
68
|
+
position: 'absolute',
|
|
69
|
+
right: '0',
|
|
70
|
+
bgColor: 'border/selected',
|
|
71
|
+
borderLeftRadius: '2',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const TreeViewTheme = defineMultiStyleConfig({
|
|
76
|
+
baseStyle,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export default TreeViewTheme;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { TreeViewContextProvider } from './TreeView.context';
|
|
3
|
+
import TreeViewGroup from './TreeViewGroup';
|
|
4
|
+
import TreeViewNode, { TreeViewNodeProps } from './TreeViewNode';
|
|
5
|
+
import { TreeViewData } from './TreeView.types';
|
|
6
|
+
|
|
7
|
+
const TreeViewLeaf = (props: Omit<TreeViewNodeProps, 'children'>) => <TreeViewNode type="leaf" {...props} />;
|
|
8
|
+
const TreeViewBranch = (props: Omit<TreeViewNodeProps, 'type'>) => <TreeViewNode type="branch" {...props} />;
|
|
9
|
+
|
|
10
|
+
const renderTreeNodes = (nodes: TreeViewData, level: number) => {
|
|
11
|
+
return nodes.map((node) => {
|
|
12
|
+
const { id, title, description, iconName, iconColor, suffixText, suffixIconName, suffixIconColor, children, type } =
|
|
13
|
+
node;
|
|
14
|
+
|
|
15
|
+
const nodeType = type || (children && children.length > 0 ? 'branch' : 'leaf');
|
|
16
|
+
const isBranch = nodeType === 'branch';
|
|
17
|
+
|
|
18
|
+
if (isBranch) {
|
|
19
|
+
return (
|
|
20
|
+
<TreeViewBranch
|
|
21
|
+
key={id}
|
|
22
|
+
id={id}
|
|
23
|
+
level={level}
|
|
24
|
+
title={title}
|
|
25
|
+
description={description}
|
|
26
|
+
iconName={iconName}
|
|
27
|
+
iconColor={iconColor}
|
|
28
|
+
suffixText={suffixText}
|
|
29
|
+
suffixIconName={suffixIconName}
|
|
30
|
+
suffixIconColor={suffixIconColor}
|
|
31
|
+
>
|
|
32
|
+
{children && renderTreeNodes(children, level + 1)}
|
|
33
|
+
</TreeViewBranch>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<TreeViewLeaf
|
|
39
|
+
key={id}
|
|
40
|
+
id={id}
|
|
41
|
+
level={level}
|
|
42
|
+
title={title}
|
|
43
|
+
description={description}
|
|
44
|
+
iconName={iconName}
|
|
45
|
+
iconColor={iconColor}
|
|
46
|
+
suffixText={suffixText}
|
|
47
|
+
suffixIconName={suffixIconName}
|
|
48
|
+
suffixIconColor={suffixIconColor}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type TreeViewProps = {
|
|
55
|
+
label: string;
|
|
56
|
+
data?: TreeViewData;
|
|
57
|
+
defaultSelectedId?: string;
|
|
58
|
+
onNodeSelect?: (id: string) => void;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const TreeView = ({ children, label, data, defaultSelectedId, onNodeSelect }: PropsWithChildren<TreeViewProps>) => {
|
|
62
|
+
return (
|
|
63
|
+
<TreeViewContextProvider defaultSelectedId={defaultSelectedId} onNodeSelect={onNodeSelect}>
|
|
64
|
+
<TreeViewGroup level={1} label={label} role="tree">
|
|
65
|
+
{data ? renderTreeNodes(data, 1) : children}
|
|
66
|
+
</TreeViewGroup>
|
|
67
|
+
</TreeViewContextProvider>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
TreeView.Leaf = TreeViewLeaf;
|
|
72
|
+
TreeView.Branch = TreeViewBranch;
|
|
73
|
+
|
|
74
|
+
export default TreeView;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TypeIconName } from '../Icon/Icon';
|
|
2
|
+
|
|
3
|
+
export type TreeNodeData = {
|
|
4
|
+
id: string;
|
|
5
|
+
type?: 'branch' | 'leaf';
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
iconName?: TypeIconName;
|
|
9
|
+
iconColor?: string;
|
|
10
|
+
suffixText?: string;
|
|
11
|
+
suffixIconName?: TypeIconName;
|
|
12
|
+
suffixIconColor?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
expanded?: boolean;
|
|
15
|
+
children?: TreeNodeData[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type TreeViewData = TreeNodeData[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Children, cloneElement, isValidElement, memo, PropsWithChildren } from 'react';
|
|
2
|
+
import Box from '../Box/Box';
|
|
3
|
+
|
|
4
|
+
type TreeViewGroupProps = PropsWithChildren<{
|
|
5
|
+
level: number;
|
|
6
|
+
label?: string;
|
|
7
|
+
role?: string;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
const TreeViewGroup = ({ label, role, level, children }: TreeViewGroupProps) => {
|
|
11
|
+
return (
|
|
12
|
+
<Box as="ul" role={role} aria-label={label} listStyleType="none" textStyle="body/md/regular">
|
|
13
|
+
{Children.map(children, (child) =>
|
|
14
|
+
isValidElement(child) ? cloneElement(child, { level } as { level: number }) : child,
|
|
15
|
+
)}
|
|
16
|
+
</Box>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default memo(TreeViewGroup);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { memo, PropsWithChildren, useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { useMultiStyleConfig } from '@chakra-ui/react';
|
|
3
|
+
import Icon from '../Icon/Icon';
|
|
4
|
+
import Box from '../Box/Box';
|
|
5
|
+
import Text from '../Text/Text';
|
|
6
|
+
import Collapse from '../Collapse/Collapse';
|
|
7
|
+
import TreeViewGroup from './TreeViewGroup';
|
|
8
|
+
import { useTreeViewContext } from './TreeView.context';
|
|
9
|
+
import { TreeNodeData } from './TreeView.types';
|
|
10
|
+
|
|
11
|
+
export type TreeViewNodeProps = PropsWithChildren<
|
|
12
|
+
Omit<TreeNodeData, 'disabled' | 'expanded' | 'children'> & {
|
|
13
|
+
level?: number;
|
|
14
|
+
defaultDisabled?: boolean;
|
|
15
|
+
defaultExpanded?: boolean;
|
|
16
|
+
}
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
type TreeViewNodeContentProps = Omit<TreeViewNodeProps, 'defaultDisabled' | 'defaultExpanded' | 'level'> & {
|
|
20
|
+
level: number;
|
|
21
|
+
isBranch?: boolean;
|
|
22
|
+
isSelected?: boolean;
|
|
23
|
+
isDisabled?: boolean;
|
|
24
|
+
isExpanded?: boolean;
|
|
25
|
+
onClick: VoidFunction;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const TreeViewNodeContent = memo(
|
|
29
|
+
({
|
|
30
|
+
id,
|
|
31
|
+
children,
|
|
32
|
+
level,
|
|
33
|
+
iconName,
|
|
34
|
+
iconColor,
|
|
35
|
+
title,
|
|
36
|
+
description,
|
|
37
|
+
suffixText,
|
|
38
|
+
suffixIconName,
|
|
39
|
+
suffixIconColor,
|
|
40
|
+
isBranch,
|
|
41
|
+
isDisabled,
|
|
42
|
+
isExpanded,
|
|
43
|
+
isSelected,
|
|
44
|
+
onClick,
|
|
45
|
+
}: TreeViewNodeContentProps) => {
|
|
46
|
+
const styles = useMultiStyleConfig('TreeView', {});
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Box
|
|
50
|
+
as="li"
|
|
51
|
+
id={id}
|
|
52
|
+
role="treeitem"
|
|
53
|
+
aria-level={level}
|
|
54
|
+
aria-disabled={isDisabled}
|
|
55
|
+
aria-selected={isSelected}
|
|
56
|
+
{...(isBranch ? { 'aria-expanded': isExpanded } : {})}
|
|
57
|
+
>
|
|
58
|
+
<Box
|
|
59
|
+
as="button"
|
|
60
|
+
tabIndex={isSelected ? 0 : -1}
|
|
61
|
+
data-selected={isSelected ? '' : undefined}
|
|
62
|
+
sx={{ '--level': `${level - 1}`, ...styles.button }}
|
|
63
|
+
onClick={onClick}
|
|
64
|
+
>
|
|
65
|
+
<Box sx={{ ...styles.buttonContent }}>
|
|
66
|
+
{isBranch && <Icon size="16" name={isExpanded ? 'ChevronDown' : 'ChevronRight'} sx={styles.icon} />}
|
|
67
|
+
{iconName && (
|
|
68
|
+
<Icon
|
|
69
|
+
size="16"
|
|
70
|
+
name={iconName}
|
|
71
|
+
sx={{ ...styles.icon, ...{ color: iconColor || (isSelected ? 'icon/primary' : 'icon/secondary') } }}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
<Box sx={styles.textBlock}>
|
|
75
|
+
<Text fontWeight={isExpanded ? 'semibold' : 'normal'}>{title}</Text>
|
|
76
|
+
{description && (
|
|
77
|
+
<Text textStyle="body/sm/regular" color="text/secondary">
|
|
78
|
+
{description}
|
|
79
|
+
</Text>
|
|
80
|
+
)}
|
|
81
|
+
</Box>
|
|
82
|
+
{(suffixText || suffixIconName) && (
|
|
83
|
+
<Box sx={styles.suffixBlock} color={isSelected || isExpanded ? 'text/primary' : 'text/secondary'}>
|
|
84
|
+
{suffixIconName && <Icon name={suffixIconName} size="16" color={suffixIconColor} />}
|
|
85
|
+
{suffixText && (
|
|
86
|
+
<Text textStyle="body/md/regular" textAlign="right">
|
|
87
|
+
{suffixText}
|
|
88
|
+
</Text>
|
|
89
|
+
)}
|
|
90
|
+
</Box>
|
|
91
|
+
)}
|
|
92
|
+
{isSelected && <Box sx={styles.selectionIndicator} />}
|
|
93
|
+
</Box>
|
|
94
|
+
</Box>
|
|
95
|
+
{isBranch && children && (
|
|
96
|
+
<Collapse in={isExpanded} unmountOnExit>
|
|
97
|
+
<TreeViewGroup level={level + 1} role="group">
|
|
98
|
+
{children}
|
|
99
|
+
</TreeViewGroup>
|
|
100
|
+
</Collapse>
|
|
101
|
+
)}
|
|
102
|
+
</Box>
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const TreeViewNode = ({
|
|
108
|
+
id,
|
|
109
|
+
type,
|
|
110
|
+
level = 1,
|
|
111
|
+
iconName,
|
|
112
|
+
iconColor,
|
|
113
|
+
title,
|
|
114
|
+
description,
|
|
115
|
+
suffixText,
|
|
116
|
+
suffixIconName,
|
|
117
|
+
suffixIconColor,
|
|
118
|
+
defaultDisabled,
|
|
119
|
+
defaultExpanded,
|
|
120
|
+
children,
|
|
121
|
+
}: TreeViewNodeProps) => {
|
|
122
|
+
const { selectedId, selectNode } = useTreeViewContext();
|
|
123
|
+
|
|
124
|
+
const isBranch = (type || (children ? 'branch' : 'leaf')) === 'branch';
|
|
125
|
+
const [isDisabled] = useState(defaultDisabled || false);
|
|
126
|
+
const [isExpanded, setIsExpanded] = useState(defaultExpanded || false);
|
|
127
|
+
const isSelected = useMemo(() => id === selectedId, [id, selectedId]);
|
|
128
|
+
|
|
129
|
+
const handleClick = useCallback(() => {
|
|
130
|
+
if (isDisabled) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!isSelected) {
|
|
135
|
+
selectNode(id);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (isBranch) {
|
|
139
|
+
setIsExpanded((prev) => !prev);
|
|
140
|
+
}
|
|
141
|
+
}, [id, isDisabled, isSelected, selectNode, isBranch]);
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<TreeViewNodeContent
|
|
145
|
+
id={id}
|
|
146
|
+
level={level}
|
|
147
|
+
iconName={iconName}
|
|
148
|
+
iconColor={iconColor}
|
|
149
|
+
title={title}
|
|
150
|
+
description={description}
|
|
151
|
+
suffixText={suffixText}
|
|
152
|
+
suffixIconName={suffixIconName}
|
|
153
|
+
suffixIconColor={suffixIconColor}
|
|
154
|
+
isBranch={isBranch}
|
|
155
|
+
isDisabled={isDisabled}
|
|
156
|
+
isExpanded={isExpanded}
|
|
157
|
+
isSelected={isSelected}
|
|
158
|
+
onClick={handleClick}
|
|
159
|
+
>
|
|
160
|
+
{children}
|
|
161
|
+
</TreeViewNodeContent>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export default memo(TreeViewNode);
|
|
@@ -51,6 +51,7 @@ import TagsInput from './Form/TagsInput/TagsInput.theme';
|
|
|
51
51
|
import DraggableCard from './DraggableCard/DraggableCard.theme';
|
|
52
52
|
import SelectableTag from './SelectableTag/SelectableTag.theme';
|
|
53
53
|
import DataWidget from './DataWidget/DataWidget.theme';
|
|
54
|
+
import TreeView from './TreeView/TreeView.theme';
|
|
54
55
|
|
|
55
56
|
const components = {
|
|
56
57
|
Accordion,
|
|
@@ -105,6 +106,7 @@ const components = {
|
|
|
105
106
|
Textarea,
|
|
106
107
|
Toggletip,
|
|
107
108
|
Tooltip,
|
|
109
|
+
TreeView,
|
|
108
110
|
SettingsCard,
|
|
109
111
|
};
|
|
110
112
|
|