agroptima-design-system 0.14.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroptima-design-system",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "scripts": {
5
5
  "dev": "npm run storybook",
6
6
  "storybook": "storybook dev -p 6006 --ci",
@@ -4,15 +4,19 @@
4
4
  @use '../../settings/depth';
5
5
 
6
6
  .menu {
7
+ @include typography.body-regular-primary;
7
8
  list-style-type: none;
8
9
  display: flex;
9
10
  flex-direction: column;
10
11
  padding: 0;
11
12
 
12
- .menu-option {
13
+ &-item {
13
14
  display: flex;
14
- flex-direction: column;
15
15
  gap: config.$space-2x;
16
+ padding: config.$space-3x;
17
+ text-decoration: none;
18
+ color: inherit;
19
+ align-items: center;
16
20
  cursor: default;
17
21
 
18
22
  .icon {
@@ -24,46 +28,10 @@
24
28
  }
25
29
  }
26
30
 
27
- details {
28
- &[open] {
29
- > .container .right .icon {
30
- transform: rotate(180deg);
31
- }
32
- }
33
- &:has(> .dropdown) {
34
- > .container .right {
35
- display: inline-block;
36
- }
37
-
38
- > .dropdown {
39
- > .menu-option details .container {
40
- padding-left: config.$space-8x;
41
- }
42
- }
43
- }
44
-
45
- .container {
46
- display: flex;
47
- padding: config.$space-3x;
48
-
49
- .left {
50
- display: flex;
51
- width: 100%;
52
- gap: config.$space-2x;
53
- justify-content: flex-start;
54
- align-items: baseline;
55
- }
56
-
57
- .right {
58
- display: none;
59
- margin-top: auto;
60
- margin-bottom: auto;
61
- }
62
- }
31
+ .title {
32
+ flex: 1;
63
33
  }
64
34
 
65
- @include typography.body-regular-primary;
66
-
67
35
  &.primary {
68
36
  color: color_alias.$neutral-white;
69
37
  background: color_alias.$neutral-color-900;
@@ -81,29 +49,30 @@
81
49
  background: color_alias.$primary-color-600;
82
50
  }
83
51
 
84
- &.selected {
52
+ &.active {
85
53
  background: color_alias.$primary-color-600;
86
54
  }
55
+ }
56
+ }
87
57
 
88
- details {
89
- &:has(> .dropdown) {
90
- > .dropdown {
91
- .menu-option {
92
- background: color_alias.$neutral-color-100;
93
- color: color_alias.$neutral-color-1000;
94
-
95
- &:hover {
96
- background: color_alias.$primary-color-100;
97
- }
98
- }
58
+ details[open] {
59
+ .arrow {
60
+ transform: rotate(180deg);
61
+ }
62
+ }
63
+ .menu-dropdown .menu {
64
+ .menu-item {
65
+ padding-left: config.$space-8x;
66
+ background: color_alias.$neutral-color-100;
67
+ color: color_alias.$neutral-color-1000;
99
68
 
100
- .selected {
101
- background: color_alias.$primary-color-100;
102
- box-shadow: inset -3px 0px 0px 0px color_alias.$primary-color-600;
103
- }
104
- }
105
- }
69
+ &:hover {
70
+ background: color_alias.$primary-color-100;
106
71
  }
107
72
  }
73
+ .active {
74
+ background: color_alias.$primary-color-100;
75
+ box-shadow: inset -3px 0px 0px 0px color_alias.$primary-color-600;
76
+ }
108
77
  }
109
78
  }
@@ -6,19 +6,15 @@ export type Variant = 'primary'
6
6
 
7
7
  export interface MenuProps extends React.ComponentPropsWithoutRef<'ul'> {
8
8
  variant?: Variant
9
- isDropdown?: boolean
10
9
  }
11
10
 
12
11
  export function Menu({
13
12
  variant = 'primary',
14
13
  className,
15
- isDropdown = false,
16
14
  children,
17
15
  ...props
18
16
  }: MenuProps): React.JSX.Element {
19
- const cssClasses = classNames('menu', variant, className, {
20
- dropdown: isDropdown,
21
- })
17
+ const cssClasses = classNames('menu', variant, className)
22
18
 
23
19
  return (
24
20
  <ul className={cssClasses} role="menu" {...props}>
@@ -0,0 +1,42 @@
1
+ import { classNames } from '../../utils/classNames'
2
+ import { Icon, IconType } from '../Icon'
3
+ import './Menu.scss'
4
+
5
+ export type Variant = 'primary'
6
+
7
+ export interface MenuDropdownProps
8
+ extends React.ComponentPropsWithoutRef<'li'> {
9
+ title: string
10
+ variant?: Variant
11
+ icon?: IconType
12
+ isOpen?: boolean
13
+ name?: string
14
+ }
15
+
16
+ export function MenuDropdown({
17
+ variant = 'primary',
18
+ className,
19
+ icon,
20
+ title,
21
+ children,
22
+ isOpen,
23
+ name,
24
+ ...props
25
+ }: MenuDropdownProps): React.JSX.Element {
26
+ const cssClasses = classNames('menu-item', variant, className)
27
+
28
+ return (
29
+ <li tabIndex={0} role="menuitem" className="menu-dropdown" {...props}>
30
+ <details open={isOpen} name={name}>
31
+ <summary className={cssClasses}>
32
+ {icon && <Icon name={icon} />}
33
+ <span className="title">{title}</span>
34
+ <Icon className="arrow" name="AngleDown" />
35
+ </summary>
36
+ <ul className="menu" role="menu">
37
+ {children}
38
+ </ul>
39
+ </details>
40
+ </li>
41
+ )
42
+ }
@@ -0,0 +1,39 @@
1
+ import './Menu.scss'
2
+ import React from 'react'
3
+ import { Icon, IconType } from '../Icon'
4
+ import { classNames } from '../../utils/classNames'
5
+ import Link from 'next/link'
6
+
7
+ export type Variant = 'primary'
8
+
9
+ export interface MenuLinkProps
10
+ extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
11
+ title: string
12
+ variant?: Variant
13
+ icon?: IconType
14
+ isActive?: boolean
15
+ href: string
16
+ }
17
+
18
+ export function MenuLink({
19
+ variant = 'primary',
20
+ isActive = false,
21
+ className,
22
+ icon,
23
+ title,
24
+ href,
25
+ ...props
26
+ }: MenuLinkProps): React.JSX.Element {
27
+ const cssClasses = classNames('menu-item', variant, className, {
28
+ active: isActive,
29
+ })
30
+
31
+ return (
32
+ <li tabIndex={0} role="menuitem">
33
+ <Link href={href} {...props} className={cssClasses}>
34
+ {icon && <Icon name={icon} />}
35
+ <span className="title">{title}</span>
36
+ </Link>
37
+ </li>
38
+ )
39
+ }
@@ -1,4 +1,5 @@
1
1
  import { Menu } from './Menu'
2
- import { MenuOption } from './MenuOption'
2
+ import { MenuLink } from './MenuLink'
3
+ import { MenuDropdown } from './MenuDropdown'
3
4
 
4
- export { Menu, MenuOption }
5
+ export { Menu, MenuLink, MenuDropdown }
@@ -3,6 +3,10 @@ import { Meta } from "@storybook/addon-docs";
3
3
  <Meta title="Changelog" />
4
4
  # Changelog
5
5
 
6
+
7
+ ## 0.14.1
8
+ - Added MenuLink and MenuDropdown components to Storybook and remove MenuOption component.
9
+
6
10
  ## 0.14.0
7
11
  - Added Menu component to Storybook.
8
12
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
 
3
- import { Menu, MenuOption } from '../atoms/Menu'
3
+ import { Menu, MenuLink, MenuDropdown } from '../atoms/Menu'
4
4
 
5
5
  const figmaPrimaryDesign = {
6
6
  design: {
@@ -23,11 +23,14 @@ const meta = {
23
23
  title: {
24
24
  description: 'Component title text',
25
25
  },
26
- isSelected: {
27
- description: 'Is the element selected?',
26
+ isActive: {
27
+ description: 'Is the element active?',
28
28
  },
29
- onClick: {
30
- description: 'Event triggered when the component is clicked',
29
+ href: {
30
+ description: 'link to the page',
31
+ },
32
+ isOpen: {
33
+ description: 'Is the dropdown open?',
31
34
  },
32
35
  },
33
36
  parameters: figmaPrimaryDesign,
@@ -35,116 +38,29 @@ const meta = {
35
38
 
36
39
  export default meta
37
40
 
38
- export const MenuWithSecondLevelDropdown = {
39
- render: () => (
40
- <Menu>
41
- <MenuOption title="Tekken 8" icon="Edit">
42
- <Menu isDropdown>
43
- <MenuOption title="Walkthrough" onClick={() => alert('click')} />
44
- <MenuOption title="Characters" onClick={() => alert('click')} />
45
- <MenuOption title="Story" onClick={() => alert('click')} />
46
- </Menu>
47
- </MenuOption>
48
- <MenuOption
49
- title="The Legend of Zelda: Tears of the Kingdom"
50
- icon="Delete"
51
- onClick={() => alert('click')}
52
- />
53
- <MenuOption
54
- title="Metal Gear Solid 5: Ground Zeroes + The Phantom Pain"
55
- icon="Show"
56
- >
57
- <Menu isDropdown>
58
- <MenuOption title="Walkthrough" onClick={() => alert('click')} />
59
- <MenuOption title="Characters" onClick={() => alert('click')} />
60
- <MenuOption title="Story" onClick={() => alert('click')} />
61
- </Menu>
62
- </MenuOption>
63
- <MenuOption title="Stray" icon="Info" onClick={() => alert('click')} />
64
- </Menu>
65
- ),
66
- }
67
-
68
- export const FirstLevelMenu = {
69
- render: () => (
70
- <Menu>
71
- <MenuOption title="Tekken 8" icon="Edit" onClick={() => alert('click')} />
72
- <MenuOption
73
- title="The Legend of Zelda: Tears of the Kingdom"
74
- icon="Delete"
75
- onClick={() => alert('click')}
76
- />
77
- <MenuOption
78
- title="Metal Gear Solid 5: Ground Zeroes + The Phantom Pain"
79
- icon="Show"
80
- onClick={() => alert('click')}
81
- />
82
- <MenuOption title="Stray" icon="Info" onClick={() => alert('click')} />
83
- </Menu>
84
- ),
85
- }
86
-
87
- export const MenuWithSecondLevelPreselectedOption = {
41
+ export const MenuWithLinks = {
88
42
  render: () => (
89
43
  <Menu>
90
- <MenuOption isSelected title="Tekken 8" icon="Edit">
91
- <Menu isDropdown>
92
- <MenuOption title="Walkthrough" onClick={() => alert('click')} />
93
- <MenuOption
94
- isSelected
95
- title="Characters"
96
- onClick={() => alert('click')}
97
- />
98
- <MenuOption title="Story" onClick={() => alert('click')} />
99
- </Menu>
100
- </MenuOption>
101
- <MenuOption
44
+ <MenuLink title="Tekken 8" href="some-link" />
45
+ <MenuLink
102
46
  title="The Legend of Zelda: Tears of the Kingdom"
103
47
  icon="Delete"
104
- onClick={() => alert('click')}
48
+ href="some-link"
49
+ isActive
105
50
  />
106
- <MenuOption
51
+ <MenuLink
107
52
  title="Metal Gear Solid 5: Ground Zeroes + The Phantom Pain"
108
53
  icon="Show"
109
- >
110
- <Menu isDropdown>
111
- <MenuOption title="Walkthrough" onClick={() => alert('click')} />
112
- <MenuOption title="Characters" onClick={() => alert('click')} />
113
- <MenuOption title="Story" onClick={() => alert('click')} />
114
- </Menu>
115
- </MenuOption>
116
- <MenuOption title="Stray" icon="Info" onClick={() => alert('click')} />
117
- </Menu>
118
- ),
119
- }
120
-
121
- export const MenuWithFirstLevelPreselectedOption = {
122
- render: () => (
123
- <Menu>
124
- <MenuOption title="Tekken 8" icon="Edit">
125
- <Menu isDropdown>
126
- <MenuOption title="Walkthrough" onClick={() => alert('click')} />
127
- <MenuOption title="Characters" onClick={() => alert('click')} />
128
- <MenuOption title="Story" onClick={() => alert('click')} />
129
- </Menu>
130
- </MenuOption>
131
- <MenuOption
132
- isSelected
133
- title="The Legend of Zelda: Tears of the Kingdom"
134
- icon="Delete"
135
- onClick={() => alert('click')}
54
+ href="some-link"
136
55
  />
137
- <MenuOption
138
- title="Metal Gear Solid 5: Ground Zeroes + The Phantom Pain"
139
- icon="Show"
140
- >
141
- <Menu isDropdown>
142
- <MenuOption title="Walkthrough" onClick={() => alert('click')} />
143
- <MenuOption title="Characters" onClick={() => alert('click')} />
144
- <MenuOption title="Story" onClick={() => alert('click')} />
145
- </Menu>
146
- </MenuOption>
147
- <MenuOption title="Stray" icon="Info" onClick={() => alert('click')} />
56
+ <MenuDropdown title="Open" icon="AddCircle" name="menu" isOpen>
57
+ <MenuLink title="Stray" href="some-link" isActive />
58
+ <MenuLink title="Fallout 3" href="some-link" />
59
+ </MenuDropdown>
60
+ <MenuDropdown title="Close" name="menu">
61
+ <MenuLink title="Dark souls" href="some-link" />
62
+ <MenuLink title="Elder ring" href="some-link" />
63
+ </MenuDropdown>
148
64
  </Menu>
149
65
  ),
150
66
  }
@@ -1,59 +1,57 @@
1
1
  import React from 'react'
2
2
  import { render } from '@testing-library/react'
3
3
  import { Menu } from '@/atoms/Menu/Menu'
4
- import { MenuOption } from '@/atoms/Menu/MenuOption'
4
+ import { MenuDropdown, MenuLink } from '@/atoms/Menu'
5
5
 
6
6
  describe('Menu', () => {
7
7
  it('renders first-level menu', () => {
8
- const { getByRole, getByText, getAllByRole } = render(
8
+ const { getByRole, getByText } = render(
9
9
  <Menu>
10
- <MenuOption title="Tekken 8" icon="Edit" />
11
- <MenuOption
12
- isSelected
10
+ <MenuLink title="Tekken 8" icon="Edit" href="#" />
11
+ <MenuLink
12
+ isActive
13
13
  title="The Legend of Zelda: Tears of the Kingdom"
14
14
  icon="Delete"
15
+ href="#"
15
16
  />
16
17
  </Menu>,
17
18
  )
18
19
 
19
20
  expect(getByRole('menu')).toHaveClass(`menu primary`)
20
- expect(getAllByRole('menuitem')[1]).toHaveClass(`selected`)
21
+ expect(
22
+ getByRole('link', { name: 'The Legend of Zelda: Tears of the Kingdom' }),
23
+ ).toHaveClass(`active`)
21
24
  expect(getByText(/Tekken/i)).toBeInTheDocument()
22
25
  expect(getByText(/Zelda/i)).toBeInTheDocument()
23
- expect(getAllByRole('img')[0].title).toBe('Edit')
24
- expect(getAllByRole('img')[2].title).toBe('Delete')
26
+ expect(getByRole('img', { name: 'Edit' })).toBeInTheDocument()
27
+ expect(getByRole('img', { name: 'Delete' })).toBeInTheDocument()
25
28
  })
26
29
 
27
30
  it('renders second-level menu', () => {
28
- const { getByText, getAllByRole } = render(
31
+ const { getByText, getAllByRole, getByRole } = render(
29
32
  <Menu>
30
- <MenuOption title="Tekken 8" icon="Edit">
31
- <Menu isDropdown>
32
- <MenuOption title="Walkthrough" onClick={() => alert('click')} />
33
- <MenuOption
34
- isSelected
35
- title="Characters"
36
- onClick={() => alert('click')}
37
- />
38
- <MenuOption title="Story" onClick={() => alert('click')} />
39
- </Menu>
40
- </MenuOption>
41
- <MenuOption
33
+ <MenuDropdown title="Tekken 8" icon="Edit">
34
+ <MenuLink title="Walkthrough" href="#" />
35
+ <MenuLink isActive title="Characters" href="#" />
36
+ <MenuLink title="Story" href="#" />
37
+ </MenuDropdown>
38
+ <MenuLink
42
39
  title="The Legend of Zelda: Tears of the Kingdom"
43
40
  icon="Delete"
41
+ href="#"
44
42
  />
45
43
  </Menu>,
46
44
  )
47
45
 
48
46
  expect(getAllByRole('menu').length).toBe(2)
49
- expect(getAllByRole('menuitem')[2]).toHaveClass(`selected`)
47
+ expect(getByRole('link', { name: 'Characters' })).toHaveClass(`active`)
50
48
  expect(getByText(/Tekken/i)).toBeInTheDocument()
51
49
  expect(getByText(/Walkthrough/i)).toBeInTheDocument()
52
50
  expect(getByText(/Characters/i)).toBeInTheDocument()
53
51
  expect(getByText(/Story/i)).toBeInTheDocument()
54
52
  expect(getByText(/Zelda/i)).toBeInTheDocument()
55
- expect(getAllByRole('img')[0].title).toBe('Edit')
56
- expect(getAllByRole('img')[2].title).toBe('AngleDown')
57
- expect(getAllByRole('img')[5].title).toBe('Delete')
53
+ expect(getByRole('img', { name: 'Edit' })).toBeInTheDocument()
54
+ expect(getByRole('img', { name: 'AngleDown' })).toBeInTheDocument()
55
+ expect(getByRole('img', { name: 'Delete' })).toBeInTheDocument()
58
56
  })
59
57
  })
@@ -1,79 +0,0 @@
1
- import './Menu.scss'
2
- import React from 'react'
3
- import { Icon, IconType } from '../Icon'
4
- import { classNames } from '../../utils/classNames'
5
-
6
- export type Variant = 'primary'
7
-
8
- export interface MenuOptionProps extends React.ComponentPropsWithoutRef<'li'> {
9
- variant?: Variant
10
- icon?: IconType
11
- title: string
12
- isSelected?: boolean
13
- }
14
-
15
- export function MenuOption({
16
- variant = 'primary',
17
- className,
18
- isSelected = false,
19
- icon,
20
- title,
21
- children,
22
- onClick,
23
- ...props
24
- }: MenuOptionProps): React.JSX.Element {
25
- const cssClasses = classNames('menu-option', variant, className, {
26
- selected: isSelected,
27
- })
28
-
29
- function closePreviousSelectedDropdown(currentTarget: HTMLLIElement) {
30
- document.querySelectorAll('details[open]').forEach((detailElement) => {
31
- const firstLevelMenuOption = detailElement?.closest('li')
32
- const currentFirstLevelMenuOption = currentTarget
33
- ?.closest('details')
34
- ?.closest('li')
35
-
36
- if (firstLevelMenuOption !== currentFirstLevelMenuOption)
37
- detailElement.removeAttribute('open')
38
- })
39
- }
40
-
41
- function unselectPreviousOption() {
42
- document
43
- .querySelectorAll('.selected')
44
- .forEach((option) => option.classList.remove('selected'))
45
- }
46
-
47
- function setOptionSelected(event: React.MouseEvent<HTMLLIElement>) {
48
- event.stopPropagation()
49
-
50
- closePreviousSelectedDropdown(event.currentTarget)
51
- unselectPreviousOption()
52
-
53
- event.currentTarget.classList.add('selected')
54
- if (onClick) onClick(event)
55
- }
56
-
57
- return (
58
- <li
59
- className={cssClasses}
60
- tabIndex={0}
61
- role="menuitem"
62
- onClick={setOptionSelected}
63
- {...props}
64
- >
65
- <details open={isSelected}>
66
- <summary className="container">
67
- <div className="left">
68
- {icon && <Icon name={icon} />}
69
- <span className="title">{title}</span>
70
- </div>
71
- <div className="right">
72
- <Icon name="AngleDown" />
73
- </div>
74
- </summary>
75
- {children}
76
- </details>
77
- </li>
78
- )
79
- }