@kaizen/components 1.69.1 → 1.70.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.
Files changed (66) hide show
  1. package/codemods/migrateBrandMomentMoodToVariant/index.ts +5 -3
  2. package/codemods/migrateBrandMomentMoodToVariant/transformBrandMomentMoodToVariant.spec.ts +16 -5
  3. package/codemods/migrateCardVariantToColor/index.ts +5 -3
  4. package/codemods/migrateCardVariantToColor/transformCardVariantToColor.spec.ts +16 -5
  5. package/codemods/migrateConfirmationModalMoodsToVariant/index.ts +3 -1
  6. package/codemods/migrateConfirmationModalMoodsToVariant/transformConfirmationModalMoodsToVariant.spec.ts +27 -44
  7. package/codemods/migrateEmptyStateIllustrationTypeToVariant/index.ts +5 -3
  8. package/codemods/migrateEmptyStateIllustrationTypeToVariant/transformEmptyStateIllustrationTypeToVariant.spec.ts +16 -5
  9. package/codemods/migrateGlobalNotificationTypeToVariant/index.ts +5 -3
  10. package/codemods/migrateInformationTileMoodToVariant/index.ts +5 -3
  11. package/codemods/migrateInformationTileMoodToVariant/transformInformationTileMoodToVariant.spec.ts +16 -7
  12. package/codemods/migrateInlineNotificationTypeToVariant/index.ts +5 -3
  13. package/codemods/migrateMultiActionTileMoodToVariant/index.ts +5 -3
  14. package/codemods/migrateMultiActionTileMoodToVariant/transformMultiActionTileMoodToVariant.spec.ts +16 -7
  15. package/codemods/migrateNotificationTypeToVariant/migrateNotificationTypeToVariant.spec.ts +42 -43
  16. package/codemods/migrateProgressBarMoodToColor/index.ts +5 -3
  17. package/codemods/migrateProgressBarMoodToColor/transformProgressBarMoodToColor.spec.ts +16 -5
  18. package/codemods/migrateToastNotificationTypeToVariant/index.ts +6 -4
  19. package/codemods/migrateWellVariantToColor/index.ts +5 -3
  20. package/codemods/migrateWellVariantToColor/transformWellVariantToColor.spec.ts +26 -68
  21. package/codemods/migrateWellVariantToColor/transformWellVariantToColor.ts +10 -4
  22. package/codemods/removeInputEditModalMood/index.ts +3 -1
  23. package/codemods/removeInputEditModalMood/removeInputEditModalMood.spec.ts +23 -20
  24. package/codemods/removePopoverVariant/index.ts +1 -1
  25. package/codemods/removePopoverVariant/removePopoverVariant.spec.ts +23 -25
  26. package/codemods/upgradeIconV1/index.ts +2 -17
  27. package/codemods/upgradeIconV1/upgradeIconV1.spec.ts +18 -120
  28. package/codemods/upgradeIconV1/upgradeIconV1.ts +21 -28
  29. package/codemods/utils/__snapshots__/transformSource.spec.ts.snap +0 -10
  30. package/codemods/utils/getKaioTagName.spec.ts +167 -23
  31. package/codemods/utils/getKaioTagName.ts +71 -35
  32. package/codemods/utils/migrateStringProp.spec.ts +16 -5
  33. package/codemods/utils/migrateStringProp.ts +10 -3
  34. package/codemods/utils/removeProps.spec.ts +26 -25
  35. package/codemods/utils/removeProps.ts +10 -3
  36. package/codemods/utils/transformComponentsInDir.ts +40 -13
  37. package/codemods/utils/transformSource.spec.ts +1 -27
  38. package/codemods/utils/transformSource.ts +0 -26
  39. package/codemods/utils/updateKaioImports.ts +2 -0
  40. package/dist/cjs/LinkButton/LinkButton.cjs +59 -0
  41. package/dist/cjs/LinkButton/LinkButton.module.css.cjs +6 -0
  42. package/dist/cjs/__rc__/Button/Button.module.css.cjs +1 -1
  43. package/dist/cjs/index.cjs +2 -0
  44. package/dist/esm/LinkButton/LinkButton.mjs +53 -0
  45. package/dist/esm/LinkButton/LinkButton.module.css.mjs +4 -0
  46. package/dist/esm/__rc__/Button/Button.module.css.mjs +1 -1
  47. package/dist/esm/index.mjs +1 -0
  48. package/dist/styles.css +3259 -3229
  49. package/dist/types/LinkButton/LinkButton.d.ts +11 -0
  50. package/dist/types/LinkButton/index.d.ts +1 -0
  51. package/dist/types/__rc__/Button/Button.d.ts +6 -5
  52. package/dist/types/index.d.ts +1 -2
  53. package/package.json +1 -1
  54. package/src/LinkButton/LinkButton.module.css +4 -0
  55. package/src/LinkButton/LinkButton.tsx +71 -0
  56. package/src/LinkButton/_docs/LinkButton--api-specification.mdx +281 -0
  57. package/src/LinkButton/_docs/LinkButton--usage-guidelines.mdx +29 -0
  58. package/src/LinkButton/_docs/LinkButton.doc.stories.tsx +136 -0
  59. package/src/LinkButton/_docs/LinkButton.spec.stories.tsx +80 -0
  60. package/src/LinkButton/_docs/LinkButton.stickersheet.stories.tsx +130 -0
  61. package/src/LinkButton/index.ts +1 -0
  62. package/src/__rc__/Button/Button.module.css +44 -19
  63. package/src/__rc__/Button/Button.tsx +8 -4
  64. package/src/__rc__/Button/_docs/Button--api-specification.mdx +5 -5
  65. package/src/__rc__/Button/_docs/Button--usage-guidelines.mdx +2 -5
  66. package/src/index.ts +1 -2
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { type LinkProps as RACLinkProps } from 'react-aria-components';
3
+ import { type ButtonUIProps } from "../__rc__/Button";
4
+ export type LinkButtonProps = ButtonUIProps & Omit<RACLinkProps, 'children'> & {
5
+ /** Used as the label for the LinkButton. */
6
+ children: RACLinkProps['children'];
7
+ };
8
+ export declare const LinkButton: React.ForwardRefExoticComponent<ButtonUIProps & Omit<RACLinkProps, "children"> & {
9
+ /** Used as the label for the LinkButton. */
10
+ children: RACLinkProps["children"];
11
+ } & React.RefAttributes<HTMLAnchorElement>>;
@@ -0,0 +1 @@
1
+ export * from './LinkButton';
@@ -1,9 +1,8 @@
1
1
  import React from 'react';
2
2
  import { type ButtonProps as RACButtonProps } from 'react-aria-components';
3
3
  import { type ButtonSizes, type ButtonVariants, type PendingButtonProps } from './types';
4
- type ButtonBaseProps = Omit<RACButtonProps, 'children'> & {
5
- /** Used as the label for the button. */
6
- children: RACButtonProps['children'];
4
+ /** Shared UI props between Button and LinkButton */
5
+ export type ButtonUIProps = {
7
6
  /** Visually hides the Button's child content used as the label and the `pendingLabel`. Use for icon-only `Button`. @default "false" */
8
7
  hasHiddenLabel?: boolean;
9
8
  /** The visual style of the button.
@@ -24,6 +23,8 @@ type ButtonBaseProps = Omit<RACButtonProps, 'children'> & {
24
23
  */
25
24
  isReversed?: boolean;
26
25
  };
27
- export type ButtonProps = ButtonBaseProps & PendingButtonProps;
26
+ export type ButtonProps = ButtonUIProps & PendingButtonProps & Omit<RACButtonProps, 'children'> & {
27
+ /** Used as the label for the button. */
28
+ children: RACButtonProps['children'];
29
+ };
28
30
  export declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
29
- export {};
@@ -30,6 +30,7 @@ export * from './KaizenProvider';
30
30
  export * from './Label';
31
31
  export * from './LabelledMessage';
32
32
  export * from './LikertScaleLegacy';
33
+ export * from './LinkButton';
33
34
  export * from './Loading';
34
35
  export * from './Menu';
35
36
  export * from './Modal';
@@ -61,5 +62,3 @@ export * from './utils';
61
62
  export * from './VisuallyHidden';
62
63
  export * from './Well';
63
64
  export * from './Workflow';
64
- export * from './Menu';
65
- export * from './Button';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaizen/components",
3
- "version": "1.69.1",
3
+ "version": "1.70.0",
4
4
  "description": "Kaizen component library",
5
5
  "author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
6
6
  "homepage": "https://cultureamp.design",
@@ -0,0 +1,4 @@
1
+ .linkButton {
2
+ /* Reset */
3
+ text-decoration: inherit;
4
+ }
@@ -0,0 +1,71 @@
1
+ import React, { forwardRef } from 'react'
2
+ import { Link as RACLink, type LinkProps as RACLinkProps } from 'react-aria-components'
3
+ import { type ButtonUIProps } from '~components/__rc__/Button'
4
+ import buttonStyles from '~components/__rc__/Button/Button.module.css'
5
+ import { ButtonContent } from '~components/__rc__/Button/subcomponents'
6
+ import { useReversedColors } from '~components/__utilities__/v3'
7
+ import { mergeClassNames } from '~components/utils/mergeClassNames'
8
+ import styles from './LinkButton.module.css'
9
+
10
+ export type LinkButtonProps = ButtonUIProps &
11
+ Omit<RACLinkProps, 'children'> & {
12
+ /** Used as the label for the LinkButton. */
13
+ children: RACLinkProps['children']
14
+ }
15
+
16
+ export const LinkButton = forwardRef(
17
+ (
18
+ {
19
+ children,
20
+ variant = 'primary',
21
+ size = 'medium',
22
+ icon,
23
+ iconPosition = 'start',
24
+ hasHiddenLabel = false,
25
+ isFullWidth = false,
26
+ isDisabled,
27
+ className,
28
+ isReversed,
29
+ ...otherProps
30
+ }: LinkButtonProps,
31
+ ref: React.ForwardedRef<HTMLAnchorElement>,
32
+ ) => {
33
+ const shouldUseReverse = useReversedColors()
34
+ const isReversedVariant = isReversed ?? shouldUseReverse
35
+
36
+ return (
37
+ <RACLink
38
+ ref={ref}
39
+ className={mergeClassNames(
40
+ styles.linkButton,
41
+ buttonStyles.button,
42
+ buttonStyles[size],
43
+ hasHiddenLabel && buttonStyles[`${size}IconButton`],
44
+ isDisabled && buttonStyles.isDisabled,
45
+ isReversedVariant ? buttonStyles[`${variant}Reversed`] : buttonStyles[variant],
46
+ isFullWidth && buttonStyles.fullWidth,
47
+ className,
48
+ )}
49
+ isDisabled={isDisabled}
50
+ {...otherProps}
51
+ >
52
+ {(racStateProps) => {
53
+ const childIsFunction = typeof children === 'function'
54
+
55
+ return (
56
+ <ButtonContent
57
+ size={size}
58
+ icon={icon}
59
+ iconPosition={iconPosition}
60
+ hasHiddenLabel={hasHiddenLabel}
61
+ >
62
+ {childIsFunction ? children(racStateProps) : children}
63
+ </ButtonContent>
64
+ )
65
+ }}
66
+ </RACLink>
67
+ )
68
+ },
69
+ )
70
+
71
+ LinkButton.displayName = 'LinkButton'
@@ -0,0 +1,281 @@
1
+ import { Canvas, Meta, Controls, ArgTypes, DocsStory } from '@storybook/blocks'
2
+ import { ResourceLinks, KAIOInstallation, LinkTo } from '~storybook/components'
3
+ import * as exampleStories from './LinkButton.doc.stories'
4
+
5
+ <Meta title="Components/LinkButton/API Specification" />
6
+
7
+ # LinkButton API Specification
8
+
9
+ Updated Dec 18, 2024
10
+
11
+ <ResourceLinks
12
+ sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/__actions__/LinkButton/v3"
13
+ figma="https://www.figma.com/design/eZKEE5kXbEMY3lx84oz8iN/%F0%9F%92%9C-Heart-UI-Kit?node-id=1929-17364"
14
+ designGuidelines="/?path=/docs/actions-linkbutton-linkbutton-v3-usage-guidelines--docs"
15
+ />
16
+
17
+ <KAIOInstallation exportNames={'LinkButton'} />
18
+
19
+ ## Overview
20
+
21
+ `LinkButton` allows users to navigate to another page or resource. It shares the same visual styles and interaction states as the <LinkTo pageId="actions-button-button-v3-usage-guidelines--docs">Button</LinkTo> component, but is intended for navigational purposes and downloading documents.
22
+
23
+ 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).
24
+
25
+ <Canvas of={exampleStories.Playground} />
26
+
27
+ <Controls
28
+ of={exampleStories.Playground}
29
+ include={[
30
+ 'className',
31
+ 'children',
32
+ 'href',
33
+ 'target',
34
+ 'download',
35
+ 'onPress',
36
+ 'routerOptions',
37
+ 'hasHiddenLabel',
38
+ 'size',
39
+ 'variant',
40
+ 'icon',
41
+ 'iconPosition',
42
+ 'isReversed',
43
+ 'isFullWidth',
44
+ 'isDisabled',
45
+ ]}
46
+ />
47
+
48
+ ## API
49
+
50
+ 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).
51
+
52
+ ### Navigation and native anchor attributes
53
+
54
+ Out of the box, the `LinkButton` offers majority of the native behavior and functionality on the `anchor` tag. `href` will trigger new page loads, `download` will download the referenced document, and `target` can be used to open links in new tabs or windows.
55
+
56
+ <Canvas of={exampleStories.DownloadIconButton} />
57
+
58
+ While client side routing is possible, the `LinkButton` is agnostic to the routing technology chosen. Refer to our general set up guide to get started with [client side routing](#client-side-routing).
59
+
60
+ #### Opening new tabs and accessibility considerations
61
+
62
+ The general recommendation is to limit the number of links that open a new tab or window on a single page. While there are valid scenarios that can help avoid loss of data and or progress, as with links in forms, opening new tabs can be disorienting for users - especially for those who have difficulty perceiving visual content.
63
+
64
+ In order to provide advance warning to all users, it is recommended that links using `target="_blank"` be accompanied by a visual indicator and audible warning. As shown in the following example, additional context can be provided via a visually hidden element within the `children` of the component.
65
+
66
+ <Canvas of={exampleStories.LinkButtonOpensInNewTab} />
67
+
68
+ For more context on this recommendation, we recommend taking a look at the [W3C page on the G200 success criteria](https://www.w3.org/TR/WCAG20-TECHS/G200.html).
69
+
70
+ ### Variants
71
+
72
+ `LinkButton` supports the following variants: `primary`, `secondary` and `tertiary`. If the `variant` prop is not specified, the default will be `primary`.
73
+
74
+ <Canvas of={exampleStories.LinkButtonVariants} />
75
+
76
+ Reversed variants are handled via the `ReversedColors` Provider.
77
+
78
+ <DocsStory of={exampleStories.LinkButtonVariantsReversed} expanded={false} />
79
+
80
+ To enable the reversed theme, you will need to wrap the component or application in the `ReversedColors` provider, ie:
81
+
82
+ ```tsx
83
+ import { RouterProvider } from 'react-aria-components'
84
+ import { Button } from '@kaizen/components/v3/actions'
85
+ import { ReversedColors } from '@kaizen/components/v3/utilities'
86
+ // application code
87
+
88
+ return (
89
+ <ReversedColors isReversed={true}>
90
+ <LinkButton {...LinkbuttonProps} />
91
+ </ReversedColors>
92
+ )
93
+ ```
94
+
95
+ ### Sizes
96
+
97
+ LinkButton supports the following sizes: `small`, `medium` and `large`. If the `size` prop is not specified, the default will be `medium`.
98
+
99
+ <Canvas of={exampleStories.LinkButtonSizes} />
100
+
101
+ ### `onPress`
102
+
103
+ As with <LinkTo pageId="actions-button-button-v3-usage-guidelines--docs">Button</LinkTo>, `LinkButton`'s API uses React Aria's `onPress` instead of `onClick`. Functionally this does not change the way we pass click events to a `LinkButton`. Consumers can safely replace `onClick` with `onPress` without any additional changes, ie:
104
+
105
+ ```tsx
106
+ <Button
107
+ label="Download doc"
108
+ href="https://cultureamp.com/a-pdf-doc.pdf"
109
+ download
110
+ onClick={(e) => trackDownloadEvent(e)}
111
+ />
112
+ ```
113
+
114
+ Can be replaced with the following:
115
+
116
+ ```tsx
117
+ <LinkButton
118
+ href="https://cultureamp.com/a-pdf-doc.pdf"
119
+ download
120
+ onPress={(e) => trackDownloadEvent(e)}
121
+ >
122
+ Download doc
123
+ </LinkButton>
124
+ ```
125
+
126
+ 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).
127
+
128
+ ### LinkButton content and children
129
+
130
+ Labels and any `LinkButton` content can be passed to the component via `children`. For icons as content, refer to the [next section](#icons-and-positioning).
131
+
132
+ ```tsx
133
+ <LinkButton href="#link">Label</LinkButton>
134
+ ```
135
+
136
+ 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 Link state. You can read more about this [here](https://react-spectrum.adobe.com/react-aria/Link.html#styling).
137
+
138
+ ### Icons and positioning
139
+
140
+ 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.
141
+
142
+ <Canvas of={exampleStories.LinkButtonWithIconStart} />
143
+
144
+ 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.
145
+
146
+ <Canvas of={exampleStories.LinkButtonWithIconEnd} />
147
+
148
+ ### Icon-only `LinkButton` and `hasHiddenLabel`
149
+
150
+ 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.
151
+
152
+ <Canvas of={exampleStories.IconLinkButton} />
153
+
154
+ 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).
155
+
156
+ ### Full width LinkButtons
157
+
158
+ If a `LinkButton` is statically the full width of a container you can use the `isFullWidth` property.
159
+
160
+ <Canvas of={exampleStories.LinkButtonFullWidth} />
161
+
162
+ 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>`.
163
+
164
+ ## Client side routing
165
+
166
+ To enable client side routing with the `LinkButton`, you will need to wrap your application in a [RouterProvider](https://react-spectrum.adobe.com/react-aria/routing.html#routerprovider) from the `react-aria-components` library. The allows you to set the `navigation` method that performs the client side routing in the `LinkButton` component. Refer to the framework specific guidance below for [Next.js](#nextjs-config-example) or [React Router](#react-router-config-example).
167
+
168
+ ### Next.js config example
169
+
170
+ The following example demonstrates how you might use the React Aria's `RouterProvider` with `Next.js`'s Pages router. This will allow the `LinkButton` to navigate using the `router.push` method.
171
+
172
+ ```tsx
173
+ // ...imports
174
+ import type { AppProps } from 'next/app'
175
+ import { type NextRouter } from 'next/router'
176
+ import { RouterProvider as RacRouterProvider } from 'react-aria-components'
177
+
178
+ // This provides the correct types for `routerOptions` based on the routing solution. As the component agnostic to routing technology this must defined here
179
+ declare module 'react-aria-components' {
180
+ interface RouterConfig {
181
+ // index 2 is the types for the pages routerOptions
182
+ routerOptions: NonNullable<Parameters<NextRouter['push']>[2]>
183
+ }
184
+ }
185
+
186
+ function App({ Component, pageProps, router }: AppProps) {
187
+ return (
188
+ <FrontendServices {...config}>
189
+ {/* application code */}
190
+ <RacRouterProvider navigate={(href, opts) => router.push(href, undefined, opts)}>
191
+ <Component {...pageProps} />
192
+ </RacRouterProvider>
193
+ {/* application code */}
194
+ </FrontendServices>
195
+ )
196
+ }
197
+
198
+ export default App
199
+ ```
200
+
201
+ The implementation in your application would then look something like this:
202
+
203
+ ```tsx
204
+ import { useRouter } from 'next/router'
205
+ import { LinkButton } from '@kaizen/components/v3/actions'
206
+
207
+ const Component = () => {
208
+ const router = useRouter()
209
+
210
+ return (
211
+ <>
212
+ <LinkButton href="http://google.com">External link</LinkButton>
213
+ <LinkButton href={`${router.pathname}/path-1`}>Internal link</LinkButton>
214
+ <LinkButton href={`${router.pathname}/path-2`} routerOptions={{ scroll: false }}>
215
+ Link with routerOptions
216
+ </LinkButton>
217
+ </>
218
+ )
219
+ }
220
+ ```
221
+
222
+ Additional config options for Next.js can be found in the React Aria's documentation on the [RouterProvider](https://react-spectrum.adobe.com/react-aria/routing.html#nextjs), including the alternative setup for the [App router](https://react-spectrum.adobe.com/react-aria/routing.html##app-router).
223
+
224
+ ### React Router config example
225
+
226
+ The following example demonstrates how to use the `RouterProvider` with `React Router`'s. This will allow the `LinkButton` to navigate using the `useNavigate` hook.
227
+
228
+ ```tsx
229
+ import { RouterProvider } from 'react-aria-components'
230
+ import { BrowserRouter, NavigateOptions, useHref, useNavigate } from 'react-router-dom'
231
+
232
+ declare module 'react-aria-components' {
233
+ interface RouterConfig {
234
+ routerOptions: NavigateOptions
235
+ }
236
+ }
237
+
238
+ function ReactRouterApp() {
239
+ const navigate = useNavigate()
240
+
241
+ return (
242
+ <RouterProvider navigate={navigate} useHref={useHref}>
243
+ {/* ...application code */}
244
+ <Routes>
245
+ <Route path="/" element={<HomePage />} />
246
+ {/* ...routes */}
247
+ </Routes>
248
+ </RouterProvider>
249
+ )
250
+ }
251
+
252
+ export default App
253
+ ```
254
+
255
+ If your application is on a version below 5.3.x, you will have to use the `useHistory` hook instead. See our example [here](https://cultureamp.atlassian.net/wiki/spaces/TV/pages/4235920479/RFC+Kaizen+Link+component#Routing-with-RAC-Link-🧭).
256
+
257
+ 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).
258
+
259
+ ## Additional API options
260
+
261
+ 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.
262
+
263
+ <ArgTypes
264
+ of={exampleStories.Playground}
265
+ exclude={[
266
+ 'className',
267
+ 'children',
268
+ 'href',
269
+ 'target',
270
+ 'download',
271
+ 'routerOptions',
272
+ 'hasHiddenLabel',
273
+ 'size',
274
+ 'variant',
275
+ 'onPress',
276
+ 'icon',
277
+ 'iconPosition',
278
+ 'isFullWidth',
279
+ 'isDisabled',
280
+ ]}
281
+ />
@@ -0,0 +1,29 @@
1
+ import { Canvas, Meta, Controls } from '@storybook/blocks'
2
+ import { ResourceLinks, KAIOInstallation, LinkTo } from '~storybook/components'
3
+ import * as LinkButton from './LinkButton.doc.stories'
4
+
5
+ <Meta title="Components/LinkButton/Usage Guidelines" />
6
+
7
+ # LinkButton
8
+
9
+ Updated Dec 18, 2024
10
+
11
+ <ResourceLinks
12
+ sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/__actions__/Button/v3"
13
+ figma="https://www.figma.com/design/eZKEE5kXbEMY3lx84oz8iN/%F0%9F%92%9C-Heart-UI-Kit?node-id=1929-17364"
14
+ apiSpecification="/?path=/docs/actions-linkbutton-linkbutton-v3-api-specification--docs"
15
+ />
16
+
17
+ <KAIOInstallation exportNames={'LinkButton'} />
18
+
19
+ ## Overview
20
+
21
+ `LinkButton` allows users to navigate to another page or resource. It shares the same visual styles and interaction states as the <LinkTo pageId="actions-button-button-v3-usage-guidelines--docs">Button</LinkTo> component, but is intended for navigational purposes and downloading documents.
22
+
23
+ <Canvas of={LinkButton.Playground} />
24
+
25
+ <Controls
26
+ of={LinkButton.Playground}
27
+ include={['href', 'variant', 'size', 'isDisabled', 'icon', 'iconPosition']}
28
+ className="mb-64"
29
+ />
@@ -0,0 +1,136 @@
1
+ import React from 'react'
2
+ import { type Meta, type StoryObj } from '@storybook/react'
3
+ import { VisuallyHidden } from '~components/VisuallyHidden'
4
+ import { Icon } from '~components/__rc__/Icon'
5
+ import { LinkButton } from '../index'
6
+
7
+ const meta = {
8
+ title: 'Components/LinkButton',
9
+ component: LinkButton,
10
+ args: {
11
+ children: 'Label',
12
+ href: '#link-button-clicked',
13
+ },
14
+ argTypes: {
15
+ icon: {
16
+ options: ['delete', 'arrow', 'plus'],
17
+ mapping: {
18
+ delete: <Icon isPresentational name="delete" />,
19
+ arrow: <Icon isPresentational name="arrow_forward" />,
20
+ add: <Icon isPresentational name="add" />,
21
+ },
22
+ },
23
+ },
24
+ } satisfies Meta<typeof LinkButton>
25
+
26
+ export default meta
27
+
28
+ type Story = StoryObj<typeof meta>
29
+
30
+ export const Playground: Story = {}
31
+
32
+ export const LinkButtonOpensInNewTab: Story = {
33
+ args: {
34
+ children: (
35
+ <>
36
+ Label
37
+ <VisuallyHidden> opens a new tab</VisuallyHidden>
38
+ </>
39
+ ),
40
+ href: 'https://www.google.com',
41
+ target: '_blank',
42
+ rel: 'noopener noreferrer',
43
+ icon: <Icon isPresentational name="open_in_new" shouldMirrorInRTL />,
44
+ iconPosition: 'end',
45
+ },
46
+ }
47
+
48
+ export const LinkButtonVariants: Story = {
49
+ render: ({ children: _, ...otherArgs }) => (
50
+ <>
51
+ <LinkButton {...otherArgs} variant="primary">
52
+ Primary
53
+ </LinkButton>
54
+ <LinkButton {...otherArgs} variant="secondary">
55
+ Secondary
56
+ </LinkButton>
57
+ <LinkButton {...otherArgs} variant="tertiary">
58
+ Tertiary
59
+ </LinkButton>
60
+ </>
61
+ ),
62
+ decorators: [
63
+ (Story) => (
64
+ <div className="flex gap-8">
65
+ <Story />
66
+ </div>
67
+ ),
68
+ ],
69
+ }
70
+
71
+ export const LinkButtonVariantsReversed: Story = {
72
+ ...LinkButtonVariants,
73
+ parameters: {
74
+ reverseColors: true,
75
+ },
76
+ }
77
+
78
+ export const LinkButtonSizes: Story = {
79
+ render: (args) => (
80
+ <>
81
+ <LinkButton {...args} size="small">
82
+ Small
83
+ </LinkButton>
84
+ <LinkButton {...args} size="medium">
85
+ Medium
86
+ </LinkButton>
87
+ <LinkButton {...args} size="large">
88
+ Large
89
+ </LinkButton>
90
+ </>
91
+ ),
92
+ decorators: [
93
+ (Story) => (
94
+ <div className="flex gap-8 items-center">
95
+ <Story />
96
+ </div>
97
+ ),
98
+ ],
99
+ }
100
+
101
+ export const LinkButtonWithIconStart: Story = {
102
+ args: {
103
+ icon: <Icon isPresentational name="delete" />,
104
+ },
105
+ }
106
+
107
+ export const LinkButtonWithIconEnd: Story = {
108
+ args: {
109
+ icon: <Icon isPresentational name="arrow_forward" shouldMirrorInRTL />,
110
+ iconPosition: 'end',
111
+ },
112
+ }
113
+
114
+ export const IconLinkButton: Story = {
115
+ args: {
116
+ children: 'Remove highlights from: May 8, 2024',
117
+ icon: <Icon isPresentational name="delete" />,
118
+ hasHiddenLabel: true,
119
+ },
120
+ }
121
+
122
+ export const DownloadIconButton: Story = {
123
+ args: {
124
+ children: 'Download the kaizen design system badge',
125
+ href: './static/media/kaizen-badge.svg',
126
+ icon: <Icon isPresentational name="download" />,
127
+ hasHiddenLabel: true,
128
+ download: true,
129
+ },
130
+ }
131
+
132
+ export const LinkButtonFullWidth: Story = {
133
+ args: {
134
+ isFullWidth: true,
135
+ },
136
+ }
@@ -0,0 +1,80 @@
1
+ import React from 'react'
2
+ import { type Meta, type StoryObj } from '@storybook/react'
3
+ import { expect, userEvent, waitFor, within } from '@storybook/test'
4
+ import { VisuallyHidden } from '~components/VisuallyHidden'
5
+ import { Icon } from '~components/__rc__/Icon'
6
+ import { LinkButton } from '../index'
7
+
8
+ const meta = {
9
+ title: 'Components/LinkButton/LinkButton 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>{isFocusVisible ? ' is focused' : ' is unfocused'}</VisuallyHidden>
45
+ <Icon
46
+ name={isFocusVisible ? 'thumb_up' : 'thumb_down'}
47
+ isPresentational
48
+ isFilled={true}
49
+ className="ms-8 [--icon-size:16]"
50
+ />
51
+ </>
52
+ )}
53
+ </LinkButton>
54
+ ),
55
+ play: async ({ canvasElement, step }) => {
56
+ const canvas = within(canvasElement.parentElement!)
57
+ const linkButton = canvas.getByRole('link')
58
+
59
+ await step('link icon reflects unfocused state', async () => {
60
+ await waitFor(() => expect(linkButton).toHaveAccessibleName('Label is unfocused'))
61
+ })
62
+
63
+ await step('focus on link and update icon', async () => {
64
+ await userEvent.tab()
65
+ await waitFor(() => expect(linkButton).toHaveAccessibleName('Label is focused'))
66
+ })
67
+ },
68
+ }
69
+
70
+ export const RACRenderPropsWithClassName: Story = {
71
+ args: {
72
+ className: ({ isFocusVisible }) => (isFocusVisible ? '!bg-gray-300' : ''),
73
+ },
74
+ play: async ({ canvasElement }) => {
75
+ const canvas = within(canvasElement.parentElement!)
76
+ const linkButton = canvas.getByRole('link')
77
+ await linkButton.focus()
78
+ expect(linkButton).toHaveClass('!bg-gray-300')
79
+ },
80
+ }