@bitrise/bitkit 13.44.0 → 13.46.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.44.0",
4
+ "version": "13.46.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+ssh://git@github.com/bitrise-io/bitkit.git"
@@ -1,3 +1,4 @@
1
+ import { MouseEvent, useEffect } from 'react';
1
2
  import { Button, forwardRef, TabProps as ChakraTabProps, useTab, useTabsStyles } from '@chakra-ui/react';
2
3
  import { TypeColors } from '../../types/bitkit';
3
4
  import Box from '../Box/Box';
@@ -5,6 +6,7 @@ import Icon, { TypeIconName } from '../Icon/Icon';
5
6
  import Text from '../Text/Text';
6
7
 
7
8
  export interface ContainedTabProps extends ChakraTabProps {
9
+ href?: string;
8
10
  id: string;
9
11
  iconColor?: TypeColors | 'currentColor';
10
12
  iconName?: TypeIconName;
@@ -13,11 +15,36 @@ export interface ContainedTabProps extends ChakraTabProps {
13
15
  }
14
16
 
15
17
  const ContainedTab = forwardRef<ContainedTabProps, 'button'>((props, ref) => {
16
- const { iconColor = 'currentColor', iconName, isWarning, secondaryText, ...rest } = props;
17
- const tabProps = useTab({ ...rest, ref });
18
+ const { href, iconColor = 'currentColor', iconName, isDisabled, isWarning, secondaryText, ...rest } = props;
19
+
18
20
  const styles = useTabsStyles();
21
+
22
+ const tabProps = useTab({
23
+ href,
24
+ isDisabled,
25
+ onClick: (e: MouseEvent<HTMLButtonElement>) => {
26
+ if (!e.metaKey) {
27
+ e.preventDefault();
28
+ }
29
+ },
30
+ ref,
31
+ ...rest,
32
+ });
33
+
34
+ useEffect(() => {
35
+ if (tabProps['aria-selected']) {
36
+ document.getElementById(tabProps.id)?.scrollIntoView({ behavior: 'smooth', inline: 'center' });
37
+ }
38
+ }, [tabProps]);
39
+
19
40
  return (
20
- <Button __css={styles.containedTab} {...tabProps}>
41
+ <Button
42
+ as={href ? 'a' : 'button'}
43
+ isDisabled={isDisabled}
44
+ __css={styles.containedTab}
45
+ {...tabProps}
46
+ type={href ? undefined : 'button'}
47
+ >
21
48
  <Box display="flex" gap="16" justifyContent="space-between">
22
49
  <Text as="span" hasEllipsis textStyle="comp/tabs/contained">
23
50
  {tabProps.children}
@@ -1,9 +1,11 @@
1
+ import { MouseEvent, useEffect } from 'react';
1
2
  import { Button, forwardRef, TabProps as ChakraTabProps, useTab, useTabsStyles } from '@chakra-ui/react';
2
3
  import Badge, { BadgeProps } from '../Badge/Badge';
3
4
  import Icon, { TypeIconName } from '../Icon/Icon';
4
5
 
5
6
  export interface TabProps extends ChakraTabProps {
6
7
  badge?: BadgeProps;
8
+ href?: string;
7
9
  id: string;
8
10
  leftIconName?: TypeIconName;
9
11
  isDisabled?: boolean;
@@ -11,11 +13,36 @@ export interface TabProps extends ChakraTabProps {
11
13
  }
12
14
 
13
15
  const Tab = forwardRef<TabProps, 'button'>((props, ref) => {
14
- const { badge, leftIconName, rightIconName, ...rest } = props;
15
- const tabProps = useTab({ ...rest, ref });
16
+ const { badge, href, isDisabled, leftIconName, rightIconName, ...rest } = props;
17
+
16
18
  const styles = useTabsStyles();
19
+
20
+ const tabProps = useTab({
21
+ href,
22
+ isDisabled,
23
+ onClick: (e: MouseEvent<HTMLButtonElement>) => {
24
+ if (!e.metaKey) {
25
+ e.preventDefault();
26
+ }
27
+ },
28
+ ref,
29
+ ...rest,
30
+ });
31
+
32
+ useEffect(() => {
33
+ if (tabProps['aria-selected']) {
34
+ document.getElementById(tabProps.id)?.scrollIntoView({ behavior: 'smooth', inline: 'center' });
35
+ }
36
+ }, [tabProps]);
37
+
17
38
  return (
18
- <Button __css={styles.lineTab} {...tabProps}>
39
+ <Button
40
+ as={href ? 'a' : 'button'}
41
+ isDisabled={isDisabled}
42
+ __css={styles.lineTab}
43
+ {...tabProps}
44
+ type={href ? undefined : 'button'}
45
+ >
19
46
  {leftIconName && <Icon name={leftIconName} />}
20
47
  <span>{tabProps.children}</span>
21
48
  {badge?.children && <Badge {...badge} />}
@@ -1,7 +1,100 @@
1
- import { forwardRef, TabList as ChakraTabList, TabListProps } from '@chakra-ui/react';
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { forwardRef, TabList as ChakraTabList, TabListProps, useTabsStyles } from '@chakra-ui/react';
3
+ import IconButton from '../IconButton/IconButton';
4
+ import Box from '../Box/Box';
2
5
 
3
6
  const TabList = forwardRef<TabListProps, 'div'>((props, ref) => {
4
- return <ChakraTabList {...props} ref={ref} />;
7
+ const styles: Record<string, any> = useTabsStyles();
8
+
9
+ const tabListRef = useRef<HTMLDivElement>(null);
10
+ const [isContentWider, setIsContentWider] = useState(false);
11
+ const [isLeftArrowDisabled, setIsLeftrrowDisabled] = useState(true);
12
+ const [isRightArrowDisabled, setIsRightArrowDisabled] = useState(true);
13
+
14
+ const updateArrowButtonStates = () => {
15
+ const tabList = tabListRef.current;
16
+ if (!tabList) {
17
+ return;
18
+ }
19
+
20
+ setIsLeftrrowDisabled(Math.floor(tabList.scrollLeft) === 0);
21
+ setIsRightArrowDisabled(Math.ceil(tabList.scrollLeft) + tabList.clientWidth >= tabList.scrollWidth);
22
+ };
23
+
24
+ useEffect(() => {
25
+ const handleResize = () => {
26
+ const tabList = tabListRef.current;
27
+ if (!tabList) {
28
+ return;
29
+ }
30
+
31
+ const viewPortWidth = document.documentElement.clientWidth;
32
+ const tabListContentWidth = tabList.scrollWidth;
33
+
34
+ setIsContentWider(tabListContentWidth > viewPortWidth);
35
+ updateArrowButtonStates();
36
+ };
37
+
38
+ window.addEventListener('resize', handleResize);
39
+ handleResize();
40
+
41
+ const tabList = tabListRef.current;
42
+ if (tabList) {
43
+ tabList.addEventListener('scroll', updateArrowButtonStates);
44
+ }
45
+
46
+ return () => {
47
+ window.removeEventListener('resize', handleResize);
48
+ if (tabList) {
49
+ tabList.removeEventListener('scroll', updateArrowButtonStates);
50
+ }
51
+ };
52
+ }, [tabListRef]);
53
+
54
+ const handleScroll = (direction: 'left' | 'right') => {
55
+ const tabList = tabListRef.current;
56
+ if (!tabList) {
57
+ return;
58
+ }
59
+
60
+ const scrollAmount = direction === 'left' ? -(tabList.offsetWidth / 8) : tabList.offsetWidth / 8;
61
+ const newPosition = tabList.scrollLeft + scrollAmount;
62
+
63
+ tabList.scrollTo({
64
+ left: newPosition,
65
+ behavior: 'smooth',
66
+ });
67
+
68
+ updateArrowButtonStates();
69
+ };
70
+
71
+ return (
72
+ <Box display="flex" alignItems="center" justifyContent="flex-start" ref={ref}>
73
+ {isContentWider && !isLeftArrowDisabled && (
74
+ <IconButton
75
+ __css={styles.arrowButton}
76
+ iconName="ChevronLeft"
77
+ onClick={() => handleScroll('left')}
78
+ aria-label="Scroll back"
79
+ isTooltipDisabled
80
+ left="0"
81
+ _after={styles.arrowButtonGradient('left')}
82
+ />
83
+ )}
84
+ <ChakraTabList {...props} ref={tabListRef} />
85
+ {isContentWider && !isRightArrowDisabled && (
86
+ <IconButton
87
+ __css={styles.arrowButton}
88
+ iconName="ChevronRight"
89
+ onClick={() => handleScroll('right')}
90
+ aria-label="Scroll forward"
91
+ isTooltipDisabled
92
+ right="0"
93
+ _after={styles.arrowButtonGradient('right')}
94
+ />
95
+ )}
96
+ </Box>
97
+ );
5
98
  });
6
99
 
7
100
  export type { TabListProps };
@@ -1,16 +1,30 @@
1
1
  import { defineStyle, defineStyleConfig, SystemStyleObject } from '@chakra-ui/styled-system';
2
+ import { rem } from '../../utils/utils';
3
+
4
+ const getGradient = (position: 'left' | 'right', variant: 'contained' | 'line') => {
5
+ const style: SystemStyleObject = {
6
+ content: `""`,
7
+ position: 'absolute',
8
+ width: '8',
9
+ top: '0',
10
+ bottom: '0',
11
+ bgGradient: `linear(${position === 'left' ? 'to-r' : 'to-l'}, ${
12
+ variant === 'line' ? 'background/primary' : 'background/tertiary'
13
+ }, transparent)`,
14
+ };
15
+
16
+ if (position === 'left') {
17
+ style.right = '-8px';
18
+ } else {
19
+ style.left = '-8px';
20
+ }
21
+
22
+ return style;
23
+ };
2
24
 
3
25
  const baseStyle = defineStyle(
4
26
  ({ variant }): SystemStyleObject => ({
5
27
  containedTab: {
6
- _disabled: {
7
- _hover: {
8
- backgroundColor: 'neutral.90',
9
- },
10
- backgroundColor: 'neutral.90',
11
- color: 'neutral.60',
12
- cursor: 'not-allowed',
13
- },
14
28
  _first: {
15
29
  _focusVisible: {
16
30
  boxShadow: 'inset 0 2px var(--colors-purple-50), inset 0 0 0 3px var(--colors-purple-70)',
@@ -23,6 +37,11 @@ const baseStyle = defineStyle(
23
37
  _hover: {
24
38
  backgroundColor: 'background/active',
25
39
  },
40
+ _disabled: {
41
+ backgroundColor: 'neutral.90',
42
+ color: 'neutral.70',
43
+ cursor: 'not-allowed',
44
+ },
26
45
  _selected: {
27
46
  '+ [role="tab"]': {
28
47
  boxShadow: 'none',
@@ -49,22 +68,20 @@ const baseStyle = defineStyle(
49
68
  whiteSpace: 'nowrap',
50
69
  },
51
70
  lineTab: {
71
+ _hover: {
72
+ backgroundColor: 'background/secondary',
73
+ },
52
74
  _active: {
53
75
  backgroundColor: 'neutral.93',
54
76
  },
55
77
  _disabled: {
56
- _hover: {
57
- background: 'transparent',
58
- },
78
+ background: 'transparent',
59
79
  color: 'neutral.70',
60
80
  cursor: 'not-allowed',
61
81
  },
62
82
  _focusVisible: {
63
83
  boxShadow: 'inset 0 -2px var(--colors-purple-50), inset 0 0 0 3px var(--colors-purple-70)',
64
84
  },
65
- _hover: {
66
- backgroundColor: 'neutral.95',
67
- },
68
85
  _selected: {
69
86
  _hover: {
70
87
  backgroundColor: 'transparent',
@@ -86,14 +103,31 @@ const baseStyle = defineStyle(
86
103
  borderBottom: variant === 'line' ? '1px solid' : undefined,
87
104
  borderBottomColor: variant === 'line' ? 'separator.primary' : undefined,
88
105
  maxWidth: '100%',
89
- overflow: 'hidden',
90
- paddingLeft: variant === 'contained' ? '16' : undefined,
106
+ overflowY: 'hidden',
107
+ overflowX: 'scroll',
108
+ scrollbarWidth: 'none',
109
+ '::-webkit-scrollbar': {
110
+ width: 0,
111
+ },
91
112
  },
92
113
  tabpanel: {
93
114
  _focusVisible: {
94
115
  boxShadow: 'none',
95
116
  },
96
117
  },
118
+ arrowButton: {
119
+ width: variant === 'line' ? '48' : rem(68),
120
+ height: variant === 'line' ? '48' : rem(68),
121
+ borderRadius: 0,
122
+ backgroundColor: variant === 'line' ? 'background/primary' : 'background/tertiary',
123
+ flexShrink: 0,
124
+ color: 'icon/primary',
125
+ paddingY: '12',
126
+ paddingX: '16',
127
+ position: 'absolute',
128
+ top: '0',
129
+ },
130
+ arrowButtonGradient: ((position: 'left' | 'right') => getGradient(position, variant)) as any,
97
131
  }),
98
132
  );
99
133