agroptima-design-system 0.14.0 → 0.15.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.
- package/package.json +1 -1
- package/src/atoms/Alert.tsx +1 -1
- package/src/atoms/Button/BaseButton.tsx +28 -0
- package/src/atoms/{Button.scss → Button/Button.scss} +3 -3
- package/src/atoms/{Button.tsx → Button/Button.tsx} +7 -31
- package/src/atoms/Button/FloatingButton.scss +51 -0
- package/src/atoms/Button/FloatingButton.tsx +36 -0
- package/src/atoms/{IconButton.scss → Button/IconButton.scss} +3 -2
- package/src/atoms/Button/IconButton.tsx +36 -0
- package/src/atoms/Button/index.ts +9 -0
- package/src/atoms/Menu/Menu.scss +27 -58
- package/src/atoms/Menu/Menu.tsx +1 -5
- package/src/atoms/Menu/MenuDropdown.tsx +42 -0
- package/src/atoms/Menu/MenuLink.tsx +39 -0
- package/src/atoms/Menu/index.ts +3 -2
- package/src/icons/add.svg +1 -0
- package/src/icons/index.tsx +2 -0
- package/src/stories/Card.stories.js +1 -1
- package/src/stories/CardsTable.stories.js +1 -1
- package/src/stories/Changelog.stories.mdx +9 -0
- package/src/stories/FloatingButton.stories.ts +58 -0
- package/src/stories/IconButton.stories.ts +1 -1
- package/src/stories/Menu.stories.js +23 -107
- package/tests/CardsTable.spec.tsx +1 -1
- package/tests/Menu.spec.tsx +23 -25
- package/src/atoms/IconButton.tsx +0 -58
- package/src/atoms/Menu/MenuOption.tsx +0 -79
package/package.json
CHANGED
package/src/atoms/Alert.tsx
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import NextLink from 'next/link'
|
|
2
|
+
|
|
3
|
+
interface CommonProps {
|
|
4
|
+
disabled?: boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type HtmlButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
8
|
+
|
|
9
|
+
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
10
|
+
|
|
11
|
+
export type BaseButtonProps =
|
|
12
|
+
| (HtmlButtonProps & CommonProps)
|
|
13
|
+
| (AnchorProps & CommonProps)
|
|
14
|
+
|
|
15
|
+
const hasHref = (props: HtmlButtonProps | AnchorProps): props is AnchorProps =>
|
|
16
|
+
'href' in props
|
|
17
|
+
|
|
18
|
+
export function BaseButton({ children, ...props }: BaseButtonProps) {
|
|
19
|
+
if (hasHref(props)) {
|
|
20
|
+
return (
|
|
21
|
+
<NextLink href={props.href || ''} {...props}>
|
|
22
|
+
{children}
|
|
23
|
+
</NextLink>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return <button {...(props as HtmlButtonProps)}>{children}</button>
|
|
28
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
@use '
|
|
2
|
-
@use '
|
|
3
|
-
@use '
|
|
1
|
+
@use '../../settings/color_alias';
|
|
2
|
+
@use '../../settings/typography/content' as typography;
|
|
3
|
+
@use '../../settings/config';
|
|
4
4
|
|
|
5
5
|
@mixin button-style($main-color, $secondary-color, $hover-color) {
|
|
6
6
|
background: $main-color;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import NextLink from 'next/link'
|
|
2
1
|
import './Button.scss'
|
|
3
|
-
import { Icon, IconType } from '
|
|
4
|
-
import { classNames } from '
|
|
2
|
+
import { Icon, IconType } from '../Icon'
|
|
3
|
+
import { classNames } from '../../utils/classNames'
|
|
4
|
+
import { BaseButtonProps, BaseButton } from './BaseButton'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
interface CustomProps {
|
|
7
7
|
label: string
|
|
8
8
|
accessibilityLabel?: string
|
|
9
9
|
leftIcon?: IconType
|
|
@@ -13,16 +13,7 @@ export interface BaseButtonProps {
|
|
|
13
13
|
disabled?: boolean
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
type
|
|
17
|
-
|
|
18
|
-
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
19
|
-
|
|
20
|
-
export type ButtonProps =
|
|
21
|
-
| (HtmlButtonProps & BaseButtonProps)
|
|
22
|
-
| (AnchorProps & BaseButtonProps)
|
|
23
|
-
|
|
24
|
-
const hasHref = (props: HtmlButtonProps | AnchorProps): props is AnchorProps =>
|
|
25
|
-
'href' in props
|
|
16
|
+
export type ButtonProps = CustomProps & BaseButtonProps
|
|
26
17
|
|
|
27
18
|
export type ButtonVariant =
|
|
28
19
|
| 'primary'
|
|
@@ -59,23 +50,8 @@ export function Button({
|
|
|
59
50
|
}
|
|
60
51
|
const cssClasses = classNames('button', variant, props.className)
|
|
61
52
|
|
|
62
|
-
if (hasHref(props)) {
|
|
63
|
-
return (
|
|
64
|
-
<NextLink
|
|
65
|
-
href={props.href || ''}
|
|
66
|
-
aria-label={accessibilityLabel || label}
|
|
67
|
-
{...props}
|
|
68
|
-
className={cssClasses}
|
|
69
|
-
>
|
|
70
|
-
{leftIcon && <Icon name={leftIcon} />}
|
|
71
|
-
{label}
|
|
72
|
-
{rightIcon && <Icon name={rightIcon} />}
|
|
73
|
-
</NextLink>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
53
|
return (
|
|
78
|
-
<
|
|
54
|
+
<BaseButton
|
|
79
55
|
disabled={loading || disabled}
|
|
80
56
|
aria-label={accessibilityLabel || label}
|
|
81
57
|
{...props}
|
|
@@ -84,6 +60,6 @@ export function Button({
|
|
|
84
60
|
{leftIcon && <Icon name={leftIcon} />}
|
|
85
61
|
{label}
|
|
86
62
|
{rightIcon && <Icon name={rightIcon} />}
|
|
87
|
-
</
|
|
63
|
+
</BaseButton>
|
|
88
64
|
)
|
|
89
65
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
@use '../../settings/color_alias';
|
|
2
|
+
@use '../../settings/config';
|
|
3
|
+
|
|
4
|
+
.floating-button {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
align-items: center;
|
|
8
|
+
flex-shrink: 0;
|
|
9
|
+
gap: config.$space-1x;
|
|
10
|
+
cursor: default;
|
|
11
|
+
height: fit-content;
|
|
12
|
+
padding: config.$space-5x;
|
|
13
|
+
border-radius: config.$corner-radius-m;
|
|
14
|
+
text-decoration: none;
|
|
15
|
+
border: 1px solid transparent;
|
|
16
|
+
border-radius: 50%;
|
|
17
|
+
|
|
18
|
+
> .icon {
|
|
19
|
+
width: config.$icon-size-5x;
|
|
20
|
+
height: config.$icon-size-5x;
|
|
21
|
+
> svg {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&.primary {
|
|
28
|
+
background: color_alias.$primary-color-600;
|
|
29
|
+
|
|
30
|
+
> .icon {
|
|
31
|
+
> svg {
|
|
32
|
+
fill: color_alias.$neutral-white;
|
|
33
|
+
path {
|
|
34
|
+
fill: color_alias.$neutral-white;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&:disabled {
|
|
40
|
+
background: color_alias.$neutral-color-400;
|
|
41
|
+
> .icon {
|
|
42
|
+
> svg {
|
|
43
|
+
fill: color_alias.$neutral-white;
|
|
44
|
+
path {
|
|
45
|
+
fill: color_alias.$neutral-white;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import './FloatingButton.scss'
|
|
2
|
+
import { Icon, IconType } from '../Icon'
|
|
3
|
+
import { classNames } from '../../utils/classNames'
|
|
4
|
+
import { BaseButtonProps, BaseButton } from './BaseButton'
|
|
5
|
+
|
|
6
|
+
export type Variant = 'primary'
|
|
7
|
+
|
|
8
|
+
interface CustomProps {
|
|
9
|
+
icon: IconType
|
|
10
|
+
variant?: Variant
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
accessibilityLabel: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type FloatingButtonProps = CustomProps & BaseButtonProps
|
|
16
|
+
|
|
17
|
+
export function FloatingButton({
|
|
18
|
+
accessibilityLabel,
|
|
19
|
+
icon,
|
|
20
|
+
disabled,
|
|
21
|
+
variant = 'primary',
|
|
22
|
+
...props
|
|
23
|
+
}: FloatingButtonProps) {
|
|
24
|
+
const cssClasses = classNames('floating-button', variant, props.className)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<BaseButton
|
|
28
|
+
disabled={disabled}
|
|
29
|
+
aria-label={accessibilityLabel}
|
|
30
|
+
{...props}
|
|
31
|
+
className={cssClasses}
|
|
32
|
+
>
|
|
33
|
+
<Icon name={icon} />
|
|
34
|
+
</BaseButton>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
@use '
|
|
2
|
-
@use '
|
|
1
|
+
@use '../../settings/color_alias';
|
|
2
|
+
@use '../../settings/config';
|
|
3
3
|
|
|
4
4
|
.icon-button {
|
|
5
5
|
border: none;
|
|
6
6
|
background: none;
|
|
7
7
|
cursor: default;
|
|
8
|
+
padding: 0;
|
|
8
9
|
|
|
9
10
|
> .icon {
|
|
10
11
|
width: config.$icon-size-5x;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import './IconButton.scss'
|
|
2
|
+
import { Icon, IconType } from '../Icon'
|
|
3
|
+
import { classNames } from '../../utils/classNames'
|
|
4
|
+
import { BaseButtonProps, BaseButton } from './BaseButton'
|
|
5
|
+
|
|
6
|
+
export type Variant = 'primary'
|
|
7
|
+
|
|
8
|
+
interface CustomProps {
|
|
9
|
+
icon: IconType
|
|
10
|
+
variant?: Variant
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
accessibilityLabel: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type IconButtonProps = CustomProps & BaseButtonProps
|
|
16
|
+
|
|
17
|
+
export function IconButton({
|
|
18
|
+
accessibilityLabel,
|
|
19
|
+
icon,
|
|
20
|
+
disabled,
|
|
21
|
+
variant = 'primary',
|
|
22
|
+
...props
|
|
23
|
+
}: IconButtonProps) {
|
|
24
|
+
const cssClasses = classNames('icon-button', variant, props.className)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<BaseButton
|
|
28
|
+
disabled={disabled}
|
|
29
|
+
aria-label={accessibilityLabel}
|
|
30
|
+
{...props}
|
|
31
|
+
className={cssClasses}
|
|
32
|
+
>
|
|
33
|
+
<Icon name={icon} />
|
|
34
|
+
</BaseButton>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Button } from './Button'
|
|
2
|
+
import { IconButton } from './IconButton'
|
|
3
|
+
import { FloatingButton } from './FloatingButton'
|
|
4
|
+
|
|
5
|
+
export type { ButtonProps } from './Button'
|
|
6
|
+
export type { IconButtonProps } from './IconButton'
|
|
7
|
+
export type { FloatingButtonProps } from './FloatingButton'
|
|
8
|
+
|
|
9
|
+
export { Button, IconButton, FloatingButton }
|
package/src/atoms/Menu/Menu.scss
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
&.
|
|
52
|
+
&.active {
|
|
85
53
|
background: color_alias.$primary-color-600;
|
|
86
54
|
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
87
57
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
}
|
package/src/atoms/Menu/Menu.tsx
CHANGED
|
@@ -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
|
+
}
|
package/src/atoms/Menu/index.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg width="20" height="20" fill="#444" xmlns="http://www.w3.org/2000/svg"><path d="M20 11.429h-8.571V20H8.57v-8.571H0V8.57h8.571V0h2.858v8.571H20v2.858Z" fill="#444"/><defs><clipPath id="add__a"><path fill="#fff" d="M0 0h20v20H0z"/></clipPath></defs></svg>
|
package/src/icons/index.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Add from './add.svg'
|
|
1
2
|
import AddCircle from './add-circle.svg'
|
|
2
3
|
import AngleDown from './angle-down.svg'
|
|
3
4
|
import AngleLeft from './angle-left.svg'
|
|
@@ -23,6 +24,7 @@ import Sorter from './sorter.svg'
|
|
|
23
24
|
import Warning from './warning.svg'
|
|
24
25
|
|
|
25
26
|
export {
|
|
27
|
+
Add,
|
|
26
28
|
AddCircle,
|
|
27
29
|
AngleDown,
|
|
28
30
|
AngleLeft,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
3
|
import { CardsTable, CardsTableHead, CardsTableHeader, CardsTableRow, CardsTableBody, CardsTableCell } from '../atoms/CardsTable'
|
|
4
|
-
import { IconButton } from '../atoms/
|
|
4
|
+
import { IconButton } from '../atoms/Button'
|
|
5
5
|
import { Badge } from '../atoms/Badge'
|
|
6
6
|
|
|
7
7
|
const figmaPrimaryDesign = {
|
|
@@ -3,6 +3,15 @@ import { Meta } from "@storybook/addon-docs";
|
|
|
3
3
|
<Meta title="Changelog" />
|
|
4
4
|
# Changelog
|
|
5
5
|
|
|
6
|
+
## 0.15.0
|
|
7
|
+
- Added FloatingButton component to Storybook
|
|
8
|
+
|
|
9
|
+
BREAKING CHANGES
|
|
10
|
+
- All button components have been moved to a common folder
|
|
11
|
+
|
|
12
|
+
## 0.14.1
|
|
13
|
+
- Added MenuLink and MenuDropdown components to Storybook and remove MenuOption component.
|
|
14
|
+
|
|
6
15
|
## 0.14.0
|
|
7
16
|
- Added Menu component to Storybook.
|
|
8
17
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react'
|
|
2
|
+
import { FloatingButton } from '../atoms/Button'
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Design System/Atoms/FloatingButton',
|
|
6
|
+
component: FloatingButton,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
argTypes: {
|
|
9
|
+
accessibilityLabel: {
|
|
10
|
+
description: 'Accessible name & description of the element',
|
|
11
|
+
},
|
|
12
|
+
variant: {
|
|
13
|
+
description: 'Component variant used from a list of values',
|
|
14
|
+
},
|
|
15
|
+
disabled: {
|
|
16
|
+
description: 'Is the button in disabled state?',
|
|
17
|
+
},
|
|
18
|
+
icon: {
|
|
19
|
+
description: 'Icon from a list of values',
|
|
20
|
+
control: { type: 'select' },
|
|
21
|
+
},
|
|
22
|
+
href: {
|
|
23
|
+
description:
|
|
24
|
+
'If a link is provided, the component will be rendered as NextLink, otherwise as button',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const figmaPrimaryDesign = {
|
|
30
|
+
design: {
|
|
31
|
+
type: 'figma',
|
|
32
|
+
url: 'https://www.figma.com/file/DN2ova21vWqCRvPspBXgI1/Design-System?type=design&node-id=1873-922&mode=dev',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default meta
|
|
37
|
+
type Story = StoryObj<typeof meta>
|
|
38
|
+
|
|
39
|
+
export const Link: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
icon: 'Add',
|
|
42
|
+
variant: 'primary',
|
|
43
|
+
accessibilityLabel: 'Edit game',
|
|
44
|
+
href: 'link.com',
|
|
45
|
+
disabled: false,
|
|
46
|
+
},
|
|
47
|
+
parameters: figmaPrimaryDesign,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Button: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
icon: 'Add',
|
|
53
|
+
variant: 'primary',
|
|
54
|
+
accessibilityLabel: 'Delete game',
|
|
55
|
+
disabled: false,
|
|
56
|
+
},
|
|
57
|
+
parameters: figmaPrimaryDesign,
|
|
58
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
import { 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
|
-
|
|
27
|
-
description: 'Is the element
|
|
26
|
+
isActive: {
|
|
27
|
+
description: 'Is the element active?',
|
|
28
28
|
},
|
|
29
|
-
|
|
30
|
-
description: '
|
|
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
|
|
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
|
-
<
|
|
91
|
-
|
|
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
|
-
|
|
48
|
+
href="some-link"
|
|
49
|
+
isActive
|
|
105
50
|
/>
|
|
106
|
-
<
|
|
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
|
-
<
|
|
138
|
-
title="
|
|
139
|
-
|
|
140
|
-
>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
}
|
package/tests/Menu.spec.tsx
CHANGED
|
@@ -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 {
|
|
4
|
+
import { MenuDropdown, MenuLink } from '@/atoms/Menu'
|
|
5
5
|
|
|
6
6
|
describe('Menu', () => {
|
|
7
7
|
it('renders first-level menu', () => {
|
|
8
|
-
const { getByRole, getByText
|
|
8
|
+
const { getByRole, getByText } = render(
|
|
9
9
|
<Menu>
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
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(
|
|
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(
|
|
24
|
-
expect(
|
|
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
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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(
|
|
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(
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
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
|
})
|
package/src/atoms/IconButton.tsx
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import NextLink from 'next/link'
|
|
2
|
-
import './IconButton.scss'
|
|
3
|
-
import { Icon, IconType } from './Icon'
|
|
4
|
-
import { classNames } from '../utils/classNames'
|
|
5
|
-
|
|
6
|
-
export type Variant = 'primary'
|
|
7
|
-
|
|
8
|
-
export interface BaseIconButtonProps {
|
|
9
|
-
icon: IconType
|
|
10
|
-
variant?: Variant
|
|
11
|
-
disabled?: boolean
|
|
12
|
-
accessibilityLabel: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type HtmlButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
16
|
-
|
|
17
|
-
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
18
|
-
|
|
19
|
-
export type IconButtonProps =
|
|
20
|
-
| (HtmlButtonProps & BaseIconButtonProps)
|
|
21
|
-
| (AnchorProps & BaseIconButtonProps)
|
|
22
|
-
|
|
23
|
-
const hasHref = (props: HtmlButtonProps | AnchorProps): props is AnchorProps =>
|
|
24
|
-
'href' in props
|
|
25
|
-
|
|
26
|
-
export function IconButton({
|
|
27
|
-
accessibilityLabel,
|
|
28
|
-
icon,
|
|
29
|
-
disabled,
|
|
30
|
-
variant = 'primary',
|
|
31
|
-
...props
|
|
32
|
-
}: IconButtonProps) {
|
|
33
|
-
const cssClasses = classNames('icon-button', variant, props.className)
|
|
34
|
-
|
|
35
|
-
if (hasHref(props)) {
|
|
36
|
-
return (
|
|
37
|
-
<NextLink
|
|
38
|
-
href={props.href || ''}
|
|
39
|
-
aria-label={accessibilityLabel}
|
|
40
|
-
{...props}
|
|
41
|
-
className={cssClasses}
|
|
42
|
-
>
|
|
43
|
-
<Icon name={icon} />
|
|
44
|
-
</NextLink>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<button
|
|
50
|
-
disabled={disabled}
|
|
51
|
-
aria-label={accessibilityLabel}
|
|
52
|
-
{...props}
|
|
53
|
-
className={cssClasses}
|
|
54
|
-
>
|
|
55
|
-
<Icon name={icon} />
|
|
56
|
-
</button>
|
|
57
|
-
)
|
|
58
|
-
}
|
|
@@ -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
|
-
}
|