@kaizen/components 0.0.0-canary-package-bundler-v2-20241113071536 → 0.0.0-canary-link-button-wip-canary-20241121043208
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/codemods/README.md +16 -1
- package/codemods/upgradeIconV1/getNewIconPropsFromOldIconName.ts +9 -9
- package/dist/cjs/EmptyState/EmptyState.cjs +15 -17
- package/dist/cjs/EmptyState/EmptyState.module.css.cjs +20 -0
- package/dist/cjs/GuidanceBlock/GuidanceBlock.cjs +1 -1
- package/dist/cjs/GuidanceBlock/GuidanceBlock.module.css.cjs +28 -0
- package/dist/cjs/__actions__/Button/v3/Button.cjs +43 -8
- package/dist/cjs/__actions__/Button/v3/Button.module.css.cjs +21 -0
- package/dist/cjs/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.cjs +37 -0
- package/dist/cjs/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.module.css.cjs +9 -0
- package/dist/cjs/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.cjs +35 -0
- package/dist/cjs/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.module.css.cjs +8 -0
- package/dist/cjs/__actions__/LinkButton/v3/LinkButton.cjs +56 -0
- package/dist/cjs/__actions__/LinkButton/v3/LinkButton.module.css.cjs +6 -0
- package/dist/cjs/__overlays__/Tooltip/v3/Tooltip.cjs +2 -2
- package/dist/cjs/actionsV3.cjs +2 -0
- package/dist/esm/Calendar/CalendarPopover/CalendarPopover.mjs +1 -1
- package/dist/esm/EmptyState/EmptyState.mjs +16 -18
- package/dist/esm/EmptyState/EmptyState.module.css.mjs +18 -0
- package/dist/esm/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.mjs +1 -1
- package/dist/esm/GuidanceBlock/GuidanceBlock.mjs +1 -1
- package/dist/esm/GuidanceBlock/GuidanceBlock.module.css.mjs +26 -0
- package/dist/esm/MultiSelect/subcomponents/Popover/Popover.mjs +1 -1
- package/dist/esm/RichTextEditor/RichTextEditor/RichTextEditor.mjs +1 -1
- package/dist/esm/RichTextEditor/RichTextEditor/schema.mjs +1 -1
- package/dist/esm/RichTextEditor/utils/schema/nodes.mjs +1 -1
- package/dist/esm/TimeField/TimeField.mjs +1 -1
- package/dist/esm/__actions__/Button/v3/Button.mjs +44 -9
- package/dist/esm/__actions__/Button/v3/Button.module.css.mjs +19 -0
- package/dist/esm/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.mjs +28 -0
- package/dist/esm/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.module.css.mjs +7 -0
- package/dist/esm/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.mjs +26 -0
- package/dist/esm/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.module.css.mjs +6 -0
- package/dist/esm/__actions__/LinkButton/v3/LinkButton.mjs +47 -0
- package/dist/esm/__actions__/LinkButton/v3/LinkButton.module.css.mjs +4 -0
- package/dist/esm/__overlays__/Tooltip/v1/Tooltip.mjs +1 -1
- package/dist/esm/__overlays__/Tooltip/v3/Tooltip.mjs +1 -1
- package/dist/esm/actionsV3.mjs +1 -0
- package/dist/styles.css +552 -463
- package/dist/types/EmptyState/EmptyState.d.ts +2 -1
- package/dist/types/__actions__/Button/v3/Button.d.ts +17 -4
- package/dist/types/__actions__/Button/v3/index.d.ts +1 -0
- package/dist/types/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.d.ts +11 -0
- package/dist/types/__actions__/Button/v3/subcomponents/ButtonContent/index.d.ts +1 -0
- package/dist/types/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.d.ts +5 -0
- package/dist/types/__actions__/Button/v3/subcomponents/PendingContent/index.d.ts +1 -0
- package/dist/types/__actions__/Button/v3/subcomponents/index.d.ts +2 -0
- package/dist/types/__actions__/Button/v3/types.d.ts +21 -0
- package/dist/types/__actions__/LinkButton/index.d.ts +1 -0
- package/dist/types/__actions__/LinkButton/v3/LinkButton.d.ts +11 -0
- package/dist/types/__actions__/LinkButton/v3/index.d.ts +1 -0
- package/dist/types/__actions__/v3.d.ts +1 -0
- package/package.json +8 -8
- package/src/EmptyState/EmptyState.module.css +114 -0
- package/src/EmptyState/EmptyState.tsx +18 -20
- package/src/EmptyState/_docs/EmptyState.stickersheet.stories.tsx +55 -39
- package/src/GuidanceBlock/{GuidanceBlock.module.scss → GuidanceBlock.module.css} +60 -114
- package/src/GuidanceBlock/GuidanceBlock.tsx +1 -1
- package/src/__actions__/Button/v3/Button.module.css +235 -0
- package/src/__actions__/Button/v3/Button.tsx +95 -29
- package/src/__actions__/Button/v3/_docs/Button--api-specification.mdx +151 -0
- package/src/__actions__/Button/v3/_docs/Button--usage-guidelines.mdx +30 -0
- package/src/__actions__/Button/v3/_docs/Button.docs.stories.tsx +112 -50
- package/src/__actions__/Button/v3/_docs/Button.spec.stories.tsx +80 -120
- package/src/__actions__/Button/v3/_docs/Button.stickersheet.stories.tsx +183 -81
- package/src/__actions__/Button/v3/index.ts +1 -0
- package/src/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.module.css +19 -0
- package/src/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.tsx +40 -0
- package/src/__actions__/Button/v3/subcomponents/ButtonContent/index.ts +1 -0
- package/src/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.module.css +16 -0
- package/src/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.tsx +28 -0
- package/src/__actions__/Button/v3/subcomponents/PendingContent/index.ts +1 -0
- package/src/__actions__/Button/v3/subcomponents/index.ts +2 -0
- package/src/__actions__/Button/v3/types.ts +25 -0
- package/src/__actions__/LinkButton/index.ts +1 -0
- package/src/__actions__/LinkButton/v3/LinkButton.module.css +4 -0
- package/src/__actions__/LinkButton/v3/LinkButton.tsx +72 -0
- package/src/__actions__/LinkButton/v3/_docs/LinkButton--api-specification.mdx +210 -0
- package/src/__actions__/LinkButton/v3/_docs/LinkButton.doc.stories.tsx +132 -0
- package/src/__actions__/LinkButton/v3/_docs/LinkButton.spec.stories.tsx +100 -0
- package/src/__actions__/LinkButton/v3/_docs/LinkButton.stickersheet.stories.tsx +176 -0
- package/src/__actions__/LinkButton/v3/index.ts +1 -0
- package/src/__actions__/Menu/v3/_docs/Menu.docs.stories.tsx +54 -18
- package/src/__actions__/Menu/v3/_docs/Menu.spec.stories.tsx +30 -10
- package/src/__actions__/Menu/v3/_docs/Menu.stories.tsx +12 -4
- package/src/__actions__/v3.ts +1 -0
- package/src/__future__/Icon/_docs/Icon.docs.stories.tsx +7 -7
- package/src/__overlays__/Tooltip/v3/Tooltip.tsx +1 -1
- package/src/__overlays__/Tooltip/v3/_docs/Tooltip.spec.stories.tsx +2 -0
- package/dist/cjs/EmptyState/EmptyState.module.scss.cjs +0 -23
- package/dist/cjs/GuidanceBlock/GuidanceBlock.module.scss.cjs +0 -33
- package/dist/cjs/__actions__/Button/v3/Button.module.scss.cjs +0 -9
- package/dist/esm/EmptyState/EmptyState.module.scss.mjs +0 -21
- package/dist/esm/GuidanceBlock/GuidanceBlock.module.scss.mjs +0 -31
- package/dist/esm/__actions__/Button/v3/Button.module.scss.mjs +0 -7
- package/src/EmptyState/EmptyState.module.scss +0 -177
- package/src/EmptyState/EmptyState.spec.tsx +0 -48
- package/src/EmptyState/_mixins.scss +0 -44
- package/src/__actions__/Button/v3/Button.module.scss +0 -104
- package/src/__actions__/Button/v3/_docs/ApiSpecification.mdx +0 -173
- package/src/__actions__/Button/v3/_docs/Button.mdx +0 -41
- package/src/__actions__/Button/v3/_docs/Button.stories.tsx +0 -98
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type ButtonVariants = "primary" | "secondary" | "tertiary"
|
|
2
|
+
|
|
3
|
+
export type ButtonSizes = "small" | "medium" | "large"
|
|
4
|
+
|
|
5
|
+
export type PendingPropsUndefined = {
|
|
6
|
+
isPending?: undefined
|
|
7
|
+
/** Rendered as the child while `isPending` is `true`. This determines the accessible label for the Button while pending. */
|
|
8
|
+
pendingLabel?: never
|
|
9
|
+
/** Visually hides the `pendingLabel` and renders the loading spinner. This will maintain the width of the Button's `children` to avoid layout shifts.
|
|
10
|
+
* @default false
|
|
11
|
+
*/
|
|
12
|
+
hasHiddenPendingLabel?: never
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type PendingProps = {
|
|
16
|
+
isPending: boolean
|
|
17
|
+
/** Rendered as the child while `pendingLabel` is `true`. This determines the accessible label for the Button while pending. */
|
|
18
|
+
pendingLabel: string
|
|
19
|
+
/** Visually Hides the `pendingLabel` and renders the loading spinner. This will maintain the width of the Button's `children` to avoid layout shifts.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
hasHiddenPendingLabel?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type PendingButtonProps = PendingProps | PendingPropsUndefined
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./v3"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { forwardRef } from "react"
|
|
2
|
+
import {
|
|
3
|
+
Link as RACLink,
|
|
4
|
+
LinkProps as RACLinkProps,
|
|
5
|
+
} from "react-aria-components"
|
|
6
|
+
import { ButtonBaseProps } from "~components/__actions__/Button/v3"
|
|
7
|
+
import buttonStyles from "~components/__actions__/Button/v3/Button.module.css"
|
|
8
|
+
import { ButtonContent } from "~components/__actions__/Button/v3/subcomponents"
|
|
9
|
+
import { useReversedColors } from "~components/__utilities__/v3"
|
|
10
|
+
import { mergeClassNames } from "~components/utils/mergeClassNames"
|
|
11
|
+
import styles from "./LinkButton.module.css"
|
|
12
|
+
|
|
13
|
+
export type LinkButtonProps = ButtonBaseProps &
|
|
14
|
+
Omit<RACLinkProps, "children"> & {
|
|
15
|
+
/** Used as the label for the LinkButton. */
|
|
16
|
+
children: RACLinkProps["children"]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const LinkButton = forwardRef(
|
|
20
|
+
(
|
|
21
|
+
{
|
|
22
|
+
children,
|
|
23
|
+
variant = "primary",
|
|
24
|
+
size = "medium",
|
|
25
|
+
icon,
|
|
26
|
+
iconPosition = "start",
|
|
27
|
+
hasHiddenLabel = false,
|
|
28
|
+
isFullWidth = false,
|
|
29
|
+
isDisabled,
|
|
30
|
+
className,
|
|
31
|
+
...otherProps
|
|
32
|
+
}: LinkButtonProps,
|
|
33
|
+
ref: React.ForwardedRef<HTMLAnchorElement>
|
|
34
|
+
) => {
|
|
35
|
+
const isReversed = useReversedColors()
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<RACLink
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={mergeClassNames(
|
|
41
|
+
styles.linkButton,
|
|
42
|
+
buttonStyles.button,
|
|
43
|
+
buttonStyles[size],
|
|
44
|
+
hasHiddenLabel && buttonStyles[`${size}IconButton`],
|
|
45
|
+
isDisabled && buttonStyles.isDisabled,
|
|
46
|
+
isReversed
|
|
47
|
+
? buttonStyles[`${variant}Reversed`]
|
|
48
|
+
: buttonStyles[variant],
|
|
49
|
+
isFullWidth && buttonStyles.fullWidth,
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
isDisabled={isDisabled}
|
|
53
|
+
{...otherProps}
|
|
54
|
+
>
|
|
55
|
+
{racStateProps => {
|
|
56
|
+
const childIsFunction = typeof children === "function"
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<ButtonContent
|
|
60
|
+
size={size}
|
|
61
|
+
icon={icon}
|
|
62
|
+
iconPosition={iconPosition}
|
|
63
|
+
hasHiddenLabel={hasHiddenLabel}
|
|
64
|
+
>
|
|
65
|
+
{childIsFunction ? children(racStateProps) : children}
|
|
66
|
+
</ButtonContent>
|
|
67
|
+
)
|
|
68
|
+
}}
|
|
69
|
+
</RACLink>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls, ArgTypes, DocsStory } from "@storybook/blocks"
|
|
2
|
+
import { ResourceLinks, KAIOInstallation } from "~storybook/components"
|
|
3
|
+
import { LinkTo } from "../../../../../../../docs/components/LinkTo"
|
|
4
|
+
import * as exampleStories from "./LinkButton.doc.stories"
|
|
5
|
+
|
|
6
|
+
<Meta title="Actions/LinkButton/LinkButton (v3)/API Specification" />
|
|
7
|
+
|
|
8
|
+
# Button API Specification (v3)
|
|
9
|
+
|
|
10
|
+
Updated Nov 12, 2024
|
|
11
|
+
|
|
12
|
+
<ResourceLinks
|
|
13
|
+
sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/__actions__/LinkButton/v3"
|
|
14
|
+
figma="https://www.figma.com/design/eZKEE5kXbEMY3lx84oz8iN/branch/sPhYSlgPScLOAOYfAbkaI5/%F0%9F%92%9C-Heart-UI-Kit?node-id=1929-17364&node-type=canvas&m=dev"
|
|
15
|
+
designGuidelines="/?path=/docs/actions-linkbutton-linkbutton-v3-usage-guidelines--docs"
|
|
16
|
+
/>
|
|
17
|
+
|
|
18
|
+
<KAIOInstallation exportNames={["LinkButton" ]} family="actions" version="3" />
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
`LinkButton` allows users to navigate to another page or resource within a web page or application.
|
|
23
|
+
|
|
24
|
+
The following example and table showcases the essential props that enable the core functionality of `LinkButton`. For the remaining suite of API options refer to [this section](#additional-api-options).
|
|
25
|
+
|
|
26
|
+
<Canvas of={exampleStories.Playground} />
|
|
27
|
+
|
|
28
|
+
<Controls of={exampleStories.Playground} include={["className", "children", "href", "target", "download", "onPress", "routerOptions", "hasHiddenLabel", "size", "variant", "icon", "iconPosition", "isFullWidth", "isDisabled" ]} />
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
This is built on top of [React Aria's Link component](https://react-spectrum.adobe.com/react-aria/Link.html) and is the counterpart to the <LinkTo pageId="actions-button-button-v3-api-specification--docs">Kaizen Button</LinkTo>, handling icons, variants and sizes in the same way. It provides a semantic wrapper for navigational buttons and allows for native `href` navigation and client side routing with [additional configuration](#client-side-routing) .
|
|
33
|
+
|
|
34
|
+
### Navigation and routing
|
|
35
|
+
|
|
36
|
+
{/* // TODO: basic docs on href usage */}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Client side routing
|
|
40
|
+
|
|
41
|
+
While the `LinkButton` supports native `href` navigation out of the box, it is agnostic to the routing technology chosen. As such, additional set up is required to support client side routing from `React Router` or `Next.js`.
|
|
42
|
+
|
|
43
|
+
In order to achieve this, you can use the [RouterProvider](https://react-spectrum.adobe.com/react-aria/routing.html#routerprovider) from the `react-aria-components` library, along with the additional configuration for each routing solution.
|
|
44
|
+
|
|
45
|
+
#### Next.js config example
|
|
46
|
+
|
|
47
|
+
The following example demonstrates how to use the `RouterProvider` with `Next.js`'s Pages router. This will allow the `LinkButton` to navigate using the `router.push` method.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import type {AppProps} from "next/app"
|
|
51
|
+
import {type NextRouter, useRouter} from "next/router"
|
|
52
|
+
import { RouterProvider } from "react-aria-components"
|
|
53
|
+
|
|
54
|
+
// This provides the necessary typings for the routerOptions to uses of LinkButton across the app
|
|
55
|
+
declare module "react-aria-components" {
|
|
56
|
+
interface RouterConfig {
|
|
57
|
+
routerOptions: NonNullable<Parameters<NextRouter["push"]>[2]>;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default function NextPageRouterApp({ Component, pageProps }: AppProps) {
|
|
62
|
+
const router = useRouter()
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<RouterProvider
|
|
66
|
+
navigate={(href, opts) => router.push(href, undefined, opts)}
|
|
67
|
+
>
|
|
68
|
+
{/* ...application code */}
|
|
69
|
+
</RouterProvider>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Additional notes on Next.js config can be found in the React Aria's documentation on the [RouterProvider](https://react-spectrum.adobe.com/react-aria/routing.html#routerprovider), including the alternative setup for the [App router](https://react-spectrum.adobe.com/react-aria/routing.html##app-router).
|
|
75
|
+
|
|
76
|
+
#### React Router config example
|
|
77
|
+
The following example demonstrates how to use the `RouterProvider` with `React Router`'s. This will allow the `LinkButton` to navigate using the `useNavigate` hook.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { RouterProvider } from "react-aria-components"
|
|
81
|
+
import { BrowserRouter, NavigateOptions, useHref, useNavigate } from "react-router-dom"
|
|
82
|
+
|
|
83
|
+
declare module "react-aria-components" {
|
|
84
|
+
interface RouterConfig {
|
|
85
|
+
routerOptions: NavigateOptions
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ReactRouterApp() {
|
|
90
|
+
const navigate = useNavigate()
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<RouterProvider navigate={navigate} useHref={useHref}>
|
|
94
|
+
{/* ...application code */}
|
|
95
|
+
<Routes>
|
|
96
|
+
<Route path="/" element={<HomePage />} />
|
|
97
|
+
{/* ...routes */}
|
|
98
|
+
</Routes>
|
|
99
|
+
</RouterProvider>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Additional notes can be found in the React Aria's documentation on the using the `RouterProvider` with [React Router](https://react-spectrum.adobe.com/react-aria/routing.html#react-router).
|
|
105
|
+
|
|
106
|
+
### Target `_blank` and opening in a new tab
|
|
107
|
+
|
|
108
|
+
The general recommendation is to limit the number of links that open in a new tab or window on a page. While there are valid scenarios that help avoid loss of data and or progress on a page, opening a new tab or window can be disorienting for users, especially those who have difficulty perceiving visual content.
|
|
109
|
+
|
|
110
|
+
In order to provide advance warning to all users, it is recommended that `LinkButton`'s that use `target="_blank"` be accompanied by a visual indicator and audible warning, as in the example below.
|
|
111
|
+
|
|
112
|
+
<Canvas of={exampleStories.LinkButtonOpensInNewTab} />
|
|
113
|
+
|
|
114
|
+
You can get more context on this recommendation via the [W3C page on the G200 success criteria](https://www.w3.org/TR/WCAG20-TECHS/G200.html).
|
|
115
|
+
|
|
116
|
+
### Variants
|
|
117
|
+
|
|
118
|
+
`LinkButton` supports the following variants: `primary`, `secondary` and `tertiary`. If the `variant` prop is not specified, the default will be `primary`.
|
|
119
|
+
|
|
120
|
+
<Canvas of={exampleStories.LinkButtonVariants} />
|
|
121
|
+
|
|
122
|
+
Reversed variants are handled via the `ReversedColors` Provider.
|
|
123
|
+
|
|
124
|
+
<DocsStory of={exampleStories.LinkButtonVariantsReversed} expanded={false} />
|
|
125
|
+
|
|
126
|
+
To enable the reversed theme, you will need to wrap the component or application in the `ReversedColors` provider, ie:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { RouterProvider } from "react-aria-components"
|
|
130
|
+
import { Button } from "@kaizen/components/v3/actions"
|
|
131
|
+
import { ReversedColors } from "@kaizen/components/v3/utilities"
|
|
132
|
+
// application code
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<ReversedColors isReversed={true}>
|
|
136
|
+
<LinkButton {...LinkbuttonProps} />
|
|
137
|
+
</ReversedColors>
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Sizes
|
|
142
|
+
|
|
143
|
+
LinkButton supports the following sizes: `small`, `medium` and `large`. If the `size` prop is not specified, the default will be `medium`.
|
|
144
|
+
|
|
145
|
+
<Canvas of={exampleStories.LinkButtonSizes} />
|
|
146
|
+
|
|
147
|
+
### `onPress`
|
|
148
|
+
|
|
149
|
+
TODO: revamp this section: One key change to the `LinkButton`'s API is the shift from `onClick` to React Aria's implementation of `onPress`. This approach has been adopted as it provides better support for consistent touch events across device types, such mobile, desktop and tablet. You can read more about the development and reason behind this pattern [here](https://react-spectrum.adobe.com/blog/building-a-button-part-1.html#touch-interactions).
|
|
150
|
+
|
|
151
|
+
Functionally this does not change the way we pass actions into `LinkButton`. Consumers can safely replace `onClick` with `onPress` without any additional changes, ie:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<LinkButton label="Submit" onClick={e => sumbit(e)}/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Can safely be replaced with the following:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
<LinkButton onPress={e => submit(e)}>
|
|
161
|
+
Submit
|
|
162
|
+
</LinkButton>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
### Button content and children
|
|
167
|
+
|
|
168
|
+
Labels and any button content can be passed into the `LinkButton` as `children`. Content wrapped by the `LinkButton` will be spaced evenly relative to the `size` prop.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<LinkButton variant="secondary" href="#link">
|
|
172
|
+
Label
|
|
173
|
+
</LinkButton>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
While in most cases, `children` will be a `ReactNode`, `LinkButton` also accepts a render function with React Aria's `LinkRenderProps`. This allows for more advanced styling and rendering options by hooking into React Aria's internal button state. You can read more about this [here](https://react-spectrum.adobe.com/react-aria/Link.html#styling).
|
|
177
|
+
|
|
178
|
+
### Icons and positioning
|
|
179
|
+
|
|
180
|
+
The `icon` property abstracts the need to handle positioning and sizing logic for icons within the `LinkButton`. When paired with the [Icon component](/docs/illustrations-icon-icon-future-api-specification--docs), this will scale the icon to the `LinkButton`'s `size` prop.
|
|
181
|
+
|
|
182
|
+
<Canvas of={exampleStories.LinkButtonWithIconStart} />
|
|
183
|
+
|
|
184
|
+
Set the position of the icon using the `iconPosition` prop. This will ensure content is flipped in `RTL` layouts. Note that icons will need the [shouldMirrorInRTL](/docs/illustrations-icon-icon-future-api-specification--docs#mirror-in-rtl) prop set to `true` when mirroring is required.
|
|
185
|
+
|
|
186
|
+
<Canvas of={exampleStories.LinkButtonWithIconEnd} />
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
### Icon-only `LinkButton` and `hasHiddenLabel`
|
|
190
|
+
|
|
191
|
+
To achieve an icon-only `LinkButton` (previously: `IconButton`) use the `icon` prop and set `hasHiddenLabel` to `true`. This will visually hide the button's `children`, while still announcing the content to screen readers.
|
|
192
|
+
|
|
193
|
+
<Canvas of={exampleStories.IconLinkButton} />
|
|
194
|
+
|
|
195
|
+
This pattern ensures that the `LinkButton`'s accessible name is determined by its children, which helps to announce relevant content to the screen readers. You can learn more about this [accessible pattern here](https://cultureamp.atlassian.net/wiki/spaces/PA/pages/3833331831/Accessible+button+and+link+labels).
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
### Full width LinkButtons
|
|
199
|
+
|
|
200
|
+
If a `LinkButton` is statically the full width of a container you can use the `isFullWidth` property.
|
|
201
|
+
|
|
202
|
+
<Canvas of={exampleStories.LinkButtonFullWidth} />
|
|
203
|
+
|
|
204
|
+
For resizing on smaller screens, consider using the `className` prop to leverage CSS media or container queries, ie: `<LinkButton className="w-full md:w-[initial]">Label</LinkButton>`.
|
|
205
|
+
|
|
206
|
+
## Additional API options
|
|
207
|
+
|
|
208
|
+
The following table is a collection of additional React Aria and native HTML props that are exposed from the [React Aria Link API](https://react-spectrum.adobe.com/react-aria/Link.html). These are not required for the implementation of `LinkButton` but can be used to extend its functionality. Refer back to the [overview section](#overview) for the core props that enable most use cases.
|
|
209
|
+
|
|
210
|
+
<ArgTypes of={exampleStories.Playground} exclude={["className", "children", "href", "target", "download", "routerOptions", "hasHiddenLabel", "size", "variant", "onPress", "icon", "iconPosition", "isFullWidth", "isDisabled" ]}/>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { VisuallyHidden } from "~components/VisuallyHidden"
|
|
4
|
+
import { Icon } from "~components/__future__"
|
|
5
|
+
import { ReversedColors } from "~components/__utilities__/v3"
|
|
6
|
+
import { LinkButton } from "../index"
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "Actions/LinkButton/LinkButton (v3)",
|
|
10
|
+
component: LinkButton,
|
|
11
|
+
args: {
|
|
12
|
+
children: "Label",
|
|
13
|
+
href: "#link-button-clicked",
|
|
14
|
+
target: "_blank",
|
|
15
|
+
},
|
|
16
|
+
} satisfies Meta<typeof LinkButton>
|
|
17
|
+
|
|
18
|
+
export default meta
|
|
19
|
+
|
|
20
|
+
type Story = StoryObj<typeof meta>
|
|
21
|
+
|
|
22
|
+
export const Playground: Story = {}
|
|
23
|
+
|
|
24
|
+
export const LinkButtonOpensInNewTab: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
children: (
|
|
27
|
+
<>
|
|
28
|
+
Label
|
|
29
|
+
<VisuallyHidden> opens a new tab</VisuallyHidden>
|
|
30
|
+
</>
|
|
31
|
+
),
|
|
32
|
+
href: "https://www.google.com",
|
|
33
|
+
target: "_blank",
|
|
34
|
+
icon: <Icon isPresentational name="open_in_new" shouldMirrorInRTL />,
|
|
35
|
+
iconPosition: "end",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const LinkButtonVariants: Story = {
|
|
40
|
+
render: args => (
|
|
41
|
+
<>
|
|
42
|
+
<LinkButton {...args} variant="primary" />
|
|
43
|
+
<LinkButton {...args} variant="secondary" />
|
|
44
|
+
<LinkButton {...args} variant="tertiary" />
|
|
45
|
+
</>
|
|
46
|
+
),
|
|
47
|
+
decorators: [
|
|
48
|
+
Story => (
|
|
49
|
+
<div className="flex gap-8">
|
|
50
|
+
<Story />
|
|
51
|
+
</div>
|
|
52
|
+
),
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const LinkButtonVariantsReversed: Story = {
|
|
57
|
+
render: args => (
|
|
58
|
+
<ReversedColors isReversed={true}>
|
|
59
|
+
<LinkButton {...args} variant="primary" />
|
|
60
|
+
<LinkButton {...args} variant="secondary" />
|
|
61
|
+
<LinkButton {...args} variant="tertiary" />
|
|
62
|
+
</ReversedColors>
|
|
63
|
+
),
|
|
64
|
+
parameters: {
|
|
65
|
+
reverseColors: true,
|
|
66
|
+
},
|
|
67
|
+
decorators: [
|
|
68
|
+
Story => (
|
|
69
|
+
<div className="flex gap-8 bg-purple-700 p-16">
|
|
70
|
+
<Story />
|
|
71
|
+
</div>
|
|
72
|
+
),
|
|
73
|
+
],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const LinkButtonSizes: Story = {
|
|
77
|
+
render: args => (
|
|
78
|
+
<>
|
|
79
|
+
<LinkButton {...args} size="small" />
|
|
80
|
+
<LinkButton {...args} size="medium" />
|
|
81
|
+
<LinkButton {...args} size="large" />
|
|
82
|
+
</>
|
|
83
|
+
),
|
|
84
|
+
decorators: [
|
|
85
|
+
Story => (
|
|
86
|
+
<div className="[&>*]:ms-8">
|
|
87
|
+
<Story />
|
|
88
|
+
</div>
|
|
89
|
+
),
|
|
90
|
+
],
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const LinkButtonWithIconStart: Story = {
|
|
94
|
+
args: {
|
|
95
|
+
icon: <Icon isPresentational name="delete" />,
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const LinkButtonWithIconEnd: Story = {
|
|
100
|
+
args: {
|
|
101
|
+
icon: <Icon isPresentational name="arrow_forward" shouldMirrorInRTL />,
|
|
102
|
+
iconPosition: "end",
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const IconLinkButton: Story = {
|
|
107
|
+
args: {
|
|
108
|
+
children: "Remove highlights from: May 8, 2024",
|
|
109
|
+
icon: <Icon isPresentational name="delete" />,
|
|
110
|
+
hasHiddenLabel: true,
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const ReversedLinkButton: Story = {
|
|
115
|
+
parameters: {
|
|
116
|
+
reverseColors: true,
|
|
117
|
+
docs: {
|
|
118
|
+
source: {
|
|
119
|
+
code: `<ReversedColors isReversed={true}>
|
|
120
|
+
<LinkButton>Label</LinkButton>
|
|
121
|
+
</ReversedColors>
|
|
122
|
+
`,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const LinkButtonFullWidth: Story = {
|
|
129
|
+
args: {
|
|
130
|
+
isFullWidth: true,
|
|
131
|
+
},
|
|
132
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { userEvent, waitFor, within, expect } from "@storybook/test"
|
|
4
|
+
import { VisuallyHidden } from "~components/VisuallyHidden"
|
|
5
|
+
import { Icon } from "~components/__future__/Icon"
|
|
6
|
+
import { LinkButton } from "../index"
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "Actions/LinkButton/LinkButton (v3)/LinkButton (v3) tests",
|
|
10
|
+
component: LinkButton,
|
|
11
|
+
args: {
|
|
12
|
+
children: "Label",
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof LinkButton>
|
|
15
|
+
|
|
16
|
+
export default meta
|
|
17
|
+
|
|
18
|
+
type Story = StoryObj<typeof meta>
|
|
19
|
+
|
|
20
|
+
export const IconLinkButtonWithHiddenLabel: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
hasHiddenLabel: true,
|
|
23
|
+
icon: <Icon name="add" isPresentational />,
|
|
24
|
+
children: (
|
|
25
|
+
<span>
|
|
26
|
+
Hidden label <span>is</span> <span>accessible</span>
|
|
27
|
+
</span>
|
|
28
|
+
),
|
|
29
|
+
},
|
|
30
|
+
play: async ({ canvasElement }) => {
|
|
31
|
+
const canvas = within(canvasElement.parentElement!)
|
|
32
|
+
const linkButton = canvas.getByRole("link")
|
|
33
|
+
|
|
34
|
+
expect(linkButton).toHaveAccessibleName("Hidden label is accessible")
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const RACRenderPropsWithChildren: Story = {
|
|
39
|
+
render: ({ children: _, ...otherArgs }) => (
|
|
40
|
+
<LinkButton {...otherArgs}>
|
|
41
|
+
{({ isFocusVisible }) => (
|
|
42
|
+
<>
|
|
43
|
+
Label
|
|
44
|
+
<VisuallyHidden>
|
|
45
|
+
{isFocusVisible ? " is focused" : " is unfocused"}
|
|
46
|
+
</VisuallyHidden>
|
|
47
|
+
<Icon
|
|
48
|
+
name={isFocusVisible ? "thumb_up" : "thumb_down"}
|
|
49
|
+
isPresentational
|
|
50
|
+
isFilled={true}
|
|
51
|
+
className="ms-8 [--icon-size:16]"
|
|
52
|
+
/>
|
|
53
|
+
</>
|
|
54
|
+
)}
|
|
55
|
+
</LinkButton>
|
|
56
|
+
),
|
|
57
|
+
play: async ({ canvasElement, step }) => {
|
|
58
|
+
const canvas = within(canvasElement.parentElement!)
|
|
59
|
+
const linkButton = canvas.getByRole("link")
|
|
60
|
+
|
|
61
|
+
await step("link icon reflects unfocused state", async () => {
|
|
62
|
+
await waitFor(() =>
|
|
63
|
+
expect(linkButton).toHaveAccessibleName("Label is unfocused")
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
await step("focus on link and update icon", async () => {
|
|
68
|
+
await userEvent.tab()
|
|
69
|
+
await waitFor(() =>
|
|
70
|
+
expect(linkButton).toHaveAccessibleName("Label is focused")
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const RACRenderPropsWithClassName: Story = {
|
|
77
|
+
args: {
|
|
78
|
+
className: ({ isFocusVisible }) => (isFocusVisible ? "!bg-gray-300" : ""),
|
|
79
|
+
},
|
|
80
|
+
play: async ({ canvasElement }) => {
|
|
81
|
+
const canvas = within(canvasElement.parentElement!)
|
|
82
|
+
const linkButton = canvas.getByRole("link")
|
|
83
|
+
await linkButton.focus()
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Test cases:
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Can route to a new page when clicked
|
|
91
|
+
*
|
|
92
|
+
* has accepts routing options from a client side router (will have to mock this)
|
|
93
|
+
*
|
|
94
|
+
* can have href and onPress at the same item
|
|
95
|
+
*
|
|
96
|
+
* can have href, onPress and download
|
|
97
|
+
*
|
|
98
|
+
* ping will send a post request with body ping to URL
|
|
99
|
+
*
|
|
100
|
+
*/
|