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.
@@ -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.2",
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
- "publish-chromatic": "npx chromatic --exit-zero-on-changes"
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",
@@ -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: 1px solid color_alias.$neutral-color-400;
149
+ border: none;
148
150
  color: color_alias.$neutral-color-400;
149
151
  svg,
150
152
  svg path {
@@ -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): React.JSX.Element {
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 className={cssClasses} disabled={loading || disabled} {...props}>
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 './CardsTableList.scss'
1
+ import './CardsTable.scss'
2
2
  import React, { useState } from 'react'
3
3
  import { sortBy } from '../utils/sort'
4
- import { IconType } from './Icon'
5
- import { CardsTableListHeader } from './CardsTableListHeader'
6
- import { CardsTableListRow } from './CardsTableListRow'
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
- <table summary={summary} role="table" className={cssClasses}>
79
- <thead role="rowgroup">
80
- <tr role="row">
82
+ <CardsTable summary={summary} variant={variant}>
83
+ <CardsTableHead>
84
+ <CardsTableRow>
81
85
  {headers.map((header) => (
82
- <CardsTableListHeader
86
+ <CardsTableHeader
83
87
  key={header.columnId}
84
- header={header}
85
- order={checkColumnOrder(header.columnId)}
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
- </tr>
90
- </thead>
91
- <tbody role="rowgroup">
92
- {sortedRows.map((row: Row) => (
93
- <CardsTableListRow key={row.id} {...row} />
94
- ))}
95
- </tbody>
96
- </table>
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
+ }
@@ -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
- buttonLabel?: string
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
- buttonLabel,
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
- {buttonLabel && action && <Button label={buttonLabel} onClick={action} />}
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
+ }
@@ -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
- invalid?: boolean
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
- invalid,
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 = invalid ? 'invalid' : ''
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 && <span className="input-help-text">{helpText}</span>}
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
  }