@bitrise/bitkit 13.263.0 → 13.265.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.263.0",
4
+ "version": "13.265.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+ssh://git@github.com/bitrise-io/bitkit.git"
@@ -0,0 +1,22 @@
1
+ import { createContext, ReactNode, useContext } from 'react';
2
+ import { DialogProps } from './DialogProps';
3
+
4
+ export type DialogContextValueType = {
5
+ scrollBehavior: DialogProps['scrollBehavior'];
6
+ };
7
+
8
+ const DialogContext = createContext<DialogContextValueType>({ scrollBehavior: 'outside' });
9
+
10
+ export const DialogContextProvider = ({ children, value }: { children: ReactNode; value: DialogContextValueType }) => {
11
+ return <DialogContext.Provider value={value}>{children}</DialogContext.Provider>;
12
+ };
13
+
14
+ export const useDialogContext = () => {
15
+ const context = useContext(DialogContext);
16
+
17
+ if (!context) {
18
+ throw new Error('DialogContext is not provided');
19
+ }
20
+
21
+ return context;
22
+ };
@@ -1,13 +1,11 @@
1
1
  import { useId } from 'react';
2
2
  import {
3
3
  ComponentWithAs,
4
- HTMLChakraProps,
5
4
  Modal,
6
5
  ModalCloseButton,
7
6
  ModalContent,
8
7
  ModalHeader,
9
8
  ModalOverlay,
10
- ModalProps,
11
9
  useBreakpointValue,
12
10
  usePrefersReducedMotion,
13
11
  } from '@chakra-ui/react';
@@ -15,21 +13,8 @@ import { BREAKPOINTS } from '../../types/bitkit';
15
13
  import Icon from '../Icon/Icon';
16
14
  import Text from '../Text/Text';
17
15
  import Tooltip from '../Tooltip/Tooltip';
18
-
19
- export interface DialogProps
20
- extends Omit<HTMLChakraProps<'section'>, 'scrollBehavior'>,
21
- Pick<ModalProps, 'returnFocusOnClose'> {
22
- isClosable?: boolean;
23
- isOpen: boolean;
24
- onClose(): void;
25
- onCloseComplete?: () => void;
26
- size?: 'small' | 'medium' | 'large' | 'full';
27
- scrollBehavior?: 'inside' | 'outside';
28
- title: string;
29
- trapFocus?: boolean;
30
- variant?: 'default' | 'empty';
31
- closeNotice?: string;
32
- }
16
+ import { DialogContextProvider } from './Dialog.context';
17
+ import { DialogProps } from './DialogProps';
33
18
 
34
19
  const Dialog: ComponentWithAs<'section', DialogProps> = ({
35
20
  children,
@@ -57,39 +42,42 @@ const Dialog: ComponentWithAs<'section', DialogProps> = ({
57
42
  );
58
43
 
59
44
  return (
60
- <Modal
61
- closeOnEsc={isClosable}
62
- closeOnOverlayClick={false}
63
- isOpen={isOpen}
64
- motionPreset={prefersReducedMotion ? 'none' : 'scale'}
65
- onClose={isClosable ? onClose : () => {}}
66
- onCloseComplete={onCloseComplete}
67
- returnFocusOnClose={returnFocusOnClose}
68
- scrollBehavior={scrollBehavior}
69
- size={dialogSize}
70
- trapFocus={trapFocus}
71
- >
72
- <ModalOverlay />
73
- <ModalContent aria-labelledby={variant !== 'empty' ? headerId : undefined} {...rest}>
74
- {variant !== 'empty' && (
75
- <>
76
- <ModalHeader marginRight="48">
77
- <Text as="h1" id={headerId} textStyle="comp/dialog/title">
78
- {title}
79
- </Text>
80
- </ModalHeader>
81
- {closeNotice ? (
82
- <Tooltip label={closeNotice} placement="top">
83
- {closeBtn}
84
- </Tooltip>
85
- ) : (
86
- closeBtn
87
- )}
88
- </>
89
- )}
90
- {children}
91
- </ModalContent>
92
- </Modal>
45
+ <DialogContextProvider value={{ scrollBehavior }}>
46
+ <Modal
47
+ closeOnEsc={isClosable}
48
+ closeOnOverlayClick={false}
49
+ isOpen={isOpen}
50
+ motionPreset={prefersReducedMotion ? 'none' : 'scale'}
51
+ onClose={isClosable ? onClose : () => {}}
52
+ onCloseComplete={onCloseComplete}
53
+ returnFocusOnClose={returnFocusOnClose}
54
+ scrollBehavior={scrollBehavior}
55
+ size={dialogSize}
56
+ trapFocus={trapFocus}
57
+ >
58
+ <ModalOverlay />
59
+
60
+ <ModalContent aria-labelledby={variant !== 'empty' ? headerId : undefined} {...rest}>
61
+ {variant !== 'empty' && (
62
+ <>
63
+ <ModalHeader marginRight="48">
64
+ <Text as="h1" id={headerId} textStyle="comp/dialog/title">
65
+ {title}
66
+ </Text>
67
+ </ModalHeader>
68
+ {closeNotice ? (
69
+ <Tooltip label={closeNotice} placement="top">
70
+ {closeBtn}
71
+ </Tooltip>
72
+ ) : (
73
+ closeBtn
74
+ )}
75
+ </>
76
+ )}
77
+ {children}
78
+ </ModalContent>
79
+ </Modal>
80
+ </DialogContextProvider>
93
81
  );
94
82
  };
95
83
 
@@ -1,9 +1,102 @@
1
- import { ModalBody, ModalBodyProps } from '@chakra-ui/react';
1
+ import { useRef, useState, useEffect, useCallback } from 'react';
2
+ import { ModalBody, ModalBodyProps, useModalContext } from '@chakra-ui/react';
3
+ import Box, { BoxProps } from '../Box/Box';
4
+ import Icon from '../Icon/Icon';
5
+ import { useDialogContext } from './Dialog.context';
6
+
7
+ const ScrollButton = (props: BoxProps) => {
8
+ return (
9
+ <Box
10
+ {...props}
11
+ as="button"
12
+ bg="background/primary"
13
+ borderColor="border/minimal"
14
+ borderRadius={16}
15
+ borderWidth={1}
16
+ boxShadow="large"
17
+ h={32}
18
+ w={32}
19
+ >
20
+ <Icon color="icon/tertiary" name="ArrowDown" size="16" />
21
+ </Box>
22
+ );
23
+ };
2
24
 
3
25
  export type DialogBodyProps = ModalBodyProps;
4
26
 
27
+ const ScrollableDialogBody = (props: DialogBodyProps) => {
28
+ const { isOpen } = useModalContext();
29
+
30
+ const contentRef = useRef<HTMLDivElement>(null);
31
+
32
+ const [isScrollButtonVisible, setIsScrollButtonVisible] = useState(false);
33
+
34
+ const updateScrollButtonVisibility = useCallback(() => {
35
+ const content = contentRef.current;
36
+
37
+ if (!isOpen || !content) {
38
+ setIsScrollButtonVisible(false);
39
+
40
+ return;
41
+ }
42
+
43
+ const didScrollToBottom = content.scrollTop >= content.scrollHeight - content.offsetHeight - 1;
44
+ setIsScrollButtonVisible(!didScrollToBottom);
45
+ }, []);
46
+
47
+ useEffect(() => {
48
+ const content = contentRef.current;
49
+
50
+ if (!isOpen || !content) {
51
+ return;
52
+ }
53
+
54
+ updateScrollButtonVisibility();
55
+
56
+ content.addEventListener('scroll', updateScrollButtonVisibility);
57
+ const resizeObserver = new ResizeObserver(updateScrollButtonVisibility);
58
+ resizeObserver.observe(content);
59
+
60
+ return () => {
61
+ content.removeEventListener('scroll', updateScrollButtonVisibility);
62
+ resizeObserver.unobserve(content);
63
+ resizeObserver.disconnect();
64
+ };
65
+ }, [isOpen]);
66
+
67
+ return (
68
+ <Box display="flex" flexDir="column" minH={0} position="relative">
69
+ <ModalBody ref={contentRef} {...props} />
70
+ {isScrollButtonVisible && (
71
+ <ScrollButton
72
+ alignSelf="center"
73
+ bottom={8}
74
+ flexShrink={0}
75
+ onClick={() => {
76
+ if (!contentRef.current) {
77
+ return;
78
+ }
79
+
80
+ contentRef.current.scrollTo({
81
+ top: contentRef.current.scrollHeight,
82
+ behavior: 'smooth',
83
+ });
84
+ }}
85
+ position="absolute"
86
+ />
87
+ )}
88
+ </Box>
89
+ );
90
+ };
91
+
5
92
  const DialogBody = (props: DialogBodyProps) => {
6
- return <ModalBody {...props} />;
93
+ const { scrollBehavior } = useDialogContext();
94
+
95
+ if (scrollBehavior === 'outside') {
96
+ return <ModalBody {...props} />;
97
+ }
98
+
99
+ return <ScrollableDialogBody {...props} />;
7
100
  };
8
101
 
9
102
  export default DialogBody;
@@ -0,0 +1,16 @@
1
+ import { HTMLChakraProps, ModalProps } from '@chakra-ui/react';
2
+
3
+ export interface DialogProps
4
+ extends Omit<HTMLChakraProps<'section'>, 'scrollBehavior'>,
5
+ Pick<ModalProps, 'returnFocusOnClose'> {
6
+ isClosable?: boolean;
7
+ isOpen: boolean;
8
+ onClose(): void;
9
+ onCloseComplete?: () => void;
10
+ size?: 'small' | 'medium' | 'large' | 'full';
11
+ scrollBehavior?: 'inside' | 'outside';
12
+ title: string;
13
+ trapFocus?: boolean;
14
+ variant?: 'default' | 'empty';
15
+ closeNotice?: string;
16
+ }
@@ -39,12 +39,16 @@ const baseStyle = definePartsStyle({
39
39
  },
40
40
  },
41
41
  buttonContent: {
42
+ gap: '8',
43
+ display: 'flex',
44
+ },
45
+ borderBox: {
42
46
  w: '100%',
43
47
  gap: '8',
44
48
  display: 'flex',
45
49
  alignItems: 'flex-start',
46
50
  textAlign: 'start',
47
- borderTop: '1px solid',
51
+ borderBottom: '1px solid',
48
52
  borderColor: 'border/minimal',
49
53
  },
50
54
  textBlock: {
@@ -60,31 +60,33 @@ const TreeViewNodeContent = memo(
60
60
  >
61
61
  <Box sx={{ ...styles.buttonContent }}>
62
62
  {isBranch && <Icon size="16" name={isExpanded ? 'ChevronDown' : 'ChevronRight'} sx={styles.icon} />}
63
- {iconName && (
64
- <Icon
65
- size="16"
66
- name={iconName}
67
- sx={{ ...styles.icon, ...{ color: iconColor || (isSelected ? 'icon/primary' : 'icon/secondary') } }}
68
- />
69
- )}
70
- <Box sx={styles.textBlock}>
71
- <Text fontWeight={isExpanded ? 'semibold' : 'normal'}>{title}</Text>
72
- {description && (
73
- <Text textStyle="body/sm/regular" color="text/secondary">
74
- {description}
75
- </Text>
63
+ <Box sx={styles.borderBox}>
64
+ {iconName && (
65
+ <Icon
66
+ size="16"
67
+ name={iconName}
68
+ sx={{ ...styles.icon, ...{ color: iconColor || (isSelected ? 'icon/primary' : 'icon/secondary') } }}
69
+ />
76
70
  )}
77
- </Box>
78
- {(label || labelIconName) && (
79
- <Box sx={styles.suffixBlock} color={isSelected || isExpanded ? 'text/primary' : 'text/secondary'}>
80
- {labelIconName && <Icon name={labelIconName} size="16" color={labelIconColor} />}
81
- {label && (
82
- <Text textStyle="body/md/regular" textAlign="right">
83
- {label}
71
+ <Box sx={styles.textBlock}>
72
+ <Text fontWeight={isExpanded ? 'semibold' : 'normal'}>{title}</Text>
73
+ {description && (
74
+ <Text textStyle="body/sm/regular" color="text/secondary">
75
+ {description}
84
76
  </Text>
85
77
  )}
86
78
  </Box>
87
- )}
79
+ {(label || labelIconName) && (
80
+ <Box sx={styles.suffixBlock} color={isSelected || isExpanded ? 'text/primary' : 'text/secondary'}>
81
+ {labelIconName && <Icon name={labelIconName} size="16" color={labelIconColor} />}
82
+ {label && (
83
+ <Text textStyle="body/md/regular" textAlign="right">
84
+ {label}
85
+ </Text>
86
+ )}
87
+ </Box>
88
+ )}
89
+ </Box>
88
90
  {isSelected && <Box sx={styles.selectionIndicator} />}
89
91
  </Box>
90
92
  </Box>
package/src/index.ts CHANGED
@@ -95,7 +95,7 @@ export { default as useToast } from './Components/Toast/Toast';
95
95
  export type { EmptyStateProps } from './Components/EmptyState/EmptyState';
96
96
  export { default as EmptyState } from './Components/EmptyState/EmptyState';
97
97
 
98
- export type { DialogProps } from './Components/Dialog/Dialog';
98
+ export type { DialogProps } from './Components/Dialog/DialogProps';
99
99
  export { default as Dialog } from './Components/Dialog/Dialog';
100
100
 
101
101
  export type { DialogBodyProps } from './Components/Dialog/DialogBody';