@kaizen/components 1.64.14 → 1.65.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/dist/cjs/__future__/Tabs/Tabs.cjs +23 -0
- package/dist/cjs/__future__/Tabs/subcomponents/Tab/Tab.cjs +39 -0
- package/dist/cjs/__future__/Tabs/subcomponents/Tab/Tab.module.css.cjs +7 -0
- package/dist/cjs/__future__/Tabs/subcomponents/TabList/TabList.cjs +31 -0
- package/dist/cjs/__future__/Tabs/subcomponents/TabList/TabList.module.css.cjs +7 -0
- package/dist/cjs/__future__/Tabs/subcomponents/TabPanel/TabPanel.cjs +24 -0
- package/dist/cjs/future.cjs +8 -0
- package/dist/esm/__future__/Tabs/Tabs.mjs +15 -0
- package/dist/esm/__future__/Tabs/subcomponents/Tab/Tab.mjs +30 -0
- package/dist/esm/__future__/Tabs/subcomponents/Tab/Tab.module.css.mjs +5 -0
- package/dist/esm/__future__/Tabs/subcomponents/TabList/TabList.mjs +22 -0
- package/dist/esm/__future__/Tabs/subcomponents/TabList/TabList.module.css.mjs +5 -0
- package/dist/esm/__future__/Tabs/subcomponents/TabPanel/TabPanel.mjs +16 -0
- package/dist/esm/future.mjs +4 -0
- package/dist/styles.css +188 -71
- package/dist/types/Tabs/subcomponents/index.d.ts +0 -1
- package/dist/types/__future__/Tabs/Tabs.d.ts +11 -0
- package/dist/types/__future__/Tabs/index.d.ts +2 -0
- package/dist/types/__future__/Tabs/subcomponents/Tab/Tab.d.ts +12 -0
- package/dist/types/__future__/Tabs/subcomponents/Tab/index.d.ts +1 -0
- package/dist/types/__future__/Tabs/subcomponents/TabList/TabList.d.ts +17 -0
- package/dist/types/__future__/Tabs/subcomponents/TabList/index.d.ts +1 -0
- package/dist/types/__future__/Tabs/subcomponents/TabPanel/TabPanel.d.ts +6 -0
- package/dist/types/__future__/Tabs/subcomponents/TabPanel/index.d.ts +1 -0
- package/dist/types/__future__/Tabs/subcomponents/index.d.ts +3 -0
- package/dist/types/__future__/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/Tabs/subcomponents/index.ts +0 -1
- package/src/__future__/Tabs/Tabs.tsx +18 -0
- package/src/__future__/Tabs/_docs/Tabs--api-specification.mdx +43 -0
- package/src/__future__/Tabs/_docs/Tabs--migration-guide.mdx +93 -0
- package/src/__future__/Tabs/_docs/Tabs.stories.tsx +74 -0
- package/src/__future__/Tabs/index.ts +2 -0
- package/src/__future__/Tabs/subcomponents/Tab/Tab.module.css +94 -0
- package/src/__future__/Tabs/subcomponents/Tab/Tab.tsx +58 -0
- package/src/__future__/Tabs/subcomponents/Tab/index.ts +1 -0
- package/src/__future__/Tabs/subcomponents/TabList/TabList.module.css +8 -0
- package/src/__future__/Tabs/subcomponents/TabList/TabList.tsx +45 -0
- package/src/__future__/Tabs/subcomponents/TabList/index.ts +1 -0
- package/src/__future__/Tabs/subcomponents/TabPanel/TabPanel.module.css +12 -0
- package/src/__future__/Tabs/subcomponents/TabPanel/TabPanel.tsx +20 -0
- package/src/__future__/Tabs/subcomponents/TabPanel/index.ts +1 -0
- package/src/__future__/Tabs/subcomponents/index.ts +3 -0
- package/src/__future__/index.ts +1 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Meta } from "@storybook/blocks"
|
|
2
|
+
|
|
3
|
+
<Meta title="Components/Tabs/Tabs (Future)/Migration Guide" />
|
|
4
|
+
|
|
5
|
+
# Future Tabs migration guide
|
|
6
|
+
|
|
7
|
+
A brief guide on how and why to migrate from Kaizen's current `Tabs` to the `future` release.
|
|
8
|
+
|
|
9
|
+
## Why the change?
|
|
10
|
+
|
|
11
|
+
Current Tabs uses the Reach UI library under the hood, which is no longer actively maintained. This switches the library used internally to React Aria Components.
|
|
12
|
+
|
|
13
|
+
## Component and API changes at a glance
|
|
14
|
+
|
|
15
|
+
The Reach UI and React Aria APIs are fairly similar so there's not too much to adjust.
|
|
16
|
+
|
|
17
|
+
The biggest adjustment is that you now need to provide an `id` for each `<Tab>` and match it with the one on `<TabPanel>`
|
|
18
|
+
|
|
19
|
+
Additionally:
|
|
20
|
+
- `<TabPanel>`s no longer needs to be wrapped in a `<TabPanels>` component
|
|
21
|
+
- `classNameOverride` changes to `className`
|
|
22
|
+
- `<Tabs defaultIndex={}>` changes to `<Tabs defaultSelectedKey={}>`
|
|
23
|
+
- `<Tabs index={}>` changes to `<Tabs selectedKey={}>`
|
|
24
|
+
- `<Tabs onChange={}>` changes to `<Tabs onSelectionChange={}>`
|
|
25
|
+
- `<Tab disabled>` changes to `<Tab isDisabled>`
|
|
26
|
+
|
|
27
|
+
## Migration examples
|
|
28
|
+
|
|
29
|
+
### Uncontrolled
|
|
30
|
+
|
|
31
|
+
#### Before
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Tabs defaultIndex={1}>
|
|
35
|
+
<TabList>
|
|
36
|
+
<Tab>Tab 1</Tab>
|
|
37
|
+
<Tab>Tab 2</Tab>
|
|
38
|
+
<Tab disabled>Disabled tab</Tab>
|
|
39
|
+
</TabList>
|
|
40
|
+
<TabPanels>
|
|
41
|
+
<TabPanel classNameOverride="p-4">Content 1</TabPanel>
|
|
42
|
+
<TabPanel>Content 2</TabPanel>
|
|
43
|
+
<TabPanel>Disabled content</TabPanel>
|
|
44
|
+
</TabPanels>
|
|
45
|
+
</Tabs>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### After
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<Tabs defaultSelectedKey="two">
|
|
52
|
+
<TabList>
|
|
53
|
+
<Tab id="one">Tab 1</Tab>
|
|
54
|
+
<Tab id="two">Tab 2</Tab>
|
|
55
|
+
<Tab id="three" isDisabled>Disabled tab</Tab>
|
|
56
|
+
</TabList>
|
|
57
|
+
<TabPanel id="one" className="p-4">Content 1</TabPanel>
|
|
58
|
+
<TabPanel id="two">Content 2</TabPanel>
|
|
59
|
+
</Tabs>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Controlled
|
|
63
|
+
|
|
64
|
+
#### Before
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<Tabs onChange={setSelectedTab} defaultIndex={1}>
|
|
68
|
+
<TabList>
|
|
69
|
+
<Tab>Tab 1</Tab>
|
|
70
|
+
<Tab>Tab 2</Tab>
|
|
71
|
+
<Tab disabled>Disabled tab</Tab>
|
|
72
|
+
</TabList>
|
|
73
|
+
<TabPanels>
|
|
74
|
+
<TabPanel>Content 1</TabPanel>
|
|
75
|
+
<TabPanel>Content 2</TabPanel>
|
|
76
|
+
<TabPanel>Disabled content</TabPanel>
|
|
77
|
+
</TabPanels>
|
|
78
|
+
</Tabs>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### After
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<Tabs onSelectionChange={setSelectedTab} selectedKey="two">
|
|
85
|
+
<TabList>
|
|
86
|
+
<Tab id="one">Tab 1</Tab>
|
|
87
|
+
<Tab id="two">Tab 2</Tab>
|
|
88
|
+
<Tab id="three" isDisabled>Disabled tab</Tab>
|
|
89
|
+
</TabList>
|
|
90
|
+
<TabPanel id="one">Content 1</TabPanel>
|
|
91
|
+
<TabPanel id="two">Content 2</TabPanel>
|
|
92
|
+
</Tabs>
|
|
93
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useState } from "react"
|
|
2
|
+
import { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Text } from "~components/Text"
|
|
4
|
+
import { Button } from "~components/__actions__/v2"
|
|
5
|
+
import { Tab, TabList, TabPanel, Tabs, Key } from "../index"
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Components/Tabs/Tabs (Future)",
|
|
9
|
+
component: Tabs,
|
|
10
|
+
args: {
|
|
11
|
+
children: (
|
|
12
|
+
<>
|
|
13
|
+
<TabList aria-label="Tabs">
|
|
14
|
+
<Tab id="one">Tab 1</Tab>
|
|
15
|
+
<Tab id="two">Tab 2</Tab>
|
|
16
|
+
<Tab id="three" badge="3">
|
|
17
|
+
Tab 3
|
|
18
|
+
</Tab>
|
|
19
|
+
<Tab id="four" isDisabled>
|
|
20
|
+
Disabled Tab
|
|
21
|
+
</Tab>
|
|
22
|
+
</TabList>
|
|
23
|
+
<TabPanel id="one" className="p-24">
|
|
24
|
+
<Text variant="body">Content 1</Text>
|
|
25
|
+
</TabPanel>
|
|
26
|
+
<TabPanel id="two" className="p-24">
|
|
27
|
+
<Text variant="body">Content 2</Text>
|
|
28
|
+
</TabPanel>
|
|
29
|
+
<TabPanel id="three" className="p-24">
|
|
30
|
+
<Text variant="body">Content 3</Text>
|
|
31
|
+
</TabPanel>
|
|
32
|
+
</>
|
|
33
|
+
),
|
|
34
|
+
},
|
|
35
|
+
} satisfies Meta<typeof Tabs>
|
|
36
|
+
|
|
37
|
+
export default meta
|
|
38
|
+
|
|
39
|
+
type Story = StoryObj<typeof meta>
|
|
40
|
+
|
|
41
|
+
export const Playground: Story = {
|
|
42
|
+
parameters: {
|
|
43
|
+
chromatic: { disable: false },
|
|
44
|
+
docs: {
|
|
45
|
+
canvas: {
|
|
46
|
+
sourceState: "shown",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
args: {
|
|
51
|
+
defaultSelectedKey: "one",
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
onSelectionChange: (key): void => console.log("Tab changed to ", key),
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const Controlled: Story = {
|
|
58
|
+
render: args => {
|
|
59
|
+
const [selectedKey, setSelectedKey] = useState<Key>(0)
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<Tabs
|
|
63
|
+
{...args}
|
|
64
|
+
selectedKey={selectedKey}
|
|
65
|
+
onSelectionChange={setSelectedKey}
|
|
66
|
+
/>
|
|
67
|
+
<Button
|
|
68
|
+
label="Switch to tab 2"
|
|
69
|
+
onClick={(): void => setSelectedKey("two")}
|
|
70
|
+
/>
|
|
71
|
+
</>
|
|
72
|
+
)
|
|
73
|
+
},
|
|
74
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
.tab {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
border: 2px solid transparent;
|
|
5
|
+
border-bottom: 0;
|
|
6
|
+
border-top-left-radius: var(--border-borderless-border-radius);
|
|
7
|
+
border-top-right-radius: var(--border-borderless-border-radius);
|
|
8
|
+
background: var(--color-white);
|
|
9
|
+
white-space: nowrap;
|
|
10
|
+
text-decoration: none;
|
|
11
|
+
padding: var(--spacing-md) var(--spacing-md);
|
|
12
|
+
margin: 0;
|
|
13
|
+
font-family: var(--typography-heading-4-font-family);
|
|
14
|
+
font-size: var(--typography-heading-4-font-size);
|
|
15
|
+
font-weight: var(--typography-heading-4-font-weight);
|
|
16
|
+
line-height: var(--typography-heading-4-line-height);
|
|
17
|
+
letter-spacing: var(--typography-heading-4-letter-spacing);
|
|
18
|
+
color: var(--color-purple-800);
|
|
19
|
+
|
|
20
|
+
&:focus {
|
|
21
|
+
outline: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&:focus-visible {
|
|
25
|
+
background: var(--color-blue-100);
|
|
26
|
+
color: var(--color-blue-500);
|
|
27
|
+
border-color: var(--color-blue-500);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&[data-disabled] {
|
|
31
|
+
opacity: 0.3;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:not(:first-child) {
|
|
35
|
+
margin-inline-start: var(--spacing-xs);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&:not([data-disabled]):hover {
|
|
39
|
+
background: var(--color-blue-100);
|
|
40
|
+
color: var(--color-blue-500);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.tab[data-selected] {
|
|
45
|
+
position: relative;
|
|
46
|
+
color: var(--color-blue-500);
|
|
47
|
+
|
|
48
|
+
&::before {
|
|
49
|
+
content: "";
|
|
50
|
+
display: block;
|
|
51
|
+
border-top-left-radius: 5px;
|
|
52
|
+
border-top-right-radius: 5px;
|
|
53
|
+
background-color: currentcolor;
|
|
54
|
+
height: 5px;
|
|
55
|
+
width: 100%;
|
|
56
|
+
position: absolute;
|
|
57
|
+
left: 0;
|
|
58
|
+
right: 0;
|
|
59
|
+
bottom: 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.badge {
|
|
64
|
+
margin-inline-start: var(--spacing-sm);
|
|
65
|
+
display: inline-flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media (forced-colors: active) {
|
|
70
|
+
.tab {
|
|
71
|
+
border: 2px solid transparent;
|
|
72
|
+
|
|
73
|
+
&:focus-visible::after {
|
|
74
|
+
content: "";
|
|
75
|
+
position: absolute;
|
|
76
|
+
background: transparent;
|
|
77
|
+
border-radius: var(--border-focus-ring-border-radius);
|
|
78
|
+
border-width: var(--border-focus-ring-border-width);
|
|
79
|
+
border-style: var(--border-focus-ring-border-style);
|
|
80
|
+
border-color: transparent;
|
|
81
|
+
inset: -2px;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.tab[data-selected]::before {
|
|
86
|
+
/* High contrast also doesn't see the pseudo element created to show the active tab. */
|
|
87
|
+
content: "";
|
|
88
|
+
position: absolute;
|
|
89
|
+
left: 0;
|
|
90
|
+
right: 0;
|
|
91
|
+
bottom: 0;
|
|
92
|
+
border-bottom: 2px solid transparent;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import classnames from "classnames"
|
|
3
|
+
import { Tab as RACTab, TabProps as RACTabProps } from "react-aria-components"
|
|
4
|
+
import { Badge } from "~components/Badge"
|
|
5
|
+
import styles from "./Tab.module.css"
|
|
6
|
+
|
|
7
|
+
export type TabProps = {
|
|
8
|
+
/**
|
|
9
|
+
* Adds a Kaizen Badge component to the tab.
|
|
10
|
+
* Comes with some logic baked in - changes variant based on active/focus/hover state.
|
|
11
|
+
*/
|
|
12
|
+
badge?: string
|
|
13
|
+
} & Omit<
|
|
14
|
+
RACTabProps,
|
|
15
|
+
// omitting link functionality because it goes against WAI ARIA standards https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
|
|
16
|
+
| "href"
|
|
17
|
+
| "hrefLang"
|
|
18
|
+
| "target"
|
|
19
|
+
| "rel"
|
|
20
|
+
| "download"
|
|
21
|
+
| "ping"
|
|
22
|
+
| "referrerPolicy"
|
|
23
|
+
>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A tab button
|
|
27
|
+
*/
|
|
28
|
+
export const Tab = (props: TabProps): JSX.Element => {
|
|
29
|
+
const { badge, children, className, ...restProps } = props
|
|
30
|
+
|
|
31
|
+
const tabProps = {
|
|
32
|
+
className: classnames(styles.tab, className),
|
|
33
|
+
...restProps,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<RACTab {...tabProps}>
|
|
38
|
+
{({ isSelected, isFocusVisible, isHovered }) => (
|
|
39
|
+
<>
|
|
40
|
+
{children}
|
|
41
|
+
{badge && (
|
|
42
|
+
<span className={styles.badge}>
|
|
43
|
+
<Badge
|
|
44
|
+
variant={
|
|
45
|
+
isSelected || isFocusVisible || isHovered
|
|
46
|
+
? "active"
|
|
47
|
+
: "default"
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
{badge}
|
|
51
|
+
</Badge>
|
|
52
|
+
</span>
|
|
53
|
+
)}
|
|
54
|
+
</>
|
|
55
|
+
)}
|
|
56
|
+
</RACTab>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Tab"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { ReactNode } from "react"
|
|
2
|
+
import classnames from "classnames"
|
|
3
|
+
import {
|
|
4
|
+
TabList as RACTabList,
|
|
5
|
+
TabListProps as RACTabListProps,
|
|
6
|
+
} from "react-aria-components"
|
|
7
|
+
import styles from "./TabList.module.css"
|
|
8
|
+
|
|
9
|
+
export type TabListProps = {
|
|
10
|
+
/**
|
|
11
|
+
* Accessible name for the set of tabs
|
|
12
|
+
*/
|
|
13
|
+
"aria-label": string
|
|
14
|
+
/**
|
|
15
|
+
* Removes the built in padding
|
|
16
|
+
*/
|
|
17
|
+
noPadding?: boolean
|
|
18
|
+
children: ReactNode
|
|
19
|
+
} & RACTabListProps<HTMLElement>
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wrapper for the tabs themselves
|
|
23
|
+
*/
|
|
24
|
+
export const TabList = (props: TabListProps): JSX.Element => {
|
|
25
|
+
const {
|
|
26
|
+
"aria-label": ariaLabel,
|
|
27
|
+
noPadding = false,
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
...restProps
|
|
31
|
+
} = props
|
|
32
|
+
return (
|
|
33
|
+
<RACTabList
|
|
34
|
+
aria-label={ariaLabel}
|
|
35
|
+
className={classnames(
|
|
36
|
+
styles.tabList,
|
|
37
|
+
className,
|
|
38
|
+
noPadding && styles.noPadding
|
|
39
|
+
)}
|
|
40
|
+
{...restProps}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</RACTabList>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./TabList"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import {
|
|
3
|
+
TabPanel as RACTabPanel,
|
|
4
|
+
TabPanelProps as RACTabPanelProps,
|
|
5
|
+
} from "react-aria-components"
|
|
6
|
+
import styles from "./TabPanel.module.css"
|
|
7
|
+
|
|
8
|
+
export type TabPanelProps = RACTabPanelProps
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Wrapper for the content that shows when tab is active
|
|
12
|
+
*/
|
|
13
|
+
export const TabPanel = (props: TabPanelProps): JSX.Element => {
|
|
14
|
+
const { className, children, ...restProps } = props
|
|
15
|
+
return (
|
|
16
|
+
<RACTabPanel className={(styles.tabPanel, className)} {...restProps}>
|
|
17
|
+
{children}
|
|
18
|
+
</RACTabPanel>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./TabPanel"
|
package/src/__future__/index.ts
CHANGED