agroptima-design-system 0.3.2 → 0.5.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 +0 -1
- package/package.json +2 -3
- package/src/atoms/Button.scss +3 -1
- package/src/atoms/Button.tsx +46 -10
- package/src/atoms/{CardsTableList.scss → CardsTable.scss} +16 -0
- package/src/atoms/CardsTable.tsx +23 -0
- package/src/atoms/CardsTableBody.tsx +16 -0
- package/src/atoms/CardsTableCell.tsx +32 -0
- package/src/atoms/CardsTableHead.tsx +16 -0
- package/src/atoms/CardsTableHeader.tsx +15 -0
- package/src/atoms/CardsTableList.tsx +53 -27
- package/src/atoms/CardsTableRow.tsx +20 -0
- package/src/atoms/EmptyState.tsx +4 -10
- package/src/atoms/IconButton.scss +50 -0
- package/src/atoms/IconButton.tsx +57 -0
- package/src/atoms/Input.tsx +17 -5
- package/src/atoms/Multiselect.tsx +32 -8
- package/src/atoms/Select.tsx +32 -8
- package/src/icons/delete.svg +1 -0
- package/src/icons/edit.svg +1 -0
- package/src/icons/index.tsx +4 -0
- package/src/stories/Button.stories.ts +19 -0
- package/src/stories/CardsTable.stories.js +60 -0
- package/src/stories/Changelog.stories.mdx +21 -0
- package/src/stories/EmptyState.stories.ts +4 -2
- package/src/stories/IconButton.stories.ts +58 -0
- package/src/stories/Input.stories.ts +25 -0
- package/src/stories/Multiselect.stories.ts +36 -5
- package/src/stories/Select.stories.ts +34 -2
- package/src/utils/sort.ts +2 -1
- package/tsconfig.json +1 -1
- package/src/atoms/CardsTableListHeader.tsx +0 -32
- package/src/atoms/CardsTableListRow.tsx +0 -15
package/.storybook/main.ts
CHANGED
|
@@ -13,7 +13,6 @@ const config: StorybookConfig = {
|
|
|
13
13
|
addons: [
|
|
14
14
|
getAbsolutePath('@storybook/addon-links'),
|
|
15
15
|
getAbsolutePath('@storybook/addon-essentials'),
|
|
16
|
-
getAbsolutePath('@storybook/addon-onboarding'),
|
|
17
16
|
getAbsolutePath('@storybook/addon-interactions'),
|
|
18
17
|
'@storybook/addon-a11y',
|
|
19
18
|
'@storybook/addon-designs',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agroptima-design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "npm run storybook",
|
|
6
6
|
"storybook": "storybook dev -p 6006 --ci",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"build-storybook": "storybook build",
|
|
9
9
|
"lint": "eslint",
|
|
10
10
|
"lint:fix": "eslint src --fix",
|
|
11
|
-
"
|
|
11
|
+
"chromatic": "npx chromatic --exit-zero-on-changes"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@storybook/addon-designs": "^7.0.5",
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"@storybook/addon-essentials": "^7.5.0",
|
|
24
24
|
"@storybook/addon-interactions": "^7.5.0",
|
|
25
25
|
"@storybook/addon-links": "^7.5.0",
|
|
26
|
-
"@storybook/addon-onboarding": "^1.0.8",
|
|
27
26
|
"@storybook/blocks": "^7.5.0",
|
|
28
27
|
"@storybook/nextjs": "^7.5.0",
|
|
29
28
|
"@storybook/react": "^7.5.0",
|
package/src/atoms/Button.scss
CHANGED
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
--color-fg-disabled: #{color_alias.$neutral-color-50};
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
|
|
62
64
|
display: inline-flex;
|
|
63
65
|
height: fit-content;
|
|
64
66
|
background: var(--color-bg);
|
|
@@ -144,7 +146,7 @@
|
|
|
144
146
|
}
|
|
145
147
|
&:disabled {
|
|
146
148
|
background: color_alias.$neutral-color-50;
|
|
147
|
-
border:
|
|
149
|
+
border: none;
|
|
148
150
|
color: color_alias.$neutral-color-400;
|
|
149
151
|
svg,
|
|
150
152
|
svg path {
|
package/src/atoms/Button.tsx
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
+
import NextLink from 'next/link'
|
|
1
2
|
import './Button.scss'
|
|
2
3
|
import { Icon, IconType } from './Icon'
|
|
3
4
|
|
|
5
|
+
export interface BaseButtonProps {
|
|
6
|
+
label: string
|
|
7
|
+
accessibilityLabel?: string
|
|
8
|
+
leftIcon?: IconType
|
|
9
|
+
rightIcon?: IconType
|
|
10
|
+
variant?: ButtonVariant
|
|
11
|
+
loading?: boolean
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type HtmlButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
16
|
+
|
|
17
|
+
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
18
|
+
|
|
19
|
+
export type ButtonProps =
|
|
20
|
+
| (HtmlButtonProps & BaseButtonProps)
|
|
21
|
+
| (AnchorProps & BaseButtonProps)
|
|
22
|
+
|
|
23
|
+
const hasHref = (props: HtmlButtonProps | AnchorProps): props is AnchorProps =>
|
|
24
|
+
'href' in props
|
|
25
|
+
|
|
4
26
|
export type ButtonVariant =
|
|
5
27
|
| 'primary'
|
|
6
28
|
| 'primary-ghost'
|
|
@@ -21,29 +43,43 @@ export type ButtonVariant =
|
|
|
21
43
|
| 'warning-ghost'
|
|
22
44
|
| 'warning-outlined'
|
|
23
45
|
|
|
24
|
-
export interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
|
|
25
|
-
label: string
|
|
26
|
-
leftIcon?: IconType
|
|
27
|
-
rightIcon?: IconType
|
|
28
|
-
variant?: ButtonVariant
|
|
29
|
-
loading?: boolean
|
|
30
|
-
}
|
|
31
|
-
|
|
32
46
|
export function Button({
|
|
33
47
|
label,
|
|
48
|
+
accessibilityLabel,
|
|
34
49
|
leftIcon,
|
|
35
50
|
rightIcon,
|
|
36
51
|
disabled,
|
|
37
52
|
variant = 'primary',
|
|
38
53
|
loading = false,
|
|
39
54
|
...props
|
|
40
|
-
}: ButtonProps)
|
|
55
|
+
}: ButtonProps) {
|
|
41
56
|
if (loading) {
|
|
42
57
|
leftIcon = 'Loading'
|
|
43
58
|
}
|
|
44
59
|
const cssClasses = ['button', variant].join(' ')
|
|
60
|
+
|
|
61
|
+
if (hasHref(props)) {
|
|
62
|
+
return (
|
|
63
|
+
<NextLink
|
|
64
|
+
href={props.href || ''}
|
|
65
|
+
className={cssClasses}
|
|
66
|
+
aria-label={accessibilityLabel || label}
|
|
67
|
+
{...props}
|
|
68
|
+
>
|
|
69
|
+
{leftIcon && <Icon name={leftIcon} />}
|
|
70
|
+
{label}
|
|
71
|
+
{rightIcon && <Icon name={rightIcon} />}
|
|
72
|
+
</NextLink>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
45
76
|
return (
|
|
46
|
-
<button
|
|
77
|
+
<button
|
|
78
|
+
className={cssClasses}
|
|
79
|
+
disabled={loading || disabled}
|
|
80
|
+
aria-label={accessibilityLabel || label}
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
47
83
|
{leftIcon && <Icon name={leftIcon} />}
|
|
48
84
|
{label}
|
|
49
85
|
{rightIcon && <Icon name={rightIcon} />}
|
|
@@ -48,6 +48,22 @@
|
|
|
48
48
|
padding: config.$space-5x config.$space-3x;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
.no-wrap {
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.alignment-left {
|
|
56
|
+
text-align: left;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.alignment-center {
|
|
60
|
+
text-align: center;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.alignment-right {
|
|
64
|
+
text-align: right;
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
&.primary {
|
|
52
68
|
thead > tr {
|
|
53
69
|
@include typography.cards-table-list-header;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import './CardsTable.scss'
|
|
2
|
+
|
|
3
|
+
export type Variant = 'primary'
|
|
4
|
+
|
|
5
|
+
export interface CardsTableProps
|
|
6
|
+
extends React.ComponentPropsWithoutRef<'table'> {
|
|
7
|
+
variant?: Variant
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function CardsTable({
|
|
11
|
+
summary,
|
|
12
|
+
variant = 'primary',
|
|
13
|
+
children,
|
|
14
|
+
...props
|
|
15
|
+
}: CardsTableProps): React.JSX.Element {
|
|
16
|
+
const cssClasses = ['cards-table-list', variant].join(' ')
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<table summary={summary} role="table" className={cssClasses} {...props}>
|
|
20
|
+
{children}
|
|
21
|
+
</table>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import './CardsTable.scss'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export interface CardsTableBodyProps
|
|
5
|
+
extends React.ComponentPropsWithoutRef<'tbody'> {}
|
|
6
|
+
|
|
7
|
+
export function CardsTableBody({
|
|
8
|
+
children,
|
|
9
|
+
...props
|
|
10
|
+
}: CardsTableBodyProps): React.JSX.Element {
|
|
11
|
+
return (
|
|
12
|
+
<tbody role="rowgroup" {...props}>
|
|
13
|
+
{children}
|
|
14
|
+
</tbody>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import './CardsTable.scss'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export enum Alignment {
|
|
5
|
+
Left = 'left',
|
|
6
|
+
Right = 'right',
|
|
7
|
+
Center = 'center',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CardsTableCellProps
|
|
11
|
+
extends React.ComponentPropsWithoutRef<'td'> {
|
|
12
|
+
noWrap?: boolean
|
|
13
|
+
align?: Alignment
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function CardsTableCell({
|
|
17
|
+
noWrap = false,
|
|
18
|
+
align = Alignment.Left,
|
|
19
|
+
children,
|
|
20
|
+
...props
|
|
21
|
+
}: CardsTableCellProps): React.JSX.Element {
|
|
22
|
+
const cssClasses = [
|
|
23
|
+
'cell',
|
|
24
|
+
noWrap ? 'no-wrap' : '',
|
|
25
|
+
`alignment-${align}`,
|
|
26
|
+
].join(' ')
|
|
27
|
+
return (
|
|
28
|
+
<td role="cell" className={cssClasses} {...props}>
|
|
29
|
+
{children}
|
|
30
|
+
</td>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import './CardsTable.scss'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export interface CardsTableHeadProps
|
|
5
|
+
extends React.ComponentPropsWithoutRef<'thead'> {}
|
|
6
|
+
|
|
7
|
+
export function CardsTableHead({
|
|
8
|
+
children,
|
|
9
|
+
...props
|
|
10
|
+
}: CardsTableHeadProps): React.JSX.Element {
|
|
11
|
+
return (
|
|
12
|
+
<thead role="rowgroup" {...props}>
|
|
13
|
+
{children}
|
|
14
|
+
</thead>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface CardsTableHeaderProps
|
|
2
|
+
extends React.ComponentPropsWithoutRef<'th'> {}
|
|
3
|
+
|
|
4
|
+
export function CardsTableHeader({
|
|
5
|
+
children,
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: CardsTableHeaderProps) {
|
|
9
|
+
const cssClasses = ['header', className].join(' ')
|
|
10
|
+
return (
|
|
11
|
+
<th scope="col" role="columnheader" className={cssClasses} {...props}>
|
|
12
|
+
{children}
|
|
13
|
+
</th>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -1,29 +1,34 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './CardsTable.scss'
|
|
2
2
|
import React, { useState } from 'react'
|
|
3
3
|
import { sortBy } from '../utils/sort'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { CardsTableHeader } from './CardsTableHeader'
|
|
5
|
+
import { CardsTableCell } from './CardsTableCell'
|
|
6
|
+
import { CardsTableRow } from './CardsTableRow'
|
|
7
|
+
import { Icon, IconType } from './Icon'
|
|
8
|
+
import { CardsTable } from './CardsTable'
|
|
9
|
+
import { CardsTableHead } from './CardsTableHead'
|
|
10
|
+
import { CardsTableBody } from './CardsTableBody'
|
|
7
11
|
|
|
8
12
|
export type Variant = 'primary'
|
|
9
13
|
|
|
14
|
+
export enum Order {
|
|
15
|
+
Ascending = 'ascending',
|
|
16
|
+
Descending = 'descending',
|
|
17
|
+
None = 'none',
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
export type Header = {
|
|
11
21
|
label: string
|
|
12
22
|
icon?: IconType
|
|
13
23
|
columnId: string
|
|
14
24
|
isSortable?: boolean
|
|
15
25
|
}
|
|
26
|
+
|
|
16
27
|
export type Column = {
|
|
17
28
|
[key: string]: string
|
|
18
29
|
}
|
|
19
30
|
export type Row = { id: string; isDisabled?: boolean; columns: Column }
|
|
20
31
|
|
|
21
|
-
export enum Order {
|
|
22
|
-
Ascending = 'ascending',
|
|
23
|
-
Descending = 'descending',
|
|
24
|
-
None = 'none',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
32
|
export type SortState = { columnId: string; order: Order }
|
|
28
33
|
|
|
29
34
|
export interface CardsTableListProps
|
|
@@ -45,8 +50,6 @@ export function CardsTableList({
|
|
|
45
50
|
: null
|
|
46
51
|
})
|
|
47
52
|
|
|
48
|
-
const cssClasses = ['cards-table-list', variant].join(' ')
|
|
49
|
-
|
|
50
53
|
function checkColumnOrder(columnId: string) {
|
|
51
54
|
if (sortState?.columnId === columnId) {
|
|
52
55
|
return sortState.order
|
|
@@ -74,25 +77,48 @@ export function CardsTableList({
|
|
|
74
77
|
order: sortState?.order,
|
|
75
78
|
})
|
|
76
79
|
: rows
|
|
80
|
+
|
|
77
81
|
return (
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
<
|
|
82
|
+
<CardsTable summary={summary} variant={variant}>
|
|
83
|
+
<CardsTableHead>
|
|
84
|
+
<CardsTableRow>
|
|
81
85
|
{headers.map((header) => (
|
|
82
|
-
<
|
|
86
|
+
<CardsTableHeader
|
|
83
87
|
key={header.columnId}
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
aria-sort={checkColumnOrder(header.columnId)}
|
|
89
|
+
className={header.isSortable ? 'sortable' : ''}
|
|
86
90
|
onClick={() => applySort(header.columnId)}
|
|
87
|
-
|
|
91
|
+
>
|
|
92
|
+
<div className="container">
|
|
93
|
+
<div>
|
|
94
|
+
<span>{header.label}</span>
|
|
95
|
+
{header.icon && <Icon name={header.icon} />}
|
|
96
|
+
</div>
|
|
97
|
+
{header.isSortable && (
|
|
98
|
+
<Icon
|
|
99
|
+
name="Sorter"
|
|
100
|
+
className={checkColumnOrder(header.columnId)}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</CardsTableHeader>
|
|
88
105
|
))}
|
|
89
|
-
</
|
|
90
|
-
</
|
|
91
|
-
<
|
|
92
|
-
{sortedRows.map((row: Row) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
</CardsTableRow>
|
|
107
|
+
</CardsTableHead>
|
|
108
|
+
<CardsTableBody>
|
|
109
|
+
{sortedRows.map((row: Row) => {
|
|
110
|
+
const cells = Object.entries(row.columns)
|
|
111
|
+
return (
|
|
112
|
+
<CardsTableRow key={row.id} isDisabled={row.isDisabled}>
|
|
113
|
+
{cells.map(([columnId, value]) => (
|
|
114
|
+
<CardsTableCell key={`${row.id}${columnId}`}>
|
|
115
|
+
{value}
|
|
116
|
+
</CardsTableCell>
|
|
117
|
+
))}
|
|
118
|
+
</CardsTableRow>
|
|
119
|
+
)
|
|
120
|
+
})}
|
|
121
|
+
</CardsTableBody>
|
|
122
|
+
</CardsTable>
|
|
97
123
|
)
|
|
98
124
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import './CardsTable.scss'
|
|
3
|
+
|
|
4
|
+
export interface CardsTableRowProps
|
|
5
|
+
extends React.ComponentPropsWithoutRef<'tr'> {
|
|
6
|
+
isDisabled?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function CardsTableRow({
|
|
10
|
+
isDisabled = false,
|
|
11
|
+
children,
|
|
12
|
+
...props
|
|
13
|
+
}: CardsTableRowProps): React.JSX.Element {
|
|
14
|
+
const disabledClass = isDisabled ? 'disabled' : ''
|
|
15
|
+
return (
|
|
16
|
+
<tr role="row" className={`row ${disabledClass}`} {...props}>
|
|
17
|
+
{children}
|
|
18
|
+
</tr>
|
|
19
|
+
)
|
|
20
|
+
}
|
package/src/atoms/EmptyState.tsx
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
|
-
import { Button } from './Button'
|
|
1
|
+
import { Button, ButtonProps } from './Button'
|
|
2
2
|
import './EmptyState.scss'
|
|
3
3
|
import { Icon, IconType } from './Icon'
|
|
4
4
|
|
|
5
5
|
export type Variant = 'primary'
|
|
6
6
|
|
|
7
|
-
interface callToAction {
|
|
8
|
-
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): any
|
|
9
|
-
}
|
|
10
|
-
|
|
11
7
|
export interface EmptyStateProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
12
8
|
icon?: IconType
|
|
13
9
|
text?: string
|
|
14
10
|
variant?: Variant
|
|
15
|
-
|
|
16
|
-
action?: callToAction
|
|
11
|
+
button?: ButtonProps
|
|
17
12
|
}
|
|
18
13
|
|
|
19
14
|
export function EmptyState({
|
|
20
15
|
icon = 'EmptyState',
|
|
21
16
|
text = 'No data',
|
|
22
17
|
variant = 'primary',
|
|
23
|
-
|
|
24
|
-
action,
|
|
18
|
+
button,
|
|
25
19
|
}: EmptyStateProps): React.JSX.Element {
|
|
26
20
|
const cssClasses = ['empty-state', variant].join(' ')
|
|
27
21
|
|
|
@@ -29,7 +23,7 @@ export function EmptyState({
|
|
|
29
23
|
<div className={cssClasses}>
|
|
30
24
|
<Icon name={icon} />
|
|
31
25
|
<p>{text}</p>
|
|
32
|
-
{
|
|
26
|
+
{button?.label && <Button {...button} />}
|
|
33
27
|
</div>
|
|
34
28
|
)
|
|
35
29
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
@use '../settings/color_alias';
|
|
2
|
+
@use '../settings/typography';
|
|
3
|
+
@use '../settings/config';
|
|
4
|
+
|
|
5
|
+
.icon-button {
|
|
6
|
+
border: none;
|
|
7
|
+
background: none;
|
|
8
|
+
|
|
9
|
+
> .icon {
|
|
10
|
+
width: config.$icon-size-5x;
|
|
11
|
+
height: config.$icon-size-5x;
|
|
12
|
+
> svg {
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 100%;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&.primary {
|
|
19
|
+
> .icon {
|
|
20
|
+
> svg {
|
|
21
|
+
fill: color_alias.$primary-color-600;
|
|
22
|
+
path {
|
|
23
|
+
fill: color_alias.$primary-color-600;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&:hover {
|
|
29
|
+
> .icon {
|
|
30
|
+
> svg {
|
|
31
|
+
fill: color_alias.$primary-color-1000;
|
|
32
|
+
path {
|
|
33
|
+
fill: color_alias.$primary-color-1000;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&:disabled {
|
|
40
|
+
> .icon {
|
|
41
|
+
> svg {
|
|
42
|
+
fill: color_alias.$neutral-color-400;
|
|
43
|
+
path {
|
|
44
|
+
fill: color_alias.$neutral-color-400;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import NextLink from 'next/link'
|
|
2
|
+
import './IconButton.scss'
|
|
3
|
+
import { Icon, IconType } from './Icon'
|
|
4
|
+
|
|
5
|
+
export type Variant = 'primary'
|
|
6
|
+
|
|
7
|
+
export interface BaseIconButtonProps {
|
|
8
|
+
icon: IconType
|
|
9
|
+
variant?: Variant
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
accessibilityLabel: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type HtmlButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
15
|
+
|
|
16
|
+
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
17
|
+
|
|
18
|
+
export type IconButtonProps =
|
|
19
|
+
| (HtmlButtonProps & BaseIconButtonProps)
|
|
20
|
+
| (AnchorProps & BaseIconButtonProps)
|
|
21
|
+
|
|
22
|
+
const hasHref = (props: HtmlButtonProps | AnchorProps): props is AnchorProps =>
|
|
23
|
+
'href' in props
|
|
24
|
+
|
|
25
|
+
export function IconButton({
|
|
26
|
+
accessibilityLabel,
|
|
27
|
+
icon,
|
|
28
|
+
disabled,
|
|
29
|
+
variant = 'primary',
|
|
30
|
+
...props
|
|
31
|
+
}: IconButtonProps) {
|
|
32
|
+
const cssClasses = ['icon-button', variant].join(' ')
|
|
33
|
+
|
|
34
|
+
if (hasHref(props)) {
|
|
35
|
+
return (
|
|
36
|
+
<NextLink
|
|
37
|
+
href={props.href || ''}
|
|
38
|
+
className={cssClasses}
|
|
39
|
+
aria-label={accessibilityLabel}
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
<Icon name={icon} />
|
|
43
|
+
</NextLink>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
className={cssClasses}
|
|
50
|
+
disabled={disabled}
|
|
51
|
+
aria-label={accessibilityLabel}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
<Icon name={icon} />
|
|
55
|
+
</button>
|
|
56
|
+
)
|
|
57
|
+
}
|
package/src/atoms/Input.tsx
CHANGED
|
@@ -6,16 +6,18 @@ export type InputVariant = 'primary'
|
|
|
6
6
|
|
|
7
7
|
export interface InputProps extends React.ComponentPropsWithoutRef<'input'> {
|
|
8
8
|
label: string
|
|
9
|
+
accessibilityLabel?: string
|
|
9
10
|
hideLabel?: boolean
|
|
10
11
|
icon?: IconType
|
|
11
12
|
helpText?: string
|
|
12
13
|
variant?: InputVariant
|
|
13
14
|
id: string
|
|
14
|
-
|
|
15
|
+
errors?: string[]
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function Input({
|
|
18
19
|
label,
|
|
20
|
+
accessibilityLabel,
|
|
19
21
|
hideLabel = false,
|
|
20
22
|
icon,
|
|
21
23
|
helpText,
|
|
@@ -24,12 +26,12 @@ export function Input({
|
|
|
24
26
|
type = 'text',
|
|
25
27
|
name,
|
|
26
28
|
id,
|
|
27
|
-
|
|
29
|
+
errors,
|
|
28
30
|
...props
|
|
29
31
|
}: InputProps): React.JSX.Element {
|
|
30
32
|
const [showPassword, setShowPassword] = useState(false)
|
|
31
33
|
const iconClass = icon ? 'with-icon' : ''
|
|
32
|
-
const invalidClass =
|
|
34
|
+
const invalidClass = errors ? 'invalid' : ''
|
|
33
35
|
const cssClasses = ['input', iconClass, invalidClass].join(' ')
|
|
34
36
|
|
|
35
37
|
function handlePasswordIcon() {
|
|
@@ -61,7 +63,7 @@ export function Input({
|
|
|
61
63
|
disabled={disabled}
|
|
62
64
|
type={handleInputType()}
|
|
63
65
|
name={name}
|
|
64
|
-
aria-label={label}
|
|
66
|
+
aria-label={accessibilityLabel || label}
|
|
65
67
|
{...props}
|
|
66
68
|
/>
|
|
67
69
|
{type === 'password' && (
|
|
@@ -72,7 +74,17 @@ export function Input({
|
|
|
72
74
|
/>
|
|
73
75
|
)}
|
|
74
76
|
</div>
|
|
75
|
-
{helpText &&
|
|
77
|
+
{helpText && !errors && (
|
|
78
|
+
<span className="input-help-text">{helpText}</span>
|
|
79
|
+
)}
|
|
80
|
+
{errors &&
|
|
81
|
+
errors?.map((error, index) => {
|
|
82
|
+
return (
|
|
83
|
+
<span key={`error-${index}`} className="input-help-text">
|
|
84
|
+
{error}
|
|
85
|
+
</span>
|
|
86
|
+
)
|
|
87
|
+
})}
|
|
76
88
|
</div>
|
|
77
89
|
)
|
|
78
90
|
}
|