@kaizen/components 0.0.0-canary-package-bundler-v2-20241113055116 → 0.0.0-canary-link-button-wip-canary-20241121010628

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 (99) hide show
  1. package/codemods/README.md +16 -1
  2. package/codemods/upgradeIconV1/getNewIconPropsFromOldIconName.ts +9 -9
  3. package/dist/cjs/EmptyState/EmptyState.cjs +15 -17
  4. package/dist/cjs/EmptyState/EmptyState.module.css.cjs +20 -0
  5. package/dist/cjs/Filter/FilterBar/FilterBar.cjs +1 -3
  6. package/dist/cjs/Filter/FilterBar/FilterBar.module.css.cjs +0 -1
  7. package/dist/cjs/GuidanceBlock/GuidanceBlock.cjs +1 -1
  8. package/dist/cjs/GuidanceBlock/GuidanceBlock.module.css.cjs +28 -0
  9. package/dist/cjs/__actions__/Button/v3/Button.cjs +43 -8
  10. package/dist/cjs/__actions__/Button/v3/Button.module.css.cjs +21 -0
  11. package/dist/cjs/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.cjs +37 -0
  12. package/dist/cjs/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.module.css.cjs +9 -0
  13. package/dist/cjs/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.cjs +35 -0
  14. package/dist/cjs/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.module.css.cjs +8 -0
  15. package/dist/cjs/__overlays__/Tooltip/v3/Tooltip.cjs +2 -2
  16. package/dist/esm/Calendar/CalendarPopover/CalendarPopover.mjs +1 -1
  17. package/dist/esm/EmptyState/EmptyState.mjs +16 -18
  18. package/dist/esm/EmptyState/EmptyState.module.css.mjs +18 -0
  19. package/dist/esm/Filter/FilterBar/FilterBar.mjs +1 -3
  20. package/dist/esm/Filter/FilterBar/FilterBar.module.css.mjs +0 -1
  21. package/dist/esm/Filter/FilterMultiSelect/subcomponents/ListBox/ListBox.mjs +1 -1
  22. package/dist/esm/GuidanceBlock/GuidanceBlock.mjs +1 -1
  23. package/dist/esm/GuidanceBlock/GuidanceBlock.module.css.mjs +26 -0
  24. package/dist/esm/MultiSelect/subcomponents/Popover/Popover.mjs +1 -1
  25. package/dist/esm/RichTextEditor/RichTextEditor/RichTextEditor.mjs +1 -1
  26. package/dist/esm/RichTextEditor/RichTextEditor/schema.mjs +1 -1
  27. package/dist/esm/RichTextEditor/utils/schema/nodes.mjs +1 -1
  28. package/dist/esm/TimeField/TimeField.mjs +1 -1
  29. package/dist/esm/__actions__/Button/v3/Button.mjs +44 -9
  30. package/dist/esm/__actions__/Button/v3/Button.module.css.mjs +19 -0
  31. package/dist/esm/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.mjs +28 -0
  32. package/dist/esm/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.module.css.mjs +7 -0
  33. package/dist/esm/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.mjs +26 -0
  34. package/dist/esm/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.module.css.mjs +6 -0
  35. package/dist/esm/__overlays__/Tooltip/v1/Tooltip.mjs +1 -1
  36. package/dist/esm/__overlays__/Tooltip/v3/Tooltip.mjs +1 -1
  37. package/dist/styles.css +568 -488
  38. package/dist/types/EmptyState/EmptyState.d.ts +2 -1
  39. package/dist/types/__actions__/Button/v3/Button.d.ts +17 -4
  40. package/dist/types/__actions__/Button/v3/index.d.ts +1 -0
  41. package/dist/types/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.d.ts +11 -0
  42. package/dist/types/__actions__/Button/v3/subcomponents/ButtonContent/index.d.ts +1 -0
  43. package/dist/types/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.d.ts +5 -0
  44. package/dist/types/__actions__/Button/v3/subcomponents/PendingContent/index.d.ts +1 -0
  45. package/dist/types/__actions__/Button/v3/subcomponents/index.d.ts +2 -0
  46. package/dist/types/__actions__/Button/v3/types.d.ts +21 -0
  47. package/dist/types/__actions__/LinkButton/index.d.ts +1 -0
  48. package/dist/types/__actions__/LinkButton/v3/LinkButton.d.ts +11 -0
  49. package/dist/types/__actions__/LinkButton/v3/index.d.ts +1 -0
  50. package/package.json +8 -8
  51. package/src/EmptyState/EmptyState.module.css +114 -0
  52. package/src/EmptyState/EmptyState.tsx +18 -20
  53. package/src/EmptyState/_docs/EmptyState.stickersheet.stories.tsx +55 -39
  54. package/src/Filter/FilterBar/FilterBar.module.css +0 -4
  55. package/src/Filter/FilterBar/FilterBar.tsx +12 -14
  56. package/src/GuidanceBlock/{GuidanceBlock.module.scss → GuidanceBlock.module.css} +60 -114
  57. package/src/GuidanceBlock/GuidanceBlock.tsx +1 -1
  58. package/src/__actions__/Button/v3/Button.module.css +235 -0
  59. package/src/__actions__/Button/v3/Button.tsx +95 -29
  60. package/src/__actions__/Button/v3/_docs/Button--api-specification.mdx +151 -0
  61. package/src/__actions__/Button/v3/_docs/Button--usage-guidelines.mdx +30 -0
  62. package/src/__actions__/Button/v3/_docs/Button.docs.stories.tsx +112 -50
  63. package/src/__actions__/Button/v3/_docs/Button.spec.stories.tsx +80 -120
  64. package/src/__actions__/Button/v3/_docs/Button.stickersheet.stories.tsx +183 -81
  65. package/src/__actions__/Button/v3/index.ts +1 -0
  66. package/src/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.module.css +19 -0
  67. package/src/__actions__/Button/v3/subcomponents/ButtonContent/ButtonContent.tsx +40 -0
  68. package/src/__actions__/Button/v3/subcomponents/ButtonContent/index.ts +1 -0
  69. package/src/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.module.css +16 -0
  70. package/src/__actions__/Button/v3/subcomponents/PendingContent/PendingContent.tsx +28 -0
  71. package/src/__actions__/Button/v3/subcomponents/PendingContent/index.ts +1 -0
  72. package/src/__actions__/Button/v3/subcomponents/index.ts +2 -0
  73. package/src/__actions__/Button/v3/types.ts +25 -0
  74. package/src/__actions__/LinkButton/index.ts +1 -0
  75. package/src/__actions__/LinkButton/v3/LinkButton.module.css +4 -0
  76. package/src/__actions__/LinkButton/v3/LinkButton.tsx +71 -0
  77. package/src/__actions__/LinkButton/v3/_docs/LinkButton--api-specification.mdx +200 -0
  78. package/src/__actions__/LinkButton/v3/_docs/LinkButton.doc.stories.tsx +131 -0
  79. package/src/__actions__/LinkButton/v3/_docs/LinkButton.spec.stories.tsx +100 -0
  80. package/src/__actions__/LinkButton/v3/index.ts +1 -0
  81. package/src/__actions__/Menu/v3/_docs/Menu.docs.stories.tsx +54 -18
  82. package/src/__actions__/Menu/v3/_docs/Menu.spec.stories.tsx +30 -10
  83. package/src/__actions__/Menu/v3/_docs/Menu.stories.tsx +12 -4
  84. package/src/__future__/Icon/_docs/Icon.docs.stories.tsx +7 -7
  85. package/src/__overlays__/Tooltip/v3/Tooltip.tsx +1 -1
  86. package/src/__overlays__/Tooltip/v3/_docs/Tooltip.spec.stories.tsx +2 -0
  87. package/dist/cjs/EmptyState/EmptyState.module.scss.cjs +0 -23
  88. package/dist/cjs/GuidanceBlock/GuidanceBlock.module.scss.cjs +0 -33
  89. package/dist/cjs/__actions__/Button/v3/Button.module.scss.cjs +0 -9
  90. package/dist/esm/EmptyState/EmptyState.module.scss.mjs +0 -21
  91. package/dist/esm/GuidanceBlock/GuidanceBlock.module.scss.mjs +0 -31
  92. package/dist/esm/__actions__/Button/v3/Button.module.scss.mjs +0 -7
  93. package/src/EmptyState/EmptyState.module.scss +0 -177
  94. package/src/EmptyState/EmptyState.spec.tsx +0 -48
  95. package/src/EmptyState/_mixins.scss +0 -44
  96. package/src/__actions__/Button/v3/Button.module.scss +0 -104
  97. package/src/__actions__/Button/v3/_docs/ApiSpecification.mdx +0 -173
  98. package/src/__actions__/Button/v3/_docs/Button.mdx +0 -41
  99. 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,4 @@
1
+ .linkButton {
2
+ /* Reset */
3
+ text-decoration: inherit;
4
+ }
@@ -0,0 +1,71 @@
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
+ {...otherProps}
53
+ >
54
+ {racStateProps => {
55
+ const childIsFunction = typeof children === "function"
56
+
57
+ return (
58
+ <ButtonContent
59
+ size={size}
60
+ icon={icon}
61
+ iconPosition={iconPosition}
62
+ hasHiddenLabel={hasHiddenLabel}
63
+ >
64
+ {childIsFunction ? children(racStateProps) : children}
65
+ </ButtonContent>
66
+ )
67
+ }}
68
+ </RACLink>
69
+ )
70
+ }
71
+ )
@@ -0,0 +1,200 @@
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
+ ### Variants
107
+
108
+ `LinkButton` supports the following variants: `primary`, `secondary` and `tertiary`. If the `variant` prop is not specified, the default will be `primary`.
109
+
110
+ <Canvas of={exampleStories.LinkButtonVariants} />
111
+
112
+ Reversed variants are handled via the `ReversedColors` Provider.
113
+
114
+ <DocsStory of={exampleStories.LinkButtonVariantsReversed} expanded={false} />
115
+
116
+ To enable the reversed theme, you will need to wrap the component or application in the `ReversedColors` provider, ie:
117
+
118
+ ```tsx
119
+ import { RouterProvider } from "react-aria-components"
120
+ import { Button } from "@kaizen/components/v3/actions"
121
+ import { ReversedColors } from "@kaizen/components/v3/utilities"
122
+ // application code
123
+
124
+ return (
125
+ <ReversedColors isReversed={true}>
126
+ <LinkButton {...LinkbuttonProps} />
127
+ </ReversedColors>
128
+ )
129
+ ```
130
+
131
+ ### Sizes
132
+
133
+ LinkButton supports the following sizes: `small`, `medium` and `large`. If the `size` prop is not specified, the default will be `medium`.
134
+
135
+ <Canvas of={exampleStories.LinkButtonSizes} />
136
+
137
+ ### `onPress`
138
+
139
+ TODO: revamp this section: One key change to the `LinkButton`&apos;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).
140
+
141
+ Functionally this does not change the way we pass actions into `LinkButton`. Consumers can safely replace `onClick` with `onPress` without any additional changes, ie:
142
+
143
+ ```tsx
144
+ <LinkButton label="Submit" onClick={e => sumbit(e)}/>
145
+ ```
146
+
147
+ Can safely be replaced with the following:
148
+
149
+ ```tsx
150
+ <LinkButton onPress={e => submit(e)}>
151
+ Submit
152
+ </LinkButton>
153
+ ```
154
+
155
+
156
+ ### Button content and children
157
+
158
+ 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.
159
+
160
+ ```tsx
161
+ <LinkButton variant="secondary" href="#link">
162
+ Label
163
+ </LinkButton>
164
+ ```
165
+
166
+ 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).
167
+
168
+ ### Icons and positioning
169
+
170
+ 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.
171
+
172
+ <Canvas of={exampleStories.LinkButtonWithIconStart} />
173
+
174
+ 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.
175
+
176
+ <Canvas of={exampleStories.LinkButtonWithIconEnd} />
177
+
178
+
179
+ ### Icon-only `LinkButton` and `hasHiddenLabel`
180
+
181
+ 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.
182
+
183
+ <Canvas of={exampleStories.IconLinkButton} />
184
+
185
+ 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).
186
+
187
+
188
+ ### Full width LinkButtons
189
+
190
+ If a `LinkButton` is statically the full width of a container you can use the `isFullWidth` property.
191
+
192
+ <Canvas of={exampleStories.LinkButtonFullWidth} />
193
+
194
+ 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>`.
195
+
196
+ ## Additional API options
197
+
198
+ 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.
199
+
200
+ <ArgTypes of={exampleStories.Playground} exclude={["className", "children", "href", "target", "download", "routerOptions", "hasHiddenLabel", "size", "variant", "onPress", "icon", "iconPosition", "isFullWidth", "isDisabled" ]}/>
@@ -0,0 +1,131 @@
1
+ import React from "react"
2
+ // import { action } from "@storybook/addon-actions"
3
+ import { Meta, StoryObj } from "@storybook/react"
4
+ import { Badge } from "~components/Badge"
5
+ import { Icon } from "~components/__future__"
6
+ import { ReversedColors } from "~components/__utilities__/v3"
7
+ import { LinkButton } from "../index"
8
+
9
+ const meta = {
10
+ title: "Actions/LinkButton/LinkButton (v3)",
11
+ component: LinkButton,
12
+ args: {
13
+ children: "Label",
14
+ href: "#link-button-clicked",
15
+ target: "_blank",
16
+ },
17
+ } satisfies Meta<typeof LinkButton>
18
+
19
+ export default meta
20
+
21
+ type Story = StoryObj<typeof meta>
22
+
23
+ export const Playground: Story = {}
24
+
25
+ export const LinkButtonVariants: Story = {
26
+ render: args => (
27
+ <>
28
+ <LinkButton {...args} variant="primary" />
29
+ <LinkButton {...args} variant="secondary" />
30
+ <LinkButton {...args} variant="tertiary" />
31
+ </>
32
+ ),
33
+ decorators: [
34
+ Story => (
35
+ <div className="flex gap-8">
36
+ <Story />
37
+ </div>
38
+ ),
39
+ ],
40
+ }
41
+
42
+ export const LinkButtonVariantsReversed: Story = {
43
+ render: args => (
44
+ <ReversedColors isReversed={true}>
45
+ <LinkButton {...args} variant="primary" />
46
+ <LinkButton {...args} variant="secondary" />
47
+ <LinkButton {...args} variant="tertiary" />
48
+ </ReversedColors>
49
+ ),
50
+ parameters: {
51
+ reverseColors: true,
52
+ },
53
+ decorators: [
54
+ Story => (
55
+ <div className="flex gap-8 bg-purple-700 p-16">
56
+ <Story />
57
+ </div>
58
+ ),
59
+ ],
60
+ }
61
+
62
+ export const LinkButtonSizes: Story = {
63
+ render: args => (
64
+ <>
65
+ <LinkButton {...args} size="small" />
66
+ <LinkButton {...args} size="medium" />
67
+ <LinkButton {...args} size="large" />
68
+ </>
69
+ ),
70
+ decorators: [
71
+ Story => (
72
+ <div className="[&>*]:ms-8">
73
+ <Story />
74
+ </div>
75
+ ),
76
+ ],
77
+ }
78
+
79
+ export const LinkButtonWithIconStart: Story = {
80
+ args: {
81
+ icon: <Icon isPresentational name="delete" />,
82
+ },
83
+ }
84
+
85
+ export const LinkButtonWithIconEnd: Story = {
86
+ args: {
87
+ icon: <Icon isPresentational name="arrow_forward" shouldMirrorInRTL />,
88
+ iconPosition: "end",
89
+ },
90
+ }
91
+
92
+ export const IconLinkButton: Story = {
93
+ args: {
94
+ children: "Remove highlights from: May 8, 2024",
95
+ icon: <Icon isPresentational name="delete" />,
96
+ hasHiddenLabel: true,
97
+ },
98
+ }
99
+
100
+ export const ReversedLinkButton: Story = {
101
+ parameters: {
102
+ reverseColors: true,
103
+ docs: {
104
+ source: {
105
+ code: `<ReversedColors isReversed={true}>
106
+ <LinkButton>Label</LinkButton>
107
+ </ReversedColors>
108
+ `,
109
+ },
110
+ },
111
+ },
112
+ }
113
+
114
+ export const LinkButtonFullWidth: Story = {
115
+ args: {
116
+ isFullWidth: true,
117
+ },
118
+ }
119
+
120
+ export const LinkButtonWithBadge: Story = {
121
+ args: {
122
+ children: (
123
+ <>
124
+ Label
125
+ <Badge classNameOverride="ms-8" size="small">
126
+ 3
127
+ </Badge>
128
+ </>
129
+ ),
130
+ },
131
+ }
@@ -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
+ */
@@ -0,0 +1 @@
1
+ export * from "./LinkButton"
@@ -41,8 +41,12 @@ const DefaultMenuItems = (): ReactNode => (
41
41
  export const Actions: Story = {
42
42
  render: ({ defaultOpen: _, ...args }) => (
43
43
  <MenuTrigger {...args}>
44
- <Button className="[--icon-size:24]">
45
- <Icon name="more_horiz" alt="Additional actions" />
44
+ <Button
45
+ size="large"
46
+ icon={<Icon name="more_horiz" isPresentational />}
47
+ hasHiddenLabel
48
+ >
49
+ Additional actions
46
50
  </Button>
47
51
  <Popover>
48
52
  <Menu>
@@ -59,8 +63,12 @@ export const Actions: Story = {
59
63
  export const ItemsDo: Story = {
60
64
  render: ({ defaultOpen, ...args }) => (
61
65
  <MenuTrigger defaultOpen={defaultOpen} {...args}>
62
- <Button className="[--icon-size:24]">
63
- <Icon name="more_horiz" alt="Additional actions" />
66
+ <Button
67
+ size="large"
68
+ icon={<Icon name="more_horiz" isPresentational />}
69
+ hasHiddenLabel
70
+ >
71
+ Additional actions
64
72
  </Button>
65
73
  <Popover>
66
74
  <Menu>
@@ -74,8 +82,12 @@ export const ItemsDo: Story = {
74
82
  export const ItemsDont: Story = {
75
83
  render: ({ defaultOpen, ...args }) => (
76
84
  <MenuTrigger defaultOpen={defaultOpen} {...args}>
77
- <Button className="[--icon-size:24]">
78
- <Icon name="more_horiz" alt="Additional actions" />
85
+ <Button
86
+ size="large"
87
+ icon={<Icon name="more_horiz" isPresentational />}
88
+ hasHiddenLabel
89
+ >
90
+ Additional actions
79
91
  </Button>
80
92
  <Popover>
81
93
  <Menu>
@@ -171,8 +183,12 @@ export const LabelDont: Story = {
171
183
  export const IconsDont: Story = {
172
184
  render: ({ defaultOpen, ...args }) => (
173
185
  <MenuTrigger defaultOpen={defaultOpen}>
174
- <Button className="[--icon-size:24]">
175
- <Icon name="more_horiz" alt="Additional actions" />
186
+ <Button
187
+ size="large"
188
+ icon={<Icon name="more_horiz" isPresentational />}
189
+ hasHiddenLabel
190
+ >
191
+ Additional actions
176
192
  </Button>
177
193
  <Popover>
178
194
  <Menu {...args}>
@@ -193,8 +209,12 @@ export const IconsDont: Story = {
193
209
  export const MenuItemLabelsDont: Story = {
194
210
  render: ({ defaultOpen, ...args }) => (
195
211
  <MenuTrigger defaultOpen={defaultOpen}>
196
- <Button className="[--icon-size:24]">
197
- <Icon name="more_horiz" alt="Additional actions" />
212
+ <Button
213
+ size="large"
214
+ icon={<Icon name="more_horiz" isPresentational />}
215
+ hasHiddenLabel
216
+ >
217
+ Additional actions
198
218
  </Button>
199
219
  <Popover>
200
220
  <Menu {...args}>
@@ -210,8 +230,12 @@ export const MenuItemLabelsDont: Story = {
210
230
  export const SentenceCaseDo: Story = {
211
231
  render: ({ defaultOpen, ...args }) => (
212
232
  <MenuTrigger defaultOpen={defaultOpen}>
213
- <Button className="[--icon-size:24]">
214
- <Icon name="more_horiz" alt="Additional actions" />
233
+ <Button
234
+ size="large"
235
+ icon={<Icon name="more_horiz" isPresentational />}
236
+ hasHiddenLabel
237
+ >
238
+ Additional actions
215
239
  </Button>
216
240
  <Popover>
217
241
  <Menu {...args}>
@@ -227,8 +251,12 @@ export const SentenceCaseDo: Story = {
227
251
  export const SentenceCaseDont: Story = {
228
252
  render: ({ defaultOpen, ...args }) => (
229
253
  <MenuTrigger defaultOpen={defaultOpen}>
230
- <Button className="[--icon-size:24]">
231
- <Icon name="more_horiz" alt="Additional actions" />
254
+ <Button
255
+ size="large"
256
+ icon={<Icon name="more_horiz" isPresentational />}
257
+ hasHiddenLabel
258
+ >
259
+ Additional actions
232
260
  </Button>
233
261
  <Popover>
234
262
  <Menu {...args}>
@@ -244,8 +272,12 @@ export const SentenceCaseDont: Story = {
244
272
  export const ElipsesDo: Story = {
245
273
  render: ({ defaultOpen, ...args }) => (
246
274
  <MenuTrigger defaultOpen={defaultOpen}>
247
- <Button className="[--icon-size:24]">
248
- <Icon name="more_horiz" alt="Additional actions" />
275
+ <Button
276
+ size="large"
277
+ icon={<Icon name="more_horiz" isPresentational />}
278
+ hasHiddenLabel
279
+ >
280
+ Additional actions
249
281
  </Button>
250
282
  <Popover>
251
283
  <Menu {...args}>
@@ -261,8 +293,12 @@ export const ElipsesDo: Story = {
261
293
  export const ElipsesDont: Story = {
262
294
  render: ({ defaultOpen, ...args }) => (
263
295
  <MenuTrigger defaultOpen={defaultOpen}>
264
- <Button className="[--icon-size:24]">
265
- <Icon name="more_horiz" alt="Additional actions" />
296
+ <Button
297
+ size="large"
298
+ icon={<Icon name="more_horiz" isPresentational />}
299
+ hasHiddenLabel
300
+ >
301
+ Additional actions
266
302
  </Button>
267
303
  <Popover>
268
304
  <Menu {...args}>