@bitrise/bitkit 13.258.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bitrise/bitkit",
3
3
  "description": "Bitrise React component library",
4
- "version": "13.258.0",
4
+ "version": "13.260.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+ssh://git@github.com/bitrise-io/bitkit.git"
@@ -73,7 +73,7 @@ const Tabletheme = {
73
73
  borderTopWidth: '1px',
74
74
  height: rem(65),
75
75
  paddingX: '16',
76
- paddingY: '8',
76
+ paddingY: '12',
77
77
  textAlign: 'left',
78
78
  textStyle: 'body/md/regular',
79
79
  },
@@ -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