agroptima-design-system 0.9.3 → 0.11.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/.storybook/main.ts +7 -0
- package/package.json +1 -1
- package/src/atoms/Alert.scss +1 -0
- package/src/atoms/Alert.tsx +5 -3
- package/src/atoms/Badge.tsx +3 -1
- package/src/atoms/Button.tsx +4 -3
- package/src/atoms/CardsTable.scss +99 -22
- package/src/atoms/CardsTable.tsx +3 -2
- package/src/atoms/CardsTableCell.tsx +5 -5
- package/src/atoms/CardsTableHeader.tsx +3 -1
- package/src/atoms/Checkbox.tsx +11 -8
- package/src/atoms/EmptyState.tsx +3 -1
- package/src/atoms/Icon.tsx +4 -5
- package/src/atoms/IconButton.tsx +4 -3
- package/src/atoms/Input.tsx +17 -17
- package/src/atoms/Modal.tsx +3 -1
- package/src/atoms/Multiselect.tsx +13 -24
- package/src/atoms/Select.tsx +15 -23
- package/src/atoms/TextArea.tsx +58 -0
- package/src/settings/typography/_content.scss +8 -0
- package/src/stories/CardsTable.stories.js +85 -9
- package/src/stories/CardsTableList.stories.ts +1 -1
- package/src/stories/Changelog.stories.mdx +9 -0
- package/src/stories/Input.stories.ts +1 -1
- package/src/stories/TextArea.stories.ts +78 -0
- package/src/utils/buildHelpText.ts +12 -0
- package/src/utils/classNames.ts +23 -0
- package/tests/CardsTable.spec.tsx +128 -0
- package/tests/utils/classNames.spec.tsx +13 -0
package/.storybook/main.ts
CHANGED
package/package.json
CHANGED
package/src/atoms/Alert.scss
CHANGED
package/src/atoms/Alert.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IconButton, IconButtonProps } from './IconButton'
|
|
2
2
|
import { Icon } from './Icon'
|
|
3
3
|
import './Alert.scss'
|
|
4
|
+
import { classNames } from '@/utils/classNames'
|
|
4
5
|
|
|
5
6
|
export type Variant = 'info' | 'success' | 'warning' | 'error'
|
|
6
7
|
|
|
@@ -22,14 +23,15 @@ export enum IconVariant {
|
|
|
22
23
|
export function Alert({
|
|
23
24
|
id,
|
|
24
25
|
variant = 'success',
|
|
25
|
-
className
|
|
26
|
+
className,
|
|
26
27
|
fitContent = false,
|
|
27
28
|
text,
|
|
28
29
|
button,
|
|
29
30
|
...props
|
|
30
31
|
}: AlertProps): React.JSX.Element {
|
|
31
|
-
const
|
|
32
|
-
|
|
32
|
+
const cssClasses = classNames('alert', variant, className, {
|
|
33
|
+
'fit-content': fitContent,
|
|
34
|
+
})
|
|
33
35
|
|
|
34
36
|
return (
|
|
35
37
|
<div
|
package/src/atoms/Badge.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
1
2
|
import './Badge.scss'
|
|
2
3
|
|
|
3
4
|
export type Variant =
|
|
@@ -18,11 +19,12 @@ export interface BadgeProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
18
19
|
|
|
19
20
|
export function Badge({
|
|
20
21
|
variant = 'info',
|
|
22
|
+
className,
|
|
21
23
|
text,
|
|
22
24
|
accessibilityLabel,
|
|
23
25
|
...props
|
|
24
26
|
}: BadgeProps): React.JSX.Element {
|
|
25
|
-
const cssClasses =
|
|
27
|
+
const cssClasses = classNames('badge', variant, className)
|
|
26
28
|
|
|
27
29
|
return (
|
|
28
30
|
<span
|
package/src/atoms/Button.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import NextLink from 'next/link'
|
|
2
2
|
import './Button.scss'
|
|
3
3
|
import { Icon, IconType } from './Icon'
|
|
4
|
+
import { classNames } from '@/utils/classNames'
|
|
4
5
|
|
|
5
6
|
export interface BaseButtonProps {
|
|
6
7
|
label: string
|
|
@@ -56,15 +57,15 @@ export function Button({
|
|
|
56
57
|
if (loading) {
|
|
57
58
|
leftIcon = 'Loading'
|
|
58
59
|
}
|
|
59
|
-
const cssClasses =
|
|
60
|
+
const cssClasses = classNames('button', variant, props.className)
|
|
60
61
|
|
|
61
62
|
if (hasHref(props)) {
|
|
62
63
|
return (
|
|
63
64
|
<NextLink
|
|
64
65
|
href={props.href || ''}
|
|
65
|
-
className={cssClasses}
|
|
66
66
|
aria-label={accessibilityLabel || label}
|
|
67
67
|
{...props}
|
|
68
|
+
className={cssClasses}
|
|
68
69
|
>
|
|
69
70
|
{leftIcon && <Icon name={leftIcon} />}
|
|
70
71
|
{label}
|
|
@@ -75,10 +76,10 @@ export function Button({
|
|
|
75
76
|
|
|
76
77
|
return (
|
|
77
78
|
<button
|
|
78
|
-
className={cssClasses}
|
|
79
79
|
disabled={loading || disabled}
|
|
80
80
|
aria-label={accessibilityLabel || label}
|
|
81
81
|
{...props}
|
|
82
|
+
className={cssClasses}
|
|
82
83
|
>
|
|
83
84
|
{leftIcon && <Icon name={leftIcon} />}
|
|
84
85
|
{label}
|
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
@use '../settings/config';
|
|
4
4
|
|
|
5
5
|
.cards-table-list {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: config.$space-3x;
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
tbody {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: config.$space-3x;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
text-overflow: ellipsis;
|
|
16
|
+
tr {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-grow: 1;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
.container {
|
|
@@ -22,11 +23,11 @@
|
|
|
22
23
|
flex-direction: row;
|
|
23
24
|
justify-content: space-between;
|
|
24
25
|
align-items: center;
|
|
26
|
+
width: 100%;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
th {
|
|
28
30
|
padding: config.$space-2x config.$space-3x;
|
|
29
|
-
white-space: nowrap;
|
|
30
31
|
text-align: left;
|
|
31
32
|
|
|
32
33
|
&.sortable {
|
|
@@ -44,24 +45,16 @@
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
td {
|
|
48
|
-
padding: config.$space-5x config.$space-3x;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
48
|
.no-wrap {
|
|
52
49
|
white-space: nowrap;
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
.alignment-left {
|
|
56
|
-
|
|
53
|
+
justify-content: flex-start;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
.alignment-center {
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.alignment-right {
|
|
64
|
-
text-align: right;
|
|
57
|
+
justify-content: center;
|
|
65
58
|
}
|
|
66
59
|
|
|
67
60
|
&.primary {
|
|
@@ -126,6 +119,10 @@
|
|
|
126
119
|
background: color_alias.$primary-color-50;
|
|
127
120
|
}
|
|
128
121
|
|
|
122
|
+
tr > td:first-child {
|
|
123
|
+
@include typography.cards-table-list-highlight-text;
|
|
124
|
+
}
|
|
125
|
+
|
|
129
126
|
tr.disabled {
|
|
130
127
|
background: color_alias.$neutral-color-50;
|
|
131
128
|
|
|
@@ -133,9 +130,89 @@
|
|
|
133
130
|
@include typography.cards-table-list-disabled-text;
|
|
134
131
|
}
|
|
135
132
|
}
|
|
133
|
+
}
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
// Media queries
|
|
136
|
+
$small: 375px;
|
|
137
|
+
$medium: 768px;
|
|
138
|
+
$large: 1200px;
|
|
139
|
+
|
|
140
|
+
// Mobile & tablet cases
|
|
141
|
+
@media only screen and (min-width: $small) and (max-width: $large) {
|
|
142
|
+
thead {
|
|
143
|
+
display: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
tr {
|
|
147
|
+
flex-direction: row;
|
|
148
|
+
flex-wrap: wrap;
|
|
149
|
+
position: relative;
|
|
150
|
+
gap: config.$space-1x;
|
|
151
|
+
padding: config.$space-3x;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
td {
|
|
155
|
+
width: 100%;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
td:first-child {
|
|
159
|
+
order: -2;
|
|
160
|
+
width: 50%;
|
|
161
|
+
margin-bottom: config.$space-2x;
|
|
162
|
+
}
|
|
163
|
+
td.actions {
|
|
164
|
+
order: -1;
|
|
165
|
+
width: 35%;
|
|
166
|
+
flex-grow: 1;
|
|
167
|
+
margin-bottom: config.$space-2x;
|
|
168
|
+
|
|
169
|
+
> div {
|
|
170
|
+
justify-content: flex-end;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.badge {
|
|
175
|
+
position: absolute;
|
|
176
|
+
inset: auto config.$space-3x config.$space-3x auto;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Desktop case
|
|
180
|
+
@media only screen and (min-width: $large) {
|
|
181
|
+
thead {
|
|
182
|
+
display: flex;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
tr {
|
|
186
|
+
flex-direction: row;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
th,
|
|
190
|
+
td {
|
|
191
|
+
display: flex;
|
|
192
|
+
justify-content: flex-start;
|
|
193
|
+
align-items: center;
|
|
194
|
+
flex: 2;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
td {
|
|
198
|
+
padding: config.$space-5x config.$space-3x;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
th.actions {
|
|
202
|
+
flex: 1;
|
|
203
|
+
}
|
|
204
|
+
td.actions {
|
|
205
|
+
order: 0;
|
|
206
|
+
justify-content: center;
|
|
207
|
+
flex: 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
td:has(.badge) {
|
|
211
|
+
gap: config.$space-2x;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.alignment-right {
|
|
215
|
+
justify-content: flex-end;
|
|
139
216
|
}
|
|
140
217
|
}
|
|
141
218
|
}
|
package/src/atoms/CardsTable.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
1
2
|
import './CardsTable.scss'
|
|
2
3
|
|
|
3
4
|
export type Variant = 'primary'
|
|
@@ -8,13 +9,13 @@ export interface CardsTableProps
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export function CardsTable({
|
|
12
|
+
className,
|
|
11
13
|
summary,
|
|
12
14
|
variant = 'primary',
|
|
13
15
|
children,
|
|
14
16
|
...props
|
|
15
17
|
}: CardsTableProps): React.JSX.Element {
|
|
16
|
-
const cssClasses =
|
|
17
|
-
|
|
18
|
+
const cssClasses = classNames('cards-table-list', variant, className)
|
|
18
19
|
return (
|
|
19
20
|
<table summary={summary} role="table" className={cssClasses} {...props}>
|
|
20
21
|
{children}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
1
2
|
import './CardsTable.scss'
|
|
2
3
|
import React from 'react'
|
|
3
4
|
|
|
@@ -17,13 +18,12 @@ export function CardsTableCell({
|
|
|
17
18
|
noWrap = false,
|
|
18
19
|
align = Alignment.Left,
|
|
19
20
|
children,
|
|
21
|
+
className,
|
|
20
22
|
...props
|
|
21
23
|
}: CardsTableCellProps): React.JSX.Element {
|
|
22
|
-
const cssClasses =
|
|
23
|
-
'
|
|
24
|
-
|
|
25
|
-
`alignment-${align}`,
|
|
26
|
-
].join(' ')
|
|
24
|
+
const cssClasses = classNames('cell', `alignment-${align}`, className, {
|
|
25
|
+
'no-wrap': noWrap,
|
|
26
|
+
})
|
|
27
27
|
return (
|
|
28
28
|
<td role="cell" className={cssClasses} {...props}>
|
|
29
29
|
{children}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
2
|
+
|
|
1
3
|
export interface CardsTableHeaderProps
|
|
2
4
|
extends React.ComponentPropsWithoutRef<'th'> {}
|
|
3
5
|
|
|
@@ -6,7 +8,7 @@ export function CardsTableHeader({
|
|
|
6
8
|
className,
|
|
7
9
|
...props
|
|
8
10
|
}: CardsTableHeaderProps) {
|
|
9
|
-
const cssClasses =
|
|
11
|
+
const cssClasses = classNames('header', className)
|
|
10
12
|
return (
|
|
11
13
|
<th scope="col" role="columnheader" className={cssClasses} {...props}>
|
|
12
14
|
{children}
|
package/src/atoms/Checkbox.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
1
2
|
import './Checkbox.scss'
|
|
2
3
|
|
|
3
4
|
export type Variant = 'primary'
|
|
@@ -17,26 +18,28 @@ export function Checkbox({
|
|
|
17
18
|
disabled,
|
|
18
19
|
variant = 'primary',
|
|
19
20
|
id,
|
|
21
|
+
name,
|
|
20
22
|
...props
|
|
21
23
|
}: CheckboxProps) {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
+
const identifier = id || name
|
|
25
|
+
const inputCss = classNames('checkbox', variant)
|
|
26
|
+
const labelCss = classNames('checkbox-label-container', {
|
|
27
|
+
disabled: disabled,
|
|
28
|
+
})
|
|
24
29
|
|
|
25
30
|
return (
|
|
26
31
|
<div className={`checkbox-group ${variant}`}>
|
|
27
32
|
<input
|
|
28
|
-
id={
|
|
33
|
+
id={identifier}
|
|
34
|
+
name={name}
|
|
29
35
|
type="checkbox"
|
|
30
|
-
className={
|
|
36
|
+
className={inputCss}
|
|
31
37
|
disabled={disabled}
|
|
32
38
|
aria-label={accessibilityLabel}
|
|
33
39
|
{...props}
|
|
34
40
|
/>
|
|
35
41
|
|
|
36
|
-
<label
|
|
37
|
-
className={`checkbox-label-container ${disabledClass}`}
|
|
38
|
-
htmlFor={id}
|
|
39
|
-
>
|
|
42
|
+
<label className={labelCss} htmlFor={identifier}>
|
|
40
43
|
<span className="background-icon"></span>
|
|
41
44
|
{!hideLabel && <span className="label">{label}</span>}
|
|
42
45
|
</label>
|
package/src/atoms/EmptyState.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
1
2
|
import { Button, ButtonProps } from './Button'
|
|
2
3
|
import './EmptyState.scss'
|
|
3
4
|
import { Icon, IconType } from './Icon'
|
|
@@ -12,12 +13,13 @@ export interface EmptyStateProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export function EmptyState({
|
|
16
|
+
className,
|
|
15
17
|
icon = 'EmptyState',
|
|
16
18
|
text = 'No data',
|
|
17
19
|
variant = 'primary',
|
|
18
20
|
button,
|
|
19
21
|
}: EmptyStateProps): React.JSX.Element {
|
|
20
|
-
const cssClasses =
|
|
22
|
+
const cssClasses = classNames('empty-state', variant, className)
|
|
21
23
|
|
|
22
24
|
return (
|
|
23
25
|
<div className={cssClasses}>
|
package/src/atoms/Icon.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './Icon.scss'
|
|
2
2
|
|
|
3
3
|
import * as icons from '../icons'
|
|
4
|
+
import { classNames } from '@/utils/classNames'
|
|
4
5
|
export type IconType = keyof typeof icons
|
|
5
6
|
|
|
6
7
|
export interface IconProps extends React.SVGAttributes<HTMLOrSVGElement> {
|
|
@@ -9,11 +10,9 @@ export interface IconProps extends React.SVGAttributes<HTMLOrSVGElement> {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export const Icon: React.FC<IconProps> = ({ name, className, ...props }) => {
|
|
12
|
-
const cssClasses =
|
|
13
|
-
'
|
|
14
|
-
|
|
15
|
-
name === 'Loading' ? 'rotate' : '',
|
|
16
|
-
].join(' ')
|
|
13
|
+
const cssClasses = classNames('icon', className, {
|
|
14
|
+
rotate: name === 'Loading',
|
|
15
|
+
})
|
|
17
16
|
return (
|
|
18
17
|
<span role="img" title={name} className={cssClasses}>
|
|
19
18
|
{icons[name](props)}
|
package/src/atoms/IconButton.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import NextLink from 'next/link'
|
|
2
2
|
import './IconButton.scss'
|
|
3
3
|
import { Icon, IconType } from './Icon'
|
|
4
|
+
import { classNames } from '@/utils/classNames'
|
|
4
5
|
|
|
5
6
|
export type Variant = 'primary'
|
|
6
7
|
|
|
@@ -29,15 +30,15 @@ export function IconButton({
|
|
|
29
30
|
variant = 'primary',
|
|
30
31
|
...props
|
|
31
32
|
}: IconButtonProps) {
|
|
32
|
-
const cssClasses =
|
|
33
|
+
const cssClasses = classNames('icon-button', variant, props.className)
|
|
33
34
|
|
|
34
35
|
if (hasHref(props)) {
|
|
35
36
|
return (
|
|
36
37
|
<NextLink
|
|
37
38
|
href={props.href || ''}
|
|
38
|
-
className={cssClasses}
|
|
39
39
|
aria-label={accessibilityLabel}
|
|
40
40
|
{...props}
|
|
41
|
+
className={cssClasses}
|
|
41
42
|
>
|
|
42
43
|
<Icon name={icon} />
|
|
43
44
|
</NextLink>
|
|
@@ -46,10 +47,10 @@ export function IconButton({
|
|
|
46
47
|
|
|
47
48
|
return (
|
|
48
49
|
<button
|
|
49
|
-
className={cssClasses}
|
|
50
50
|
disabled={disabled}
|
|
51
51
|
aria-label={accessibilityLabel}
|
|
52
52
|
{...props}
|
|
53
|
+
className={cssClasses}
|
|
53
54
|
>
|
|
54
55
|
<Icon name={icon} />
|
|
55
56
|
</button>
|
package/src/atoms/Input.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import './Input.scss'
|
|
2
1
|
import React, { useState } from 'react'
|
|
3
2
|
import { Icon, IconType } from './Icon'
|
|
3
|
+
import { classNames } from '@/utils/classNames'
|
|
4
|
+
import './Input.scss'
|
|
5
|
+
import { buildHelpText } from '@/utils/buildHelpText'
|
|
4
6
|
|
|
5
7
|
export type InputVariant = 'primary'
|
|
6
8
|
|
|
@@ -18,6 +20,7 @@ export interface InputProps extends React.ComponentPropsWithoutRef<'input'> {
|
|
|
18
20
|
export function Input({
|
|
19
21
|
label,
|
|
20
22
|
accessibilityLabel,
|
|
23
|
+
className,
|
|
21
24
|
hideLabel = false,
|
|
22
25
|
icon,
|
|
23
26
|
helpText,
|
|
@@ -29,10 +32,13 @@ export function Input({
|
|
|
29
32
|
errors,
|
|
30
33
|
...props
|
|
31
34
|
}: InputProps): React.JSX.Element {
|
|
35
|
+
const identifier = id || name
|
|
32
36
|
const [showPassword, setShowPassword] = useState(false)
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
const cssClasses = classNames('input', className, {
|
|
38
|
+
'with-icon': icon,
|
|
39
|
+
invalid: errors?.length,
|
|
40
|
+
})
|
|
41
|
+
const helpTexts = buildHelpText(helpText, errors)
|
|
36
42
|
|
|
37
43
|
function handlePasswordIcon() {
|
|
38
44
|
return showPassword ? 'ShowOff' : 'Show'
|
|
@@ -51,14 +57,14 @@ export function Input({
|
|
|
51
57
|
return (
|
|
52
58
|
<div className={`input-group ${variant}`}>
|
|
53
59
|
{!hideLabel && (
|
|
54
|
-
<label className="input-label" htmlFor={
|
|
60
|
+
<label className="input-label" htmlFor={identifier}>
|
|
55
61
|
{label}
|
|
56
62
|
</label>
|
|
57
63
|
)}
|
|
58
64
|
<div className="input-container">
|
|
59
65
|
{icon && <Icon className="left-icon" name={icon} />}
|
|
60
66
|
<input
|
|
61
|
-
id={
|
|
67
|
+
id={identifier}
|
|
62
68
|
className={cssClasses}
|
|
63
69
|
disabled={disabled}
|
|
64
70
|
type={handleInputType()}
|
|
@@ -74,17 +80,11 @@ export function Input({
|
|
|
74
80
|
/>
|
|
75
81
|
)}
|
|
76
82
|
</div>
|
|
77
|
-
{helpText
|
|
78
|
-
<span className="input-help-text">
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<span key={`error-${index}`} className="input-help-text">
|
|
84
|
-
{error}
|
|
85
|
-
</span>
|
|
86
|
-
)
|
|
87
|
-
})}
|
|
83
|
+
{helpTexts.map((helpText) => (
|
|
84
|
+
<span key={`${identifier}-${helpText}`} className="input-help-text">
|
|
85
|
+
{helpText}
|
|
86
|
+
</span>
|
|
87
|
+
))}
|
|
88
88
|
</div>
|
|
89
89
|
)
|
|
90
90
|
}
|
package/src/atoms/Modal.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
1
2
|
import { Button, ButtonProps } from './Button'
|
|
2
3
|
import { Icon } from './Icon'
|
|
3
4
|
import './Modal.scss'
|
|
@@ -21,13 +22,14 @@ export interface ModalProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
21
22
|
|
|
22
23
|
export function Modal({
|
|
23
24
|
id,
|
|
25
|
+
className,
|
|
24
26
|
variant = 'info',
|
|
25
27
|
title,
|
|
26
28
|
buttons,
|
|
27
29
|
children,
|
|
28
30
|
...props
|
|
29
31
|
}: ModalProps): React.JSX.Element {
|
|
30
|
-
const cssClasses =
|
|
32
|
+
const cssClasses = classNames('modal', variant, className)
|
|
31
33
|
|
|
32
34
|
return (
|
|
33
35
|
<div className="modal-container">
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import './Multiselect.scss'
|
|
2
2
|
import React, { useState } from 'react'
|
|
3
3
|
import { Icon } from './Icon'
|
|
4
|
+
import { classNames } from '@/utils/classNames'
|
|
5
|
+
import { buildHelpText } from '@/utils/buildHelpText'
|
|
4
6
|
|
|
5
7
|
export type Variant = 'primary'
|
|
6
8
|
export type Option = { id: string; label: string }
|
|
@@ -20,6 +22,7 @@ export interface MultiselectProps
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export function Multiselect({
|
|
25
|
+
className,
|
|
23
26
|
placeholder,
|
|
24
27
|
helpText,
|
|
25
28
|
variant = 'primary',
|
|
@@ -34,23 +37,17 @@ export function Multiselect({
|
|
|
34
37
|
selected,
|
|
35
38
|
...props
|
|
36
39
|
}: MultiselectProps): React.JSX.Element {
|
|
40
|
+
const helpTexts = buildHelpText(helpText, errors)
|
|
37
41
|
const [showOptionsList, setShowOptionsList] = useState(false)
|
|
38
42
|
const [selectedOptionsIds, setSelectedOptionsIds] = useState<string[]>(
|
|
39
43
|
selected?.map((option) => option.id) || [],
|
|
40
44
|
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const cssClasses = [
|
|
48
|
-
'selected-option',
|
|
49
|
-
optionsListOpenClass,
|
|
50
|
-
filledSelectClass,
|
|
51
|
-
disabledClass,
|
|
52
|
-
invalidClass,
|
|
53
|
-
].join(' ')
|
|
45
|
+
const cssClasses = classNames('selected-option', className, {
|
|
46
|
+
open: showOptionsList,
|
|
47
|
+
filled: selectedOptionsIds.length > 0,
|
|
48
|
+
disabled: disabled,
|
|
49
|
+
invalid: errors?.length,
|
|
50
|
+
})
|
|
54
51
|
|
|
55
52
|
function handleOptionsList() {
|
|
56
53
|
if (!disabled) setShowOptionsList(!showOptionsList)
|
|
@@ -130,19 +127,11 @@ export function Multiselect({
|
|
|
130
127
|
</ul>
|
|
131
128
|
)}
|
|
132
129
|
</div>
|
|
133
|
-
{helpText
|
|
134
|
-
<span
|
|
130
|
+
{helpTexts.map((helpText) => (
|
|
131
|
+
<span key={`${name}-${helpText}`} className="multiselect-help-text">
|
|
135
132
|
{helpText}
|
|
136
133
|
</span>
|
|
137
|
-
)}
|
|
138
|
-
{errors &&
|
|
139
|
-
errors?.map((error, index) => {
|
|
140
|
-
return (
|
|
141
|
-
<span key={`error-${index}`} className="multiselect-help-text">
|
|
142
|
-
{error}
|
|
143
|
-
</span>
|
|
144
|
-
)
|
|
145
|
-
})}
|
|
134
|
+
))}
|
|
146
135
|
<input
|
|
147
136
|
type="hidden"
|
|
148
137
|
name={name}
|
package/src/atoms/Select.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import './Select.scss'
|
|
2
2
|
import React, { useState } from 'react'
|
|
3
3
|
import { Icon } from './Icon'
|
|
4
|
+
import { classNames } from '@/utils/classNames'
|
|
5
|
+
import { buildHelpText } from '@/utils/buildHelpText'
|
|
4
6
|
|
|
5
7
|
export type Variant = 'primary'
|
|
6
8
|
export type Option = { id: string; label: string }
|
|
@@ -23,6 +25,7 @@ export interface SelectProps extends InputPropsWithoutOnChange {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function Select({
|
|
28
|
+
className,
|
|
26
29
|
placeholder,
|
|
27
30
|
helpText,
|
|
28
31
|
variant = 'primary',
|
|
@@ -37,24 +40,19 @@ export function Select({
|
|
|
37
40
|
onChange,
|
|
38
41
|
...props
|
|
39
42
|
}: SelectProps): React.JSX.Element {
|
|
43
|
+
const helpTexts = buildHelpText(helpText, errors)
|
|
40
44
|
const [showOptionsList, setShowOptionsList] = useState(false)
|
|
41
45
|
const [selectedOption, setSelectedOption] = useState<Option>({
|
|
42
46
|
id: selected?.id || '',
|
|
43
47
|
label: selected?.label || '',
|
|
44
48
|
})
|
|
45
49
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'selected-option',
|
|
53
|
-
optionsListOpenClass,
|
|
54
|
-
filledSelectClass,
|
|
55
|
-
disabledClass,
|
|
56
|
-
invalidClass,
|
|
57
|
-
].join(' ')
|
|
50
|
+
const cssClasses = classNames('selected-option', className, {
|
|
51
|
+
open: showOptionsList,
|
|
52
|
+
filled: selectedOption.id,
|
|
53
|
+
disabled: disabled,
|
|
54
|
+
invalid: errors?.length,
|
|
55
|
+
})
|
|
58
56
|
|
|
59
57
|
function handleOptionsList() {
|
|
60
58
|
if (!disabled) setShowOptionsList(!showOptionsList)
|
|
@@ -113,17 +111,11 @@ export function Select({
|
|
|
113
111
|
</ul>
|
|
114
112
|
)}
|
|
115
113
|
</div>
|
|
116
|
-
{helpText
|
|
117
|
-
<span className="select-help-text">
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<span key={`error-${index}`} className="select-help-text">
|
|
123
|
-
{error}
|
|
124
|
-
</span>
|
|
125
|
-
)
|
|
126
|
-
})}
|
|
114
|
+
{helpTexts.map((helpText) => (
|
|
115
|
+
<span key={`${name}-${helpText}`} className="select-help-text">
|
|
116
|
+
{helpText}
|
|
117
|
+
</span>
|
|
118
|
+
))}
|
|
127
119
|
<input type="hidden" name={name} value={selectedOption.id} {...props} />
|
|
128
120
|
</div>
|
|
129
121
|
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
2
|
+
import { buildHelpText } from '@/utils/buildHelpText'
|
|
3
|
+
import './Input.scss'
|
|
4
|
+
|
|
5
|
+
export type TextAreaVariant = 'primary'
|
|
6
|
+
|
|
7
|
+
export interface TextAreaProps
|
|
8
|
+
extends React.ComponentPropsWithoutRef<'textarea'> {
|
|
9
|
+
label: string
|
|
10
|
+
accessibilityLabel?: string
|
|
11
|
+
hideLabel?: boolean
|
|
12
|
+
helpText?: string
|
|
13
|
+
variant?: TextAreaVariant
|
|
14
|
+
id?: string
|
|
15
|
+
errors?: string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function TextArea({
|
|
19
|
+
id,
|
|
20
|
+
label,
|
|
21
|
+
className,
|
|
22
|
+
accessibilityLabel,
|
|
23
|
+
hideLabel = false,
|
|
24
|
+
helpText,
|
|
25
|
+
variant = 'primary',
|
|
26
|
+
disabled,
|
|
27
|
+
name,
|
|
28
|
+
errors,
|
|
29
|
+
...props
|
|
30
|
+
}: TextAreaProps) {
|
|
31
|
+
const identifier = id || name
|
|
32
|
+
const cssClasses = classNames('input', className, { invalid: errors?.length })
|
|
33
|
+
const helpTexts = buildHelpText(helpText, errors)
|
|
34
|
+
return (
|
|
35
|
+
<div className={`input-group ${variant}`}>
|
|
36
|
+
{!hideLabel && (
|
|
37
|
+
<label className="input-label" htmlFor={identifier}>
|
|
38
|
+
{label}
|
|
39
|
+
</label>
|
|
40
|
+
)}
|
|
41
|
+
<div className="input-container">
|
|
42
|
+
<textarea
|
|
43
|
+
id={identifier}
|
|
44
|
+
className={cssClasses}
|
|
45
|
+
disabled={disabled}
|
|
46
|
+
name={name}
|
|
47
|
+
aria-label={accessibilityLabel || label}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
{helpTexts.map((helpText) => (
|
|
52
|
+
<span key={`${identifier}-${helpText}`} className="input-help-text">
|
|
53
|
+
{helpText}
|
|
54
|
+
</span>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -115,6 +115,14 @@
|
|
|
115
115
|
text-decoration-line: underline;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
@mixin body-link {
|
|
119
|
+
@include base;
|
|
120
|
+
font-weight: 400;
|
|
121
|
+
color: color_alias.$primary-color-600;
|
|
122
|
+
font-size: 1rem;
|
|
123
|
+
line-height: 1.5rem;
|
|
124
|
+
}
|
|
125
|
+
|
|
118
126
|
@mixin footnote-primary {
|
|
119
127
|
@include base;
|
|
120
128
|
font-weight: 400;
|
|
@@ -6,12 +6,21 @@ import { CardsTableHeader } from '../atoms/CardsTableHeader'
|
|
|
6
6
|
import { CardsTableRow } from '../atoms/CardsTableRow'
|
|
7
7
|
import { CardsTableBody } from '../atoms/CardsTableBody'
|
|
8
8
|
import { CardsTableCell } from '../atoms/CardsTableCell'
|
|
9
|
-
import {
|
|
9
|
+
import { IconButton } from '../atoms/IconButton'
|
|
10
|
+
import { Badge } from '../atoms/Badge'
|
|
11
|
+
|
|
12
|
+
const figmaPrimaryDesign = {
|
|
13
|
+
design: {
|
|
14
|
+
type: 'figma',
|
|
15
|
+
url: 'https://www.figma.com/file/DN2ova21vWqCRvPspBXgI1/Design-System?type=design&node-id=2331-990&mode=dev',
|
|
16
|
+
},
|
|
17
|
+
}
|
|
10
18
|
|
|
11
19
|
const meta = {
|
|
12
20
|
title: 'Design System/Atoms/CardsTable',
|
|
13
21
|
component: CardsTable,
|
|
14
22
|
tags: ['autodocs'],
|
|
23
|
+
parameters: figmaPrimaryDesign,
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
export default meta
|
|
@@ -24,34 +33,101 @@ export const Primary = {
|
|
|
24
33
|
<CardsTableHeader>Game title</CardsTableHeader>
|
|
25
34
|
<CardsTableHeader>Company address</CardsTableHeader>
|
|
26
35
|
<CardsTableHeader>Customer service email</CardsTableHeader>
|
|
36
|
+
<CardsTableHeader>Price</CardsTableHeader>
|
|
37
|
+
<CardsTableHeader className="actions">Actions</CardsTableHeader>
|
|
27
38
|
</CardsTableRow>
|
|
28
39
|
</CardsTableHead>
|
|
29
40
|
<CardsTableBody>
|
|
30
41
|
<CardsTableRow>
|
|
31
|
-
<CardsTableCell>
|
|
32
|
-
|
|
42
|
+
<CardsTableCell>
|
|
43
|
+
<span>Metal Gear Solid 5: The Phantom Pain</span>
|
|
44
|
+
<Badge
|
|
45
|
+
accessibilityLabel="Game is bought"
|
|
46
|
+
text="Bought"
|
|
47
|
+
variant="success-outlined"
|
|
48
|
+
/>
|
|
49
|
+
</CardsTableCell>
|
|
50
|
+
<CardsTableCell>
|
|
33
51
|
Konami Digital Entertainment Co., Ltd. 1-11-1, Ginza, Chuo-ku,
|
|
34
52
|
Tokyo, 104-0061 Japan
|
|
35
53
|
</CardsTableCell>
|
|
36
|
-
<CardsTableCell
|
|
54
|
+
<CardsTableCell>konami@fakemail.com</CardsTableCell>
|
|
55
|
+
<CardsTableCell align="right">6,99 €</CardsTableCell>
|
|
56
|
+
<CardsTableCell className="actions" align="center">
|
|
57
|
+
<div style={{ display: 'flex', gap: '1.75rem' }}>
|
|
58
|
+
<IconButton
|
|
59
|
+
icon="Edit"
|
|
60
|
+
accessibilityLabel="Edit game"
|
|
61
|
+
href="link.com"
|
|
62
|
+
/>
|
|
63
|
+
<IconButton
|
|
64
|
+
icon="Export"
|
|
65
|
+
accessibilityLabel="Export game"
|
|
66
|
+
href="link.com"
|
|
67
|
+
/>
|
|
68
|
+
<IconButton
|
|
69
|
+
icon="Delete"
|
|
70
|
+
accessibilityLabel="Delete game"
|
|
71
|
+
href="link.com"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
</CardsTableCell>
|
|
37
75
|
</CardsTableRow>
|
|
38
76
|
|
|
39
77
|
<CardsTableRow>
|
|
40
78
|
<CardsTableCell>The Witcher 3</CardsTableCell>
|
|
41
|
-
<CardsTableCell
|
|
79
|
+
<CardsTableCell>
|
|
42
80
|
CD PROJEKT S.A. ul. Jagiellońska 74 03-301 Warszawa Poland
|
|
43
81
|
</CardsTableCell>
|
|
44
|
-
<CardsTableCell
|
|
82
|
+
<CardsTableCell>cdprojekt@fakemail.com</CardsTableCell>
|
|
83
|
+
<CardsTableCell align="right">19,99 €</CardsTableCell>
|
|
84
|
+
<CardsTableCell className="actions" align="center">
|
|
85
|
+
<div style={{ display: 'flex', gap: '1.75rem' }}>
|
|
86
|
+
<IconButton
|
|
87
|
+
icon="Edit"
|
|
88
|
+
accessibilityLabel="Edit game"
|
|
89
|
+
href="link.com"
|
|
90
|
+
/>
|
|
91
|
+
<IconButton
|
|
92
|
+
icon="Export"
|
|
93
|
+
accessibilityLabel="Export game"
|
|
94
|
+
href="link.com"
|
|
95
|
+
/>
|
|
96
|
+
<IconButton
|
|
97
|
+
icon="Delete"
|
|
98
|
+
accessibilityLabel="Delete game"
|
|
99
|
+
href="link.com"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
</CardsTableCell>
|
|
45
103
|
</CardsTableRow>
|
|
46
104
|
|
|
47
105
|
<CardsTableRow>
|
|
48
106
|
<CardsTableCell>Tekken 8</CardsTableCell>
|
|
49
|
-
<CardsTableCell
|
|
107
|
+
<CardsTableCell>
|
|
50
108
|
Bandai Namco Studios Inc. ; Address: 2-37-25 Eitai, Koto-ku, Tokyo
|
|
51
109
|
135-0034, Japan
|
|
52
110
|
</CardsTableCell>
|
|
53
|
-
<CardsTableCell
|
|
54
|
-
|
|
111
|
+
<CardsTableCell>namco@fakemail.com</CardsTableCell>
|
|
112
|
+
<CardsTableCell align="right">79,99 €</CardsTableCell>
|
|
113
|
+
<CardsTableCell className="actions" align="center">
|
|
114
|
+
<div style={{ display: 'flex', gap: '1.75rem' }}>
|
|
115
|
+
<IconButton
|
|
116
|
+
icon="Edit"
|
|
117
|
+
accessibilityLabel="Edit game"
|
|
118
|
+
href="link.com"
|
|
119
|
+
/>
|
|
120
|
+
<IconButton
|
|
121
|
+
icon="Export"
|
|
122
|
+
accessibilityLabel="Export game"
|
|
123
|
+
href="link.com"
|
|
124
|
+
/>
|
|
125
|
+
<IconButton
|
|
126
|
+
icon="Delete"
|
|
127
|
+
accessibilityLabel="Delete game"
|
|
128
|
+
href="link.com"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
55
131
|
</CardsTableCell>
|
|
56
132
|
</CardsTableRow>
|
|
57
133
|
</CardsTableBody>
|
|
@@ -26,7 +26,7 @@ const meta = {
|
|
|
26
26
|
const figmaPrimaryDesign = {
|
|
27
27
|
design: {
|
|
28
28
|
type: 'figma',
|
|
29
|
-
url: 'https://www.figma.com/file/DN2ova21vWqCRvPspBXgI1/Design-System?type=design&node-id=
|
|
29
|
+
url: 'https://www.figma.com/file/DN2ova21vWqCRvPspBXgI1/Design-System?type=design&node-id=2331-990&mode=dev',
|
|
30
30
|
},
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -3,6 +3,15 @@ import { Meta } from "@storybook/addon-docs";
|
|
|
3
3
|
<Meta title="Changelog" />
|
|
4
4
|
# Changelog
|
|
5
5
|
|
|
6
|
+
## 0.11.0
|
|
7
|
+
- Added TextArea component to Storybook.
|
|
8
|
+
- Id prop is now optional in Input, Select, Multiselect and TextArea components.
|
|
9
|
+
|
|
10
|
+
## 0.10.0
|
|
11
|
+
|
|
12
|
+
- Cards Table components now are responsive.
|
|
13
|
+
- Added Link style to Typography.
|
|
14
|
+
|
|
6
15
|
## 0.9.3
|
|
7
16
|
|
|
8
17
|
- Fix alert component width.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import TextArea from '../atoms/TextArea'
|
|
2
|
+
import { StoryObj } from '@storybook/react'
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Design System/Atoms/Textarea',
|
|
6
|
+
component: TextArea,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
argTypes: {
|
|
9
|
+
label: {
|
|
10
|
+
description: 'Label for the textarea',
|
|
11
|
+
},
|
|
12
|
+
accessibilityLabel: {
|
|
13
|
+
description:
|
|
14
|
+
'Describes the textarea purpose. If empty, label content will be used',
|
|
15
|
+
},
|
|
16
|
+
placeholder: {
|
|
17
|
+
description: 'Optional textarea placeholder text',
|
|
18
|
+
},
|
|
19
|
+
variant: {
|
|
20
|
+
description: 'Textarea variant used',
|
|
21
|
+
},
|
|
22
|
+
disabled: {
|
|
23
|
+
description: 'Is the textarea in disabled state?',
|
|
24
|
+
},
|
|
25
|
+
helpText: {
|
|
26
|
+
description: 'Optional help text',
|
|
27
|
+
},
|
|
28
|
+
name: {
|
|
29
|
+
description: 'Textarea name property',
|
|
30
|
+
},
|
|
31
|
+
id: {
|
|
32
|
+
description: 'Value needed for the label relation',
|
|
33
|
+
},
|
|
34
|
+
errors: {
|
|
35
|
+
description:
|
|
36
|
+
'Optional array of errors. If passed, the errors are listed and invalid style is applied.',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const figmaPrimaryDesign = {
|
|
42
|
+
design: {
|
|
43
|
+
type: 'figma',
|
|
44
|
+
url: 'https://www.figma.com/file/DN2ova21vWqCRvPspBXgI1/Design-System?type=design&node-id=2371-2157&mode=dev',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default meta
|
|
49
|
+
type Story = StoryObj<typeof meta>
|
|
50
|
+
|
|
51
|
+
export const Primary: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
label: 'Textarea',
|
|
54
|
+
accessibilityLabel: 'Fill the textarea',
|
|
55
|
+
placeholder: 'Write here...',
|
|
56
|
+
variant: 'primary',
|
|
57
|
+
disabled: false,
|
|
58
|
+
helpText: 'This text can help you',
|
|
59
|
+
name: 'textarea',
|
|
60
|
+
id: 'textarea',
|
|
61
|
+
},
|
|
62
|
+
parameters: figmaPrimaryDesign,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const WithErrors: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
label: 'Textarea',
|
|
68
|
+
accessibilityLabel: 'Fill the form textarea',
|
|
69
|
+
placeholder: 'Write here...',
|
|
70
|
+
variant: 'primary',
|
|
71
|
+
disabled: false,
|
|
72
|
+
helpText: 'This text can help you',
|
|
73
|
+
name: 'textarea',
|
|
74
|
+
id: 'textarea',
|
|
75
|
+
errors: ['error1', 'error2'],
|
|
76
|
+
},
|
|
77
|
+
parameters: figmaPrimaryDesign,
|
|
78
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type classNameProp = string | { [key: string]: any } | undefined
|
|
2
|
+
|
|
3
|
+
export const classNames = (...classNames: classNameProp[]): string => {
|
|
4
|
+
const resultClasses: string[] = []
|
|
5
|
+
|
|
6
|
+
classNames.forEach((className) => {
|
|
7
|
+
if (className === undefined) {
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof className === 'string') {
|
|
12
|
+
return resultClasses.push(className)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Object.keys(className).forEach((key) => {
|
|
16
|
+
if (Boolean(className[key])) {
|
|
17
|
+
resultClasses.push(key)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return resultClasses.join(' ')
|
|
23
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render } from '@testing-library/react'
|
|
3
|
+
import { CardsTable } from '@/atoms/CardsTable'
|
|
4
|
+
import { CardsTableHead } from '@/atoms/CardsTableHead'
|
|
5
|
+
import { CardsTableHeader } from '@/atoms/CardsTableHeader'
|
|
6
|
+
import { CardsTableRow } from '@/atoms/CardsTableRow'
|
|
7
|
+
import { CardsTableBody } from '@/atoms/CardsTableBody'
|
|
8
|
+
import { CardsTableCell, Alignment } from '@/atoms/CardsTableCell'
|
|
9
|
+
import { IconButton } from '@/atoms/IconButton'
|
|
10
|
+
import { Badge } from '@/atoms/Badge'
|
|
11
|
+
|
|
12
|
+
describe('CardsTable', () => {
|
|
13
|
+
it('renders', () => {
|
|
14
|
+
const { getAllByRole } = render(
|
|
15
|
+
<CardsTable>
|
|
16
|
+
<CardsTableHead>
|
|
17
|
+
<CardsTableRow>
|
|
18
|
+
<CardsTableHeader>Game title</CardsTableHeader>
|
|
19
|
+
<CardsTableHeader>Company address</CardsTableHeader>
|
|
20
|
+
<CardsTableHeader>Customer service email</CardsTableHeader>
|
|
21
|
+
<CardsTableHeader>Price</CardsTableHeader>
|
|
22
|
+
<CardsTableHeader className="actions">Actions</CardsTableHeader>
|
|
23
|
+
</CardsTableRow>
|
|
24
|
+
</CardsTableHead>
|
|
25
|
+
<CardsTableBody>
|
|
26
|
+
<CardsTableRow>
|
|
27
|
+
<CardsTableCell>
|
|
28
|
+
<span>Metal Gear Solid 5: The Phantom Pain</span>
|
|
29
|
+
<Badge
|
|
30
|
+
accessibilityLabel="Game is bought"
|
|
31
|
+
text="Bought"
|
|
32
|
+
variant="success-outlined"
|
|
33
|
+
/>
|
|
34
|
+
</CardsTableCell>
|
|
35
|
+
<CardsTableCell>
|
|
36
|
+
Konami Digital Entertainment Co., Ltd. 1-11-1, Ginza, Chuo-ku,
|
|
37
|
+
Tokyo, 104-0061 Japan
|
|
38
|
+
</CardsTableCell>
|
|
39
|
+
<CardsTableCell>konami@fakemail.com</CardsTableCell>
|
|
40
|
+
<CardsTableCell align={Alignment.Right}>6,99 €</CardsTableCell>
|
|
41
|
+
<CardsTableCell className="actions" align={Alignment.Center}>
|
|
42
|
+
<div style={{ display: 'flex', gap: '1.75rem' }}>
|
|
43
|
+
<IconButton
|
|
44
|
+
icon="Edit"
|
|
45
|
+
accessibilityLabel="Edit game"
|
|
46
|
+
href="link.com"
|
|
47
|
+
/>
|
|
48
|
+
<IconButton
|
|
49
|
+
icon="Export"
|
|
50
|
+
accessibilityLabel="Export game"
|
|
51
|
+
href="link.com"
|
|
52
|
+
/>
|
|
53
|
+
<IconButton
|
|
54
|
+
icon="Delete"
|
|
55
|
+
accessibilityLabel="Delete game"
|
|
56
|
+
href="link.com"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
</CardsTableCell>
|
|
60
|
+
</CardsTableRow>
|
|
61
|
+
|
|
62
|
+
<CardsTableRow>
|
|
63
|
+
<CardsTableCell>The Witcher 3</CardsTableCell>
|
|
64
|
+
<CardsTableCell>
|
|
65
|
+
CD PROJEKT S.A. ul. Jagiellońska 74 03-301 Warszawa Poland
|
|
66
|
+
</CardsTableCell>
|
|
67
|
+
<CardsTableCell>cdprojekt@fakemail.com</CardsTableCell>
|
|
68
|
+
<CardsTableCell align={Alignment.Right}>19,99 €</CardsTableCell>
|
|
69
|
+
<CardsTableCell className="actions" align={Alignment.Center}>
|
|
70
|
+
<div style={{ display: 'flex', gap: '1.75rem' }}>
|
|
71
|
+
<IconButton
|
|
72
|
+
icon="Edit"
|
|
73
|
+
accessibilityLabel="Edit game"
|
|
74
|
+
href="link.com"
|
|
75
|
+
/>
|
|
76
|
+
<IconButton
|
|
77
|
+
icon="Export"
|
|
78
|
+
accessibilityLabel="Export game"
|
|
79
|
+
href="link.com"
|
|
80
|
+
/>
|
|
81
|
+
<IconButton
|
|
82
|
+
icon="Delete"
|
|
83
|
+
accessibilityLabel="Delete game"
|
|
84
|
+
href="link.com"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</CardsTableCell>
|
|
88
|
+
</CardsTableRow>
|
|
89
|
+
|
|
90
|
+
<CardsTableRow>
|
|
91
|
+
<CardsTableCell>Tekken 8</CardsTableCell>
|
|
92
|
+
<CardsTableCell>
|
|
93
|
+
Bandai Namco Studios Inc. ; Address: 2-37-25 Eitai, Koto-ku, Tokyo
|
|
94
|
+
135-0034, Japan
|
|
95
|
+
</CardsTableCell>
|
|
96
|
+
<CardsTableCell>namco@fakemail.com</CardsTableCell>
|
|
97
|
+
<CardsTableCell align={Alignment.Right}>79,99 €</CardsTableCell>
|
|
98
|
+
<CardsTableCell className="actions" align={Alignment.Center}>
|
|
99
|
+
<div style={{ display: 'flex', gap: '1.75rem' }}>
|
|
100
|
+
<IconButton
|
|
101
|
+
icon="Edit"
|
|
102
|
+
accessibilityLabel="Edit game"
|
|
103
|
+
href="link.com"
|
|
104
|
+
/>
|
|
105
|
+
<IconButton
|
|
106
|
+
icon="Export"
|
|
107
|
+
accessibilityLabel="Export game"
|
|
108
|
+
href="link.com"
|
|
109
|
+
/>
|
|
110
|
+
<IconButton
|
|
111
|
+
icon="Delete"
|
|
112
|
+
accessibilityLabel="Delete game"
|
|
113
|
+
href="link.com"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
</CardsTableCell>
|
|
117
|
+
</CardsTableRow>
|
|
118
|
+
</CardsTableBody>
|
|
119
|
+
</CardsTable>,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
expect(getAllByRole('table').length).toBe(1)
|
|
123
|
+
expect(getAllByRole('rowgroup').length).toBe(2)
|
|
124
|
+
expect(getAllByRole('columnheader').length).toBeGreaterThan(1)
|
|
125
|
+
expect(getAllByRole('row').length).toBeGreaterThan(1)
|
|
126
|
+
expect(getAllByRole('cell').length).toBeGreaterThan(1)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { classNames } from '@/utils/classNames'
|
|
2
|
+
|
|
3
|
+
describe('classNames', () => {
|
|
4
|
+
it('returns a string of classes', () => {
|
|
5
|
+
expect(classNames('class1', 'class2')).toBe('class1 class2')
|
|
6
|
+
})
|
|
7
|
+
it('ignores undefined', () => {
|
|
8
|
+
expect(classNames('class1', undefined, 'class2')).toBe('class1 class2')
|
|
9
|
+
})
|
|
10
|
+
it('returns a string of classes with object', () => {
|
|
11
|
+
expect(classNames({ class1: true, class2: false })).toBe('class1')
|
|
12
|
+
})
|
|
13
|
+
})
|