agroptima-design-system 1.1.0-beta.1 → 1.1.1-beta.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.
@@ -1,6 +1,13 @@
1
+ // This file has been automatically migrated to valid ESM format by Storybook.
1
2
  import type { StorybookConfig } from '@storybook/nextjs'
3
+ import { createRequire } from 'node:module'
4
+ import { fileURLToPath } from 'node:url'
2
5
  import { dirname, join, resolve } from 'path'
3
6
 
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = dirname(__filename)
9
+ const require = createRequire(import.meta.url)
10
+
4
11
  /**
5
12
  * This function is used to resolve the absolute path of a package.
6
13
  * It is needed in projects that use Yarn PnP or are set up within a monorepo.
@@ -2,7 +2,7 @@ import { addons } from 'storybook/manager-api'
2
2
  import {
3
3
  defaultConfig,
4
4
  type TagBadgeParameters,
5
- } from 'storybook-addon-tag-badges'
5
+ } from 'storybook-addon-tag-badges/manager-helpers'
6
6
 
7
7
  addons.setConfig({
8
8
  tagBadges: [
package/jest.setup.ts CHANGED
@@ -1 +1,9 @@
1
1
  import '@testing-library/jest-dom'
2
+
3
+ const mockIntersectionObserver = jest.fn()
4
+ mockIntersectionObserver.mockReturnValue({
5
+ observe: jest.fn(),
6
+ unobserve: jest.fn(),
7
+ disconnect: jest.fn(),
8
+ })
9
+ window.IntersectionObserver = mockIntersectionObserver
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroptima-design-system",
3
- "version": "1.1.0-beta.1",
3
+ "version": "1.1.1-beta.0",
4
4
  "scripts": {
5
5
  "dev": "npm run storybook",
6
6
  "storybook": "storybook dev -p 6006 --ci",
@@ -25,12 +25,12 @@
25
25
  "devDependencies": {
26
26
  "@eslint/eslintrc": "^3.3.3",
27
27
  "@eslint/js": "^9.37.0",
28
- "@storybook/addon-a11y": "^9.1.16",
29
- "@storybook/addon-designs": "^10.0.2",
30
- "@storybook/addon-docs": "^9.1.16",
31
- "@storybook/addon-links": "^9.1.16",
32
- "@storybook/addon-themes": "^9.1.16",
33
- "@storybook/nextjs": "^9.1.16",
28
+ "@storybook/addon-a11y": "^10.1.11",
29
+ "@storybook/addon-designs": "^11.1.1",
30
+ "@storybook/addon-docs": "^10.1.11",
31
+ "@storybook/addon-links": "^10.1.11",
32
+ "@storybook/addon-themes": "^10.1.11",
33
+ "@storybook/nextjs": "^10.1.11",
34
34
  "@svgr/webpack": "^8.1.0",
35
35
  "@testing-library/jest-dom": "^6.9.1",
36
36
  "@testing-library/react": "^16.3.0",
@@ -46,13 +46,13 @@
46
46
  "eslint-config-prettier": "^10.1.8",
47
47
  "eslint-plugin-prettier": "^5.5.4",
48
48
  "eslint-plugin-simple-import-sort": "^12.1.1",
49
- "eslint-plugin-storybook": "^9.1.16",
49
+ "eslint-plugin-storybook": "^10.1.11",
50
50
  "jest": "^30.2.0",
51
51
  "jest-axe": "^10.0.0",
52
52
  "jest-environment-jsdom": "^30.2.0",
53
53
  "prettier": "^3.7.4",
54
- "storybook": "^9.1.16",
55
- "storybook-addon-tag-badges": "^2.0.4",
54
+ "storybook": "^10.1.11",
55
+ "storybook-addon-tag-badges": "^3.0.4",
56
56
  "ts-node": "^10.9.2",
57
57
  "typescript": "^5.9.3"
58
58
  },
@@ -12,6 +12,7 @@
12
12
  padding: config.$space-3x;
13
13
  gap: config.$space-3x;
14
14
  border-radius: config.$corner-radius-xxs;
15
+ border: none;
15
16
  text-decoration: none;
16
17
  cursor: default;
17
18
  &:hover {
@@ -1,37 +1,31 @@
1
1
  import './CardMenu.scss'
2
- import type { LinkProps as NextLinkProps } from 'next/link'
3
- import Link from 'next/link'
4
2
  import { classNames } from '../../utils/classNames'
3
+ import { BaseButton, type BaseButtonProps } from '../Button'
5
4
  import type { IconType } from '../Icon'
6
5
  import { Icon } from '../Icon'
7
6
 
8
7
  export type Variant = 'primary'
9
8
 
10
- type LinkProps = NextLinkProps & React.AnchorHTMLAttributes<HTMLAnchorElement>
11
- export interface CardMenuOptionProps extends LinkProps {
12
- id?: string
9
+ interface CustomProps {
13
10
  variant?: Variant
14
11
  icon: IconType
15
12
  title: string
16
13
  description?: string
17
- disabled?: boolean
18
- href: string
19
14
  active?: boolean
20
15
  error?: boolean
21
16
  }
22
17
 
18
+ export type CardMenuOptionProps = CustomProps & BaseButtonProps
19
+
23
20
  export function CardMenuOption({
24
- id,
25
21
  variant = 'primary',
26
22
  className,
27
23
  icon,
28
24
  title,
29
25
  description,
30
- disabled,
31
- href,
26
+ disabled = false,
32
27
  active,
33
28
  error,
34
- prefetch = false,
35
29
  ...props
36
30
  }: CardMenuOptionProps): React.JSX.Element {
37
31
  const cssClasses = classNames('card-menu-option', variant, className, {
@@ -41,12 +35,11 @@ export function CardMenuOption({
41
35
  })
42
36
 
43
37
  return (
44
- <Link
38
+ <BaseButton
45
39
  role="menuitem"
46
40
  className={cssClasses}
47
- href={disabled ? '#' : href}
48
- aria-disabled={disabled}
49
- prefetch={prefetch}
41
+ disabled={disabled}
42
+ type="button"
50
43
  {...props}
51
44
  >
52
45
  <div className="left">
@@ -59,6 +52,6 @@ export function CardMenuOption({
59
52
  <div className="right">
60
53
  <Icon name="AngleRight" className={variant} />
61
54
  </div>
62
- </Link>
55
+ </BaseButton>
63
56
  )
64
57
  }
@@ -47,7 +47,7 @@
47
47
  cursor: default;
48
48
  }
49
49
 
50
- .checkbox-group {
50
+ .checkbox {
51
51
  margin-top: config.$space-1x;
52
52
  }
53
53
 
@@ -4,42 +4,48 @@
4
4
 
5
5
  $border-line: 1px solid var(--neutral-color-200);
6
6
 
7
- .divider {
7
+ .divider-container {
8
8
  @include mixins.icon-color(var(--primary-color-600));
9
9
  @include typography.body_bold;
10
10
  display: flex;
11
- align-items: center;
12
11
  gap: config.$space-3x;
13
12
 
14
- &-title {
13
+ .divider {
14
+ width: 100%;
15
15
  display: flex;
16
16
  align-items: center;
17
- }
18
-
19
- .short {
20
- width: config.$space-6x;
21
- margin-right: config.$space-3x;
22
- }
23
-
24
- .line {
25
- border: $border-line;
26
- }
27
-
28
- .long {
29
- flex: 1;
30
- }
31
-
32
- .icon {
33
- margin-right: config.$space-2x;
34
- }
35
-
36
- .link {
37
- color: var(--primary-color-600);
38
- text-decoration-line: underline;
39
- cursor: default;
40
- }
41
-
42
- .iconButton {
43
- margin-left: config.$space-3x;
17
+ gap: config.$space-3x;
18
+
19
+ &-title {
20
+ display: flex;
21
+ align-items: center;
22
+ }
23
+
24
+ .short {
25
+ width: config.$space-6x;
26
+ margin-right: config.$space-3x;
27
+ }
28
+
29
+ .line {
30
+ border: $border-line;
31
+ }
32
+
33
+ .long {
34
+ flex: 1;
35
+ }
36
+
37
+ .icon {
38
+ margin-right: config.$space-2x;
39
+ }
40
+
41
+ .link {
42
+ color: var(--primary-color-600);
43
+ text-decoration-line: underline;
44
+ cursor: default;
45
+ }
46
+
47
+ .iconButton {
48
+ margin-left: config.$space-3x;
49
+ }
44
50
  }
45
51
  }
@@ -27,16 +27,18 @@ export function Divider({
27
27
  const cssClasses = classNames('divider', variant, className)
28
28
 
29
29
  return (
30
- <div role="separator" className={cssClasses}>
31
- {title && (
32
- <div className="divider-title">
33
- <DividerIcon icon={icon} />
34
- <span onClick={onClick} className={classNames({ link: hasAction })}>
35
- {title}
36
- </span>
37
- </div>
38
- )}
39
- <div className="long line"></div>
30
+ <div className="divider-container">
31
+ <div role="separator" className={cssClasses}>
32
+ {title && (
33
+ <div className="divider-title">
34
+ <DividerIcon icon={icon} />
35
+ <span onClick={onClick} className={classNames({ link: hasAction })}>
36
+ {title}
37
+ </span>
38
+ </div>
39
+ )}
40
+ <div className="long line"></div>
41
+ </div>
40
42
  {children}
41
43
  </div>
42
44
  )
@@ -12,7 +12,7 @@ export interface DrawerProps extends React.ComponentPropsWithoutRef<'div'> {
12
12
  id: string
13
13
  variant?: Variant
14
14
  title: string
15
- buttons?: ButtonProps[]
15
+ buttons: ButtonProps[]
16
16
  onClose: () => void
17
17
  }
18
18
 
@@ -21,7 +21,7 @@ export function Drawer({
21
21
  className,
22
22
  variant = 'primary',
23
23
  title,
24
- buttons = [],
24
+ buttons,
25
25
  children,
26
26
  onClose,
27
27
  ...props
@@ -59,15 +59,13 @@ export function Drawer({
59
59
  {children}
60
60
  </div>
61
61
  </div>
62
- {buttons.length > 0 && (
63
- <div className="footer">
64
- <Actions>
65
- {buttons.map(({ ...button }) => (
66
- <Button key={button.label} {...button} />
67
- ))}
68
- </Actions>
69
- </div>
70
- )}
62
+ <div className="footer">
63
+ <Actions>
64
+ {buttons.map(({ ...button }) => (
65
+ <Button key={button.label} {...button} />
66
+ ))}
67
+ </Actions>
68
+ </div>
71
69
  </div>
72
70
  </div>
73
71
  )
@@ -201,7 +201,7 @@ export function InfiniteSelect<T extends { uid: string }>({
201
201
  icon="Search"
202
202
  className="search"
203
203
  />
204
- <ul role="listbox">
204
+ <ul role="listbox" aria-label="infinite-select-list">
205
205
  {items.map((item) => (
206
206
  <SelectItem
207
207
  multiple={false}
@@ -233,8 +233,9 @@ function LoadingItems({
233
233
  loaderRef: RefObject<HTMLDivElement | null>
234
234
  }) {
235
235
  if (!visible) return null
236
+
236
237
  return (
237
- <li>
238
+ <li role="option" aria-selected="false">
238
239
  <div ref={loaderRef} aria-label={label} className="loading">
239
240
  <Icon name="Loading" />
240
241
  </div>
@@ -56,9 +56,7 @@ import Settings from './settings.svg'
56
56
  import Show from './show.svg'
57
57
  import ShowOff from './show-off.svg'
58
58
  import Sorter from './sorter.svg'
59
- import TimelineCircleDefault from './timeline-circle-default.svg'
60
- import TimelineCircleSelected from './timeline-circle-selected.svg'
61
- import Traceability from './traceability.svg'
59
+ import TaxRule from './tax-rule.svg'
62
60
  import Upload from './upload.svg'
63
61
  import UserMenu from './user-menu.svg'
64
62
  import ValidateInvoice from './validate-invoice.svg'
@@ -123,9 +121,7 @@ export {
123
121
  Show,
124
122
  ShowOff,
125
123
  Sorter,
126
- TimelineCircleDefault,
127
- TimelineCircleSelected,
128
- Traceability,
124
+ TaxRule,
129
125
  Upload,
130
126
  UserMenu,
131
127
  ValidateInvoice,
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#tax-rule__a)"><path d="M4.375 8.75c-1.208 0-2.24-.427-3.094-1.281C.427 6.615 0 5.583 0 4.375s.427-2.24 1.281-3.094C2.135.427 3.167 0 4.375 0s2.24.427 3.094 1.281c.854.854 1.281 1.886 1.281 3.094s-.427 2.24-1.281 3.094c-.854.854-1.886 1.281-3.094 1.281Zm0-2.5c.52 0 .964-.182 1.328-.547.365-.364.547-.807.547-1.328 0-.52-.182-.964-.547-1.328A1.808 1.808 0 0 0 4.375 2.5c-.52 0-.964.182-1.328.547A1.808 1.808 0 0 0 2.5 4.375c0 .52.182.964.547 1.328.364.365.807.547 1.328.547ZM15.625 20c-1.208 0-2.24-.427-3.094-1.281-.854-.854-1.281-1.886-1.281-3.094s.427-2.24 1.281-3.094c.854-.854 1.886-1.281 3.094-1.281s2.24.427 3.094 1.281c.854.854 1.281 1.886 1.281 3.094s-.427 2.24-1.281 3.094c-.854.854-1.886 1.281-3.094 1.281Zm0-2.5c.52 0 .963-.182 1.328-.547.365-.364.547-.807.547-1.328 0-.52-.182-.963-.547-1.328a1.808 1.808 0 0 0-1.328-.547c-.52 0-.963.182-1.328.547a1.808 1.808 0 0 0-.547 1.328c0 .52.182.963.547 1.328.364.365.807.547 1.328.547ZM1.75 20 0 18.25 18.25 0 20 1.75 1.75 20Z" fill="#161C26"/></g><defs><clipPath id="tax-rule__a"><path fill="#fff" d="M0 0h20v20H0z"/></clipPath></defs></svg>
@@ -13,7 +13,4 @@ $z-modal: $z-alert-container + $above;
13
13
  $z-aside-desktop: $base + $above;
14
14
  $z-aside-mobile: $z-modal + $above;
15
15
 
16
- $z-header: $base + $above;
17
-
18
- $z-timeline-rail: $base;
19
- $z-timeline-circle: $base + $above;
16
+ $z-header: $base + $above;
@@ -24,7 +24,7 @@ export const Primary: Story = {
24
24
  render: () => (
25
25
  <AlertContainer>
26
26
  <Alert variant="success" id="success" text="This is a success alert" />
27
- <Alert variant="error" id="error" text="This is a error alert" />
27
+ <Alert variant="error" id="error" text="This is an error alert" />
28
28
  </AlertContainer>
29
29
  ),
30
30
  }
@@ -56,62 +56,70 @@ const meta = {
56
56
  export default meta
57
57
  type Story = StoryObj<typeof meta>
58
58
 
59
- export const Option: Story = {
59
+ export const LinkOption: Story = {
60
60
  render: () => (
61
- <CardMenuOption
62
- id="first-menu-option"
63
- href="http://fakelink.com"
64
- icon="Info"
65
- variant="primary"
66
- title="It's dangerous to go alone!"
67
- description="Take this 🗡️ and this 🛡️ and this 💣 and this 🏹 and this 🔪 and this 🐴 and this 🔫 and this 🔪"
68
- />
61
+ <CardMenu>
62
+ <CardMenuOption
63
+ id="first-menu-option"
64
+ href="http://fakelink.com"
65
+ icon="Info"
66
+ variant="primary"
67
+ title="It's dangerous to go alone!"
68
+ description="Take this 🗡️ and this 🛡️ and this 💣 and this 🏹 and this 🔪 and this 🐴 and this 🔫 and this 🔪"
69
+ />
70
+ </CardMenu>
69
71
  ),
70
72
  } as unknown as Story
71
73
 
72
- export const DisabledOption: Story = {
74
+ export const LinkDisabledOption: Story = {
73
75
  render: () => (
74
- <CardMenuOption
75
- id="first-menu-option"
76
- href="#"
77
- icon="Info"
78
- variant="primary"
79
- title="It's dangerous to go alone!"
80
- description="Take this 🗡️"
81
- disabled
82
- />
76
+ <CardMenu>
77
+ <CardMenuOption
78
+ id="first-menu-option"
79
+ href="#"
80
+ icon="Info"
81
+ variant="primary"
82
+ title="It's dangerous to go alone!"
83
+ description="Take this 🗡️"
84
+ disabled
85
+ />
86
+ </CardMenu>
83
87
  ),
84
88
  } as unknown as Story
85
89
 
86
- export const ActiveOption: Story = {
90
+ export const LinkActiveOption: Story = {
87
91
  render: () => (
88
- <CardMenuOption
89
- id="first-menu-option"
90
- href="#"
91
- icon="Info"
92
- variant="primary"
93
- title="It's dangerous to go alone!"
94
- description="Take this 🗡️"
95
- active
96
- />
92
+ <CardMenu>
93
+ <CardMenuOption
94
+ id="first-menu-option"
95
+ href="#"
96
+ icon="Info"
97
+ variant="primary"
98
+ title="It's dangerous to go alone!"
99
+ description="Take this 🗡️"
100
+ active
101
+ />
102
+ </CardMenu>
97
103
  ),
98
104
  } as unknown as Story
99
105
 
100
- export const ErrorOption: Story = {
106
+ export const LinkErrorOption: Story = {
101
107
  render: () => (
102
- <CardMenuOption
103
- id="first-menu-option"
104
- href="#"
105
- icon="Info"
106
- variant="primary"
107
- title="It's dangerous to go alone!"
108
- description="Take this 🗡️"
109
- error
110
- />
108
+ <CardMenu>
109
+ <CardMenuOption
110
+ id="first-menu-option"
111
+ href="#"
112
+ icon="Info"
113
+ variant="primary"
114
+ title="It's dangerous to go alone!"
115
+ description="Take this 🗡️"
116
+ error
117
+ />
118
+ </CardMenu>
111
119
  ),
112
120
  } as unknown as Story
113
121
 
114
- export const Menu: Story = {
122
+ export const LinkMenu: Story = {
115
123
  render: () => (
116
124
  <CardMenu>
117
125
  <CardMenuOption
@@ -142,3 +150,18 @@ export const Menu: Story = {
142
150
  </CardMenu>
143
151
  ),
144
152
  } as unknown as Story
153
+
154
+ export const ButtonOption: Story = {
155
+ render: () => (
156
+ <CardMenu>
157
+ <CardMenuOption
158
+ id="first-menu-option"
159
+ icon="Info"
160
+ variant="primary"
161
+ title="It's dangerous to go alone!"
162
+ description="Take this 🗡️ and this 🛡️ and this 💣 and this 🏹 and this 🔪 and this 🐴 and this 🔫 and this 🔪"
163
+ onClick={() => alert('Clicked!')}
164
+ />
165
+ </CardMenu>
166
+ ),
167
+ } as unknown as Story
@@ -4,21 +4,32 @@ import { Meta } from "@storybook/addon-docs/blocks";
4
4
 
5
5
  # Changelog
6
6
 
7
- ## 1.1.0
7
+ ## 1.1.1
8
+
9
+ * Update to Storybook 10
10
+ * Add custom `composeStories` helper workaround because of Storybook 10 breaking changes
11
+ * Add missing components to a11y tests
12
+
13
+ ## 1.0.9
14
+
15
+ * Add TaxRule icon
16
+
17
+
18
+ ## 1.0.8
19
+
20
+ * CardMenuOption can be a button or a link
8
21
 
9
- * Add Timeline, Milestone and LoadMoreMilestonesButton components
10
- * On Drawer component, `buttons` prop is optional
11
- * Add Traceability icon
12
- * Fix Checkbox vertical displacement on CardsTable component
13
22
 
14
23
  ## 1.0.7
15
24
 
16
25
  * Add link styles to Alert component
17
26
 
27
+
18
28
  ## 1.0.6
19
29
 
20
30
  * Restore select `disabled` styles
21
31
 
32
+
22
33
  ## 1.0.5
23
34
 
24
35
  * Rechecking npm security changes
@@ -19,4 +19,16 @@ describe('CardMenuOption', () => {
19
19
  expect(getByText(/this sword/i)).toBeInTheDocument()
20
20
  expect(getAllByRole('img')[0].title).toBe('Info')
21
21
  })
22
+ it('renders a button if dont have href', () => {
23
+ const { getByRole } = render(
24
+ <CardMenuOption
25
+ id="option-one"
26
+ icon="Info"
27
+ title="It's dangerous to go alone!"
28
+ description="Take this sword!"
29
+ />,
30
+ )
31
+
32
+ expect(getByRole('menuitem')).not.toHaveAttribute('href')
33
+ })
22
34
  })
@@ -1,7 +1,7 @@
1
- import { composeStories } from '@storybook/react'
2
1
  import { act, render } from '@testing-library/react'
3
2
  import { axe, toHaveNoViolations } from 'jest-axe'
4
3
  import * as components from './library'
4
+ import { composeStories } from './utils/storybookHelpers'
5
5
 
6
6
  expect.extend(toHaveNoViolations)
7
7
 
package/tests/library.ts CHANGED
@@ -1,13 +1,38 @@
1
+ export * as ActionsMenu from '../src/stories/ActionsMenu.stories'
2
+ export * as Alert from '../src/stories/Alert.stories'
3
+ export * as AlertContainer from '../src/stories/AlertContainer.stories'
4
+ export * as Badge from '../src/stories/Badge.stories'
1
5
  export * as Button from '../src/stories/Button.stories'
6
+ export * as ButtonGroup from '../src/stories/ButtonGroup.stories'
7
+ export * as Card from '../src/stories/Card.stories'
8
+ export * as CardMenu from '../src/stories/CardMenu.stories'
2
9
  export * as CardsTable from '../src/stories/CardsTable.stories'
10
+ export * as CheckableTag from '../src/stories/CheckableTag.stories'
3
11
  export * as Checkbox from '../src/stories/Checkbox.stories'
12
+ export * as Collapsible from '../src/stories/Collapsible.stories'
13
+ export * as DateRangePicker from '../src/stories/DateRangePicker.stories'
14
+ export * as DateSinglePicker from '../src/stories/DateSinglePicker.stories'
15
+ export * as Divider from '../src/stories/Divider.stories'
16
+ export * as Drawer from '../src/stories/Drawer.stories'
4
17
  export * as EmptyState from '../src/stories/EmptyState.stories'
18
+ export * as FloatingButton from '../src/stories/FloatingButton.stories'
5
19
  export * as Form from '../src/stories/Form.stories'
20
+ export * as Header from '../src/stories/Header.stories'
6
21
  export * as HelpText from '../src/stories/HelpText.stories'
7
22
  export * as IconButton from '../src/stories/IconButton.stories'
23
+ export * as InfiniteSelect from '../src/stories/InfiniteSelect.stories'
8
24
  export * as Input from '../src/stories/Input.stories'
25
+ export * as InputWithButton from '../src/stories/InputWithButton.stories'
9
26
  export * as Label from '../src/stories/Label.stories'
27
+ export * as Menu from '../src/stories/Menu.stories'
10
28
  export * as Modal from '../src/stories/Modal.stories'
11
29
  export * as Multiselect from '../src/stories/Multiselect.stories'
30
+ export * as Pagination from '../src/stories/Pagination.stories'
31
+ export * as Popover from '../src/stories/Popover.stories'
32
+ export * as PopoverMenu from '../src/stories/PopoverMenu.stories'
33
+ export * as QuantitySelector from '../src/stories/QuantitySelector.stories'
12
34
  export * as RadioGroup from '../src/stories/RadioGroup.stories'
13
35
  export * as Select from '../src/stories/Select.stories'
36
+ export * as Switch from '../src/stories/Switch.stories'
37
+ export * as TabMenu from '../src/stories/TabMenu.stories'
38
+ export * as TextArea from '../src/stories/TextArea.stories'
@@ -0,0 +1,45 @@
1
+ import * as React from 'react'
2
+ /**
3
+ * @storybook/react provides the composeStories utility that helps convert CSF stories from a test file
4
+ * into renderable elements that can be reused in your Node tests with JSDOM.
5
+ *
6
+ * Due to Storybook 10 change to ESM only and CommonJS support drop breaking changes,
7
+ * something on composeStories is broken related to this and when running the a11y tests, a modules error
8
+ * is thrown.
9
+ *
10
+ * The following helper is a working workaround to the original composeStories, making the a11y tests work again.
11
+ * Once Storybook provides a fix for this, we can drop this helper and use the original composeStories from
12
+ * @storybook/react.
13
+ */
14
+
15
+ export function composeStories(csfModule: Record<string, any>) {
16
+ const result: Record<string, (...args: any[]) => any> = {}
17
+ const meta = csfModule.default || {}
18
+ const metaComponent = meta.component
19
+ const metaArgs = meta.args || {}
20
+
21
+ Object.keys(csfModule).forEach((key) => {
22
+ if (key === 'default') return
23
+ const exported = csfModule[key]
24
+
25
+ // Story object with a `render` function (CSF v3 render)
26
+ if (exported && typeof exported.render === 'function') {
27
+ result[key] = (props: any = {}) =>
28
+ exported.render({ ...(exported.args || {}), ...(props.args || {}) })
29
+ return
30
+ }
31
+
32
+ // CSF named export objects that rely on `default.component`
33
+ if (metaComponent && exported && typeof exported === 'object') {
34
+ result[key] = (props: any = {}) =>
35
+ React.createElement(metaComponent, {
36
+ ...metaArgs,
37
+ ...(exported.args || {}),
38
+ ...(props.args || {}),
39
+ })
40
+ return
41
+ }
42
+ })
43
+
44
+ return result
45
+ }
@@ -1,47 +0,0 @@
1
- import './Timeline'
2
- import React from 'react'
3
- import { classNames } from '../../utils/classNames'
4
- import { BaseButton, type BaseButtonProps } from '../Button'
5
- import { Icon, type IconType } from '../Icon'
6
-
7
- export type Variant = 'primary'
8
-
9
- export interface CustomProps {
10
- variant?: Variant
11
- title: string
12
- disabled?: boolean
13
- active?: boolean
14
- leftIcon?: IconType
15
- }
16
-
17
- export type LoadMoreMilestonesButtonProps = CustomProps & BaseButtonProps
18
-
19
- export function LoadMoreMilestonesButton({
20
- variant = 'primary',
21
- className,
22
- title,
23
- disabled,
24
- leftIcon,
25
- prefetch = false,
26
- ...props
27
- }: LoadMoreMilestonesButtonProps): React.JSX.Element {
28
- const cssClasses = classNames(
29
- 'load-more-milestones-button',
30
- variant,
31
- className,
32
- {
33
- disabled,
34
- },
35
- )
36
- return (
37
- <BaseButton
38
- className={cssClasses}
39
- aria-disabled={disabled}
40
- disabled={disabled}
41
- {...props}
42
- >
43
- {leftIcon && <Icon name={leftIcon} size="6" />}
44
- <span className="title">{title}</span>
45
- </BaseButton>
46
- )
47
- }
@@ -1,44 +0,0 @@
1
- import './Timeline.scss'
2
- import Link from 'next/link'
3
- import React from 'react'
4
- import { classNames } from '../../utils/classNames'
5
- import { Icon } from '../Icon'
6
-
7
- export interface MilestoneProps extends React.HTMLAttributes<HTMLLIElement> {
8
- title: string
9
- description: string
10
- isSelected?: boolean
11
- href?: string
12
- }
13
-
14
- export function Milestone({
15
- title,
16
- description,
17
- isSelected = false,
18
- href = '',
19
- className,
20
- ...props
21
- }: MilestoneProps) {
22
- return (
23
- <li
24
- className={classNames('milestone-item', { selected: isSelected })}
25
- {...props}
26
- >
27
- <div className="left-wrapper">
28
- <Icon
29
- name={isSelected ? 'TimelineCircleSelected' : 'TimelineCircleDefault'}
30
- />
31
- </div>
32
- <div className="right-wrapper">
33
- {href === '' ? (
34
- <span className="milestone-title">{title}</span>
35
- ) : (
36
- <Link href={href} className={classNames('milestone-title-link')}>
37
- <span className="milestone-title">{title}</span>
38
- </Link>
39
- )}
40
- <p className="milestone-description">{description}</p>
41
- </div>
42
- </li>
43
- )
44
- }
@@ -1,156 +0,0 @@
1
- @use '../../settings/typography/content' as typography;
2
- @use '../../settings/config';
3
- @use '../../settings/mixins';
4
- @use '../../settings/depth';
5
-
6
- .timeline-container {
7
- .timeline-rail {
8
- position: relative;
9
-
10
- &::after {
11
- content: '';
12
- position: absolute;
13
- width: config.$space-halfx;
14
- top: 0;
15
- bottom: 0;
16
- left: 11px;
17
- z-index: depth.$z-timeline-rail;
18
- }
19
- }
20
-
21
- .milestones-list {
22
- list-style: none;
23
- padding: 0;
24
- margin: config.$space-2x 0px config.$space-2x 6px;
25
-
26
- .milestone-item {
27
- display: flex;
28
- justify-content: flex-start;
29
- align-items: flex-start;
30
- gap: config.$space-3x;
31
-
32
- .left-wrapper {
33
- display: flex;
34
- flex-direction: column;
35
- justify-content: center;
36
- align-items: center;
37
- margin-top: config.$space-2x;
38
-
39
- .icon {
40
- width: config.$icon-size-3x;
41
- height: config.$icon-size-3x;
42
- z-index: depth.$z-timeline-circle;
43
- }
44
- }
45
-
46
- .milestone-title {
47
- @include typography.body-regular-primary;
48
- }
49
- .milestone-description {
50
- @include typography.footnote-primary;
51
- }
52
-
53
- &.selected {
54
- .milestone-title {
55
- @include typography.body-bold;
56
- }
57
- }
58
- }
59
- }
60
-
61
- &.primary {
62
- .timeline-rail {
63
- &::after {
64
- background-color: var(--neutral-color-200);
65
- }
66
- }
67
- .milestone-item {
68
- .milestone-description {
69
- color: var(--neutral-color-600);
70
- }
71
-
72
- .milestone-title-link {
73
- .milestone-title {
74
- color: var(--primary-color-600);
75
- }
76
- }
77
-
78
- &.selected {
79
- .milestone-description {
80
- color: var(--neutral-color-1000);
81
- }
82
-
83
- @include mixins.icon-color(var(--primary-color-600));
84
- }
85
- }
86
- }
87
- }
88
-
89
- .load-more-milestones-button {
90
- display: flex;
91
- justify-content: space-between;
92
- gap: config.$space-2x;
93
- border: 0;
94
- background-color: transparent;
95
- padding: 0;
96
-
97
- .icon {
98
- display: flex;
99
- justify-content: center;
100
- align-items: center;
101
- gap: config.$space-2x;
102
- border-radius: config.$corner-radius-xxs;
103
- padding: config.$space-1x;
104
- }
105
-
106
- &.primary {
107
- &:hover {
108
- .icon {
109
- &:not(:disabled) {
110
- border: 1px solid var(--primary-color-600);
111
- background: var(--primary-color-50);
112
- color: var(--primary-color-600);
113
- }
114
- }
115
-
116
- .title {
117
- &:not(:disabled) {
118
- color: var(--primary-color-800);
119
- }
120
- }
121
- }
122
-
123
- .icon {
124
- border: 1px solid var(--neutral-color-600);
125
- background: var(--neutral-white);
126
- @include typography.body-regular-primary;
127
-
128
- > svg {
129
- fill: var(--primary-color-600);
130
- path {
131
- fill: var(--primary-color-600);
132
- }
133
- }
134
-
135
- &:disabled,
136
- &.disabled {
137
- pointer-events: none;
138
- border: 1px solid var(--neutral-color-400);
139
- background: var(--neutral-color-50);
140
- color: var(--neutral-color-400);
141
- > .icon {
142
- > svg {
143
- fill: var(--neutral-color-400);
144
- path {
145
- fill: var(--neutral-color-400);
146
- }
147
- }
148
- }
149
- }
150
- }
151
-
152
- .title {
153
- color: var(--primary-color-600);
154
- }
155
- }
156
- }
@@ -1,26 +0,0 @@
1
- import './Timeline.scss'
2
- import React from 'react'
3
- import { classNames } from '../../utils/classNames'
4
-
5
- export type Variant = 'primary'
6
- export interface TimelineProps extends React.HTMLAttributes<HTMLDivElement> {
7
- variant?: Variant
8
- }
9
-
10
- export function Timeline({
11
- className,
12
- variant = 'primary',
13
- children,
14
- ...props
15
- }: TimelineProps) {
16
- return (
17
- <div
18
- className={classNames('timeline-container', variant, className)}
19
- {...props}
20
- >
21
- <div className="timeline-rail">
22
- <ol className={classNames('milestones-list')}>{children}</ol>
23
- </div>
24
- </div>
25
- )
26
- }
@@ -1,5 +0,0 @@
1
- import { Timeline } from './Timeline'
2
- export type { TimelineProps } from './Timeline'
3
- import { Milestone } from './Milestone'
4
-
5
- export { Milestone, Timeline }
@@ -1 +0,0 @@
1
- <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"><path d="M0 5a5 5 0 1 1 10 0A5 5 0 0 1 0 5Z" fill="var(--neutral-white)"/><path d="M8 5a3 3 0 1 0-3 3v2A5 5 0 1 1 5 0a5 5 0 0 1 0 10V8a3 3 0 0 0 3-3Z" fill="var(--neutral-color-800)"/></svg>
@@ -1 +0,0 @@
1
- <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"><path d="M0 5a5 5 0 1 1 10 0A5 5 0 0 1 0 5Z" fill="#161C26"/></svg>
@@ -1 +0,0 @@
1
- <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17 19c-.65 0-1.233-.188-1.75-.563A2.91 2.91 0 0 1 14.175 17H9c-1.1 0-2.042-.392-2.825-1.175C5.392 15.042 5 14.1 5 13s.392-2.042 1.175-2.825C6.958 9.392 7.9 9 9 9h2c.55 0 1.02-.196 1.412-.588C12.804 8.021 13 7.55 13 7c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 11 5H5.825a3.09 3.09 0 0 1-1.088 1.438A2.852 2.852 0 0 1 3 7a2.893 2.893 0 0 1-2.125-.875A2.893 2.893 0 0 1 0 4c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 3 1c.65 0 1.23.188 1.737.563.509.375.871.854 1.088 1.437H11c1.1 0 2.042.392 2.825 1.175C14.608 4.958 15 5.9 15 7s-.392 2.042-1.175 2.825C13.042 10.608 12.1 11 11 11H9c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 7 13c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588h5.175a3.09 3.09 0 0 1 1.087-1.438A2.851 2.851 0 0 1 17 13c.833 0 1.542.292 2.125.875S20 15.167 20 16s-.292 1.542-.875 2.125A2.893 2.893 0 0 1 17 19ZM3 5a.97.97 0 0 0 .712-.287A.968.968 0 0 0 4 4a.968.968 0 0 0-.288-.712A.968.968 0 0 0 3 3a.968.968 0 0 0-.712.288A.968.968 0 0 0 2 4c0 .283.096.52.288.713A.968.968 0 0 0 3 5Z" fill="#161C26"/></svg>
@@ -1,155 +0,0 @@
1
- import type { StoryObj } from '@storybook/nextjs'
2
- import React from 'react'
3
- import { Drawer } from '../atoms/Drawer'
4
- import { Milestone, Timeline } from '../atoms/Timeline'
5
- import { LoadMoreMilestonesButton } from '../atoms/Timeline/LoadMoreMilestonesButton'
6
-
7
- const figmaPrimaryDesign = {
8
- design: {
9
- type: 'figma',
10
- url: 'https://www.figma.com/design/DN2ova21vWqCRvPspBXgI1/Design-System?node-id=4786-71&m=dev',
11
- },
12
- }
13
-
14
- const meta = {
15
- title: 'Design System/Atoms/Timeline',
16
- component: Timeline,
17
- tags: ['autodocs', 'Data display components'],
18
- argTypes: {
19
- variant: {
20
- description: 'Component variant used',
21
- },
22
- },
23
- parameters: {
24
- docs: {
25
- description: {
26
- component:
27
- '<h2>Usage guidelines</h2>' +
28
- '<p>The vertical display Timeline component shows progress through a workflow or chronological events in a linear order.</p>' +
29
- '<ul>' +
30
- ' <li>Use it when a series of information needs to be ordered by time (ascending or descending).</li>' +
31
- ' <li>Use it when you need a timeline to make a visual connection.</li>' +
32
- ' <li>The milestone title can either be a button or a link.</li>' +
33
- '</ul>',
34
- },
35
- },
36
- figmaPrimaryDesign,
37
- },
38
- }
39
-
40
- export default meta
41
- type Story = StoryObj<typeof meta>
42
-
43
- export const TimelineDrawer: Story = {
44
- render: () => (
45
- <Drawer id="timeline-drawer" title="1986" onClose={() => alert('close')}>
46
- <div style={{ marginTop: '20px' }}>
47
- <LoadMoreMilestonesButton
48
- onClick={() => alert('more milestones')}
49
- variant="primary"
50
- title="View previous"
51
- leftIcon="AngleUp"
52
- />
53
- <Timeline>
54
- <Milestone
55
- title="1985"
56
- description="Nintendo
57
- NES launches with breakout hits like Super
58
- Mario Bros.
59
- Sega launches its Master System."
60
- />
61
- <Milestone
62
- isSelected
63
- title="1986"
64
- description="Legend of Zelda comes out, spawning a long series of popular games."
65
- />
66
- <Milestone
67
- title="1988"
68
- description="Sega's 16-bit
69
- Mega Drive is introduced, becoming the company's most successful system."
70
- />
71
- <Milestone
72
- title="1989"
73
- description="Nintendo's handheld
74
- Game Boy hits the market."
75
- />
76
- <Milestone
77
- title="1991"
78
- description="Sega's hit Sonic the Hedgehog and the 16-bit
79
- Super Nintendo come out."
80
- />
81
- <Milestone
82
- title="1994"
83
- description="32-bit consoles like the Sega Saturn, Sony PlayStation, and NEC PC-FX launch."
84
- />
85
- </Timeline>
86
- <LoadMoreMilestonesButton
87
- onClick={() => alert('more milestones')}
88
- variant="primary"
89
- title="View next"
90
- leftIcon="AngleDown"
91
- />
92
- </div>
93
- </Drawer>
94
- ),
95
- } as unknown as Story
96
-
97
- export const TimelineWithLinksDrawer: Story = {
98
- render: () => (
99
- <Drawer id="timeline-drawer" title="1986" onClose={() => alert('close')}>
100
- <div style={{ marginTop: '20px' }}>
101
- <LoadMoreMilestonesButton
102
- onClick={() => alert('more milestones')}
103
- variant="primary"
104
- title="View previous"
105
- leftIcon="AngleUp"
106
- />
107
- <Timeline>
108
- <Milestone
109
- title="1985"
110
- description="Nintendo
111
- NES launches with breakout hits like Super
112
- Mario Bros.
113
- Sega launches its Master System."
114
- href="link.com"
115
- />
116
- <Milestone
117
- isSelected
118
- title="1986"
119
- description="Legend of Zelda comes out, spawning a long series of popular games."
120
- href="link.com"
121
- />
122
- <Milestone
123
- title="1988"
124
- description="Sega's 16-bit
125
- Mega Drive is introduced, becoming the company's most successful system."
126
- href="link.com"
127
- />
128
- <Milestone
129
- title="1989"
130
- description="Nintendo's handheld
131
- Game Boy hits the market."
132
- href="link.com"
133
- />
134
- <Milestone
135
- title="1991"
136
- description="Sega's hit Sonic the Hedgehog and the 16-bit
137
- Super Nintendo come out."
138
- href="link.com"
139
- />
140
- <Milestone
141
- title="1994"
142
- description="32-bit consoles like the Sega Saturn, Sony PlayStation, and NEC PC-FX launch."
143
- href="link.com"
144
- />
145
- </Timeline>
146
- <LoadMoreMilestonesButton
147
- onClick={() => alert('more milestones')}
148
- variant="primary"
149
- title="View next"
150
- leftIcon="AngleDown"
151
- />
152
- </div>
153
- </Drawer>
154
- ),
155
- } as unknown as Story
@@ -1,53 +0,0 @@
1
- import { render } from '@testing-library/react'
2
- import React from 'react'
3
- import { Milestone, Timeline } from '../src/atoms/Timeline'
4
- import { LoadMoreMilestonesButton } from '../src/atoms/Timeline/LoadMoreMilestonesButton'
5
-
6
- describe('Timeline', () => {
7
- it('renders with expected content and buttons', () => {
8
- const { getByText, getAllByRole } = render(
9
- <>
10
- <LoadMoreMilestonesButton
11
- onClick={() => jest.fn()}
12
- variant="primary"
13
- title="View previous"
14
- leftIcon="AngleUp"
15
- />
16
- <Timeline>
17
- <Milestone
18
- title="1985"
19
- description="Nintendo
20
- NES launches with breakout hits like Super
21
- Mario Bros.
22
- Sega launches its Master System."
23
- />
24
- <Milestone
25
- isSelected
26
- title="1986"
27
- description="Legend of Zelda comes out, spawning a long series of popular games."
28
- />
29
- </Timeline>
30
- <LoadMoreMilestonesButton
31
- onClick={() => jest.fn()}
32
- variant="primary"
33
- title="View next"
34
- leftIcon="AngleDown"
35
- />
36
- </>,
37
- )
38
- expect(getByText('1985')).toBeInTheDocument()
39
- expect(getByText(/Nintendo NES launches with/i)).toBeInTheDocument()
40
- expect(getByText('1986')).toBeInTheDocument()
41
- expect(getByText(/Legend of Zelda comes out/i)).toBeInTheDocument()
42
- expect(getAllByRole('listitem')[0]).toHaveClass('milestone-item')
43
- expect(getAllByRole('listitem')[1]).toHaveClass('milestone-item selected')
44
- expect(getAllByRole('button')[0]).toBeInTheDocument()
45
- expect(getAllByRole('button')[1]).toBeInTheDocument()
46
- expect(getAllByRole('button')[0]).toHaveClass(
47
- 'load-more-milestones-button primary',
48
- )
49
- expect(getAllByRole('button')[1]).toHaveClass(
50
- 'load-more-milestones-button primary',
51
- )
52
- })
53
- })