@bitrise/bitkit 13.170.0 → 13.172.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.170.0",
4
+ "version": "13.172.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+ssh://git@github.com/bitrise-io/bitkit.git"
@@ -38,7 +38,7 @@
38
38
  "@floating-ui/react-dom-interactions": "^0.8.1",
39
39
  "@fontsource/figtree": "^5.1.1",
40
40
  "@fontsource/source-code-pro": "^5.1.0",
41
- "framer-motion": "^11.11.9",
41
+ "framer-motion": "^11.11.10",
42
42
  "luxon": "^3.5.0",
43
43
  "react": "^18.3.1",
44
44
  "react-dom": "^18.3.1",
@@ -51,10 +51,10 @@
51
51
  "react-dom": "^18.2.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@babel/core": "^7.25.8",
55
- "@babel/preset-env": "^7.25.8",
56
- "@babel/preset-react": "^7.25.7",
57
- "@babel/preset-typescript": "^7.25.7",
54
+ "@babel/core": "^7.26.0",
55
+ "@babel/preset-env": "^7.26.0",
56
+ "@babel/preset-react": "^7.25.9",
57
+ "@babel/preset-typescript": "^7.26.0",
58
58
  "@bitrise/eslint-plugin": "^2.12.0",
59
59
  "@chakra-ui/cli": "^2.5.5",
60
60
  "@google-cloud/storage": "^7.13.0",
@@ -71,9 +71,9 @@
71
71
  "@testing-library/jest-dom": "6.6.2",
72
72
  "@testing-library/react": "16.0.1",
73
73
  "@testing-library/user-event": "^14.5.2",
74
- "@types/jest": "^29.5.13",
74
+ "@types/jest": "^29.5.14",
75
75
  "@types/luxon": "^3.4.2",
76
- "@types/react": "^18.3.11",
76
+ "@types/react": "^18.3.12",
77
77
  "@types/react-dom": "^18.3.1",
78
78
  "@typescript-eslint/eslint-plugin": "^7.18.0",
79
79
  "@typescript-eslint/parser": "^7.18.0",
@@ -1,5 +1,6 @@
1
1
  import { FormEvent, useEffect, useMemo, useState } from 'react';
2
- import { FocusLock, useMultiStyleConfig } from '@chakra-ui/react';
2
+ import { useMultiStyleConfig } from '@chakra-ui/react';
3
+ import FocusLock from 'react-focus-lock';
3
4
  import Badge from '../../Badge/Badge';
4
5
  import Box from '../../Box/Box';
5
6
  import Button from '../../Button/Button';
@@ -4,6 +4,7 @@ import { TextSizes } from '../../types/bitkit';
4
4
  export interface LinkProps extends ChakraLinkProps {
5
5
  as?: 'a' | 'button';
6
6
  colorScheme?: 'purple' | 'white' | 'gray';
7
+ isExternal?: boolean;
7
8
  isUnderlined?: boolean;
8
9
  size?: TextSizes;
9
10
  }
@@ -12,15 +13,19 @@ export interface LinkProps extends ChakraLinkProps {
12
13
  * Links are accessible elements used primarily for navigation.
13
14
  */
14
15
  const Link = forwardRef<LinkProps, 'a'>((props, ref) => {
15
- const { as = 'a', isUnderlined, ...rest } = props;
16
- const properties: ChakraLinkProps = { as, ...rest };
16
+ const { as = 'a', isExternal, isUnderlined, ...rest } = props;
17
+ const properties: ChakraLinkProps = { as };
18
+ if (isExternal) {
19
+ properties.rel = 'noreferrer noopener';
20
+ properties.target = '_blank';
21
+ }
17
22
  if (isUnderlined) {
18
23
  properties.textDecoration = 'underline';
19
24
  }
20
25
  if (as === 'button') {
21
26
  properties.type = 'button';
22
27
  }
23
- return <ChakraLink as={as} {...properties} ref={ref} />;
28
+ return <ChakraLink as={as} {...properties} {...rest} ref={ref} />;
24
29
  });
25
30
 
26
31
  export default Link;
@@ -7,7 +7,6 @@ import Text from '../Text/Text';
7
7
 
8
8
  export interface ContainedTabProps extends ChakraTabProps {
9
9
  href?: string;
10
- id: string;
11
10
  iconColor?: TypeColors | 'currentColor';
12
11
  iconName?: TypeIconName;
13
12
  isWarning?: boolean;
@@ -6,7 +6,6 @@ import Icon, { TypeIconName } from '../Icon/Icon';
6
6
  export interface TabProps extends ChakraTabProps {
7
7
  badge?: BadgeProps;
8
8
  href?: string;
9
- id: string;
10
9
  leftIconName?: TypeIconName;
11
10
  isDisabled?: boolean;
12
11
  rightIconName?: TypeIconName;
@@ -1,92 +1,16 @@
1
- import React, { isValidElement, ReactElement, useEffect, useState } from 'react';
2
1
  import { forwardRef, Tabs as ChakraTabs, TabsProps as ChakraTabsProps } from '@chakra-ui/react';
3
- import { useHistory } from '../../hooks';
4
- import { TabProps } from './Tab';
5
2
 
6
3
  export interface TabsProps extends ChakraTabsProps {
7
- defaultTab?: string;
8
- onChange?: (index: number, tabId?: string) => void;
9
- tabId?: string;
4
+ onChange?: (index: number) => void;
10
5
  variant?: 'contained' | 'line';
11
- withHistory?: boolean;
12
6
  }
13
7
 
14
- const getTabIds = (props: TabsProps): string[] => {
15
- const tabList = React.Children.toArray(props.children)[0] as ReactElement;
16
- const tabs = React.Children.toArray(tabList.props.children) as ReactElement[];
17
-
18
- return tabs.filter((item) => isValidElement(item)).map((item) => (item.props as TabProps).id || '');
19
- };
20
-
21
- const getTabIndexFromSearchParams = (tabIds: string[], tabIndex?: number) => {
22
- if (typeof window === 'undefined') {
23
- return undefined;
24
- }
25
-
26
- const searchParams = new URLSearchParams(window.location.search);
27
- const tabName = searchParams.get('tab');
28
- if (!tabName) {
29
- return undefined;
30
- }
31
-
32
- const index = tabIds.indexOf(tabName);
33
- if (index === -1) {
34
- return tabIndex;
35
- }
36
-
37
- return index;
38
- };
39
-
40
8
  /**
41
9
  * An accessible tabs component that provides keyboard interactions and ARIA attributes described in the WAI-ARIA Tabs Design Pattern.
42
10
  */
43
11
  const Tabs = forwardRef<TabsProps, 'div'>((props, ref) => {
44
- const { defaultTab = '', onChange, tabId, variant = 'line', withHistory, ...rest } = props;
45
- const { replace } = useHistory();
46
-
47
- const tabIds = getTabIds(props);
48
-
49
- let tabIndex = 0;
50
-
51
- if (defaultTab) {
52
- tabIndex = tabIds.includes(defaultTab) ? tabIds.indexOf(defaultTab) : 0;
53
- }
54
-
55
- if (tabId) {
56
- tabIndex = tabIds.includes(tabId) ? tabIds.indexOf(tabId) : 0;
57
- }
58
-
59
- const [actualIndex, setActualIndex] = useState(
60
- withHistory ? getTabIndexFromSearchParams(tabIds, tabIndex) : tabIndex,
61
- );
62
-
63
- const onTabChange = (index: number) => {
64
- const newTabId = tabIds[index];
65
-
66
- if (withHistory) {
67
- const searchParams = new URLSearchParams(window.location.search);
68
- if (newTabId) {
69
- searchParams.set('tab', newTabId);
70
- } else {
71
- searchParams.delete('tab');
72
- }
73
- replace(`?${searchParams.toString()}`);
74
- }
75
-
76
- if (onChange) {
77
- onChange(index, newTabId);
78
- }
79
-
80
- setActualIndex(index);
81
- };
82
-
83
- useEffect(() => {
84
- if (tabId) {
85
- setActualIndex(tabIds.includes(tabId) ? tabIds.indexOf(tabId) : 0);
86
- }
87
- }, [tabId]);
88
-
89
- return <ChakraTabs {...rest} ref={ref} index={actualIndex} onChange={onTabChange} variant={variant} />;
12
+ const { variant = 'line', ...rest } = props;
13
+ return <ChakraTabs {...rest} ref={ref} variant={variant} />;
90
14
  });
91
15
 
92
16
  export default Tabs;
@@ -0,0 +1,46 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+
3
+ type UseTabsProps<T> = {
4
+ defaultId?: T;
5
+ queryKey?: string;
6
+ tabIds: T[];
7
+ withHistory?: boolean;
8
+ };
9
+
10
+ const useTabs = <T extends string>(props: UseTabsProps<T>) => {
11
+ const { defaultId, queryKey = 'tab', tabIds, withHistory } = props;
12
+
13
+ const searchParams = new URLSearchParams(window.location.search);
14
+
15
+ const defaultIndex = useMemo(() => {
16
+ if (withHistory && searchParams.get(queryKey)) {
17
+ return tabIds.indexOf(searchParams.get(queryKey) as T);
18
+ }
19
+ if (defaultId) {
20
+ return tabIds.indexOf(defaultId);
21
+ }
22
+ return 0;
23
+ }, []);
24
+
25
+ const [tabIndex, setTabIndex] = useState(defaultIndex);
26
+
27
+ const setTabId = useCallback((id: T) => {
28
+ setTabIndex(tabIds.indexOf(id));
29
+ }, []);
30
+
31
+ useEffect(() => {
32
+ if (withHistory) {
33
+ searchParams.set(queryKey, tabIds[tabIndex]);
34
+ window.parent.history.replaceState(null, '', `?${decodeURIComponent(searchParams.toString())}`);
35
+ }
36
+ }, [tabIndex]);
37
+
38
+ return {
39
+ setTabId,
40
+ setTabIndex,
41
+ tabId: tabIds[tabIndex],
42
+ tabIndex,
43
+ };
44
+ };
45
+
46
+ export default useTabs;
@@ -1,3 +1,2 @@
1
- export { default as useHistory } from './useHistory';
2
1
  export { default as useResponsive } from './useResponsive';
3
- export { useDisclosure, useOutsideClick, useToken } from '@chakra-ui/react';
2
+ export { useDisclosure, useToken } from '@chakra-ui/react';
package/src/index.ts CHANGED
@@ -58,6 +58,8 @@ export { default as TabPanels } from './Components/Tabs/TabPanels';
58
58
  export type { TabPanelProps } from './Components/Tabs/TabPanel';
59
59
  export { default as TabPanel } from './Components/Tabs/TabPanel';
60
60
 
61
+ export { default as useTabs } from './Components/Tabs/useTabs';
62
+
61
63
  export type { BadgeProps } from './Components/Badge/Badge';
62
64
  export { default as Badge } from './Components/Badge/Badge';
63
65
 
@@ -1,67 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
-
3
- type UseLocation = {
4
- pathname: string;
5
- push: (url: string) => void;
6
- replace: (url: string) => void;
7
- search: string;
8
- searchParams: URLSearchParams;
9
- };
10
-
11
- const getCurrentLocation = () => ({
12
- pathname: window.location.pathname,
13
- search: window.location.search,
14
- });
15
-
16
- const listeners: Array<() => void> = [];
17
-
18
- const notify = () => {
19
- listeners.forEach((listener) => listener());
20
- };
21
-
22
- const getSearchParams = (search: string): URLSearchParams => {
23
- const searchParams = new URLSearchParams(search);
24
- return searchParams;
25
- };
26
-
27
- const useLocation = (): UseLocation => {
28
- const [{ pathname, search }, setLocation] = useState(getCurrentLocation());
29
-
30
- const handleChange = () => {
31
- setLocation(getCurrentLocation());
32
- };
33
-
34
- const push = (url: string) => {
35
- window.history.pushState(null, '', url);
36
- notify();
37
- };
38
-
39
- const replace = (url: string) => {
40
- window.history.replaceState(null, '', url);
41
- notify();
42
- };
43
-
44
- useEffect(() => {
45
- window.addEventListener('popstate', handleChange);
46
- return () => {
47
- window.removeEventListener('popstate', handleChange);
48
- };
49
- }, []);
50
-
51
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
52
- // @ts-ignore
53
- useEffect(() => {
54
- listeners.push(handleChange);
55
- return () => listeners.splice(listeners.indexOf(handleChange), 1);
56
- }, []);
57
-
58
- return {
59
- pathname,
60
- push,
61
- replace,
62
- search,
63
- searchParams: getSearchParams(search),
64
- };
65
- };
66
-
67
- export default useLocation;