agroptima-design-system 0.8.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroptima-design-system",
3
- "version": "0.8.0",
3
+ "version": "0.9.2",
4
4
  "scripts": {
5
5
  "dev": "npm run storybook",
6
6
  "storybook": "storybook dev -p 6006 --ci",
@@ -0,0 +1,123 @@
1
+ @use '../settings/color_alias';
2
+ @use '../settings/typography/content' as typography;
3
+ @use '../settings/config';
4
+ @use '../settings/depth';
5
+
6
+ .alert {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ width: 100%;
10
+ margin: auto;
11
+ padding: config.$space-2x config.$space-3x;
12
+ align-items: center;
13
+ gap: config.$space-2x;
14
+ border-radius: config.$corner-radius-xxs;
15
+
16
+ &.fit-content {
17
+ width: fit-content;
18
+ }
19
+
20
+ .information-container {
21
+ display: flex;
22
+ justify-content: flex-start;
23
+ align-items: center;
24
+ gap: config.$space-2x;
25
+ }
26
+
27
+ .text {
28
+ @include typography.body-regular-primary;
29
+ }
30
+
31
+ .icon {
32
+ width: config.$icon-size-5x;
33
+ height: config.$icon-size-5x;
34
+ > svg {
35
+ width: 100%;
36
+ height: 100%;
37
+ }
38
+ }
39
+
40
+ .icon-button {
41
+ .icon {
42
+ width: config.$icon-size-2x;
43
+ height: config.$icon-size-2x;
44
+
45
+ > svg {
46
+ fill: color_alias.$neutral-color-400;
47
+ path {
48
+ fill: color_alias.$neutral-color-400;
49
+ }
50
+ }
51
+ }
52
+ &:hover {
53
+ .icon {
54
+ > svg {
55
+ fill: color_alias.$neutral-color-400;
56
+ path {
57
+ fill: color_alias.$neutral-color-400;
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ &.info {
65
+ border: 1px solid color_alias.$info-color-600;
66
+ background: color_alias.$info-color-50;
67
+ .information-container {
68
+ .icon {
69
+ > svg {
70
+ fill: color_alias.$info-color-1000;
71
+ path {
72
+ fill: color_alias.$info-color-1000;
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ &.success {
80
+ border: 1px solid color_alias.$success-color-300;
81
+ background: color_alias.$success-color-50;
82
+ .information-container {
83
+ .icon {
84
+ > svg {
85
+ fill: color_alias.$success-color-1000;
86
+ path {
87
+ fill: color_alias.$success-color-1000;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ &.warning {
95
+ border: 1px solid color_alias.$warning-color-300;
96
+ background: color_alias.$warning-color-50;
97
+ .information-container {
98
+ .icon {
99
+ > svg {
100
+ fill: color_alias.$warning-color-1000;
101
+ path {
102
+ fill: color_alias.$warning-color-1000;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ &.error {
110
+ border: 1px solid color_alias.$error-color-600;
111
+ background: color_alias.$error-color-50;
112
+ .information-container {
113
+ .icon {
114
+ > svg {
115
+ fill: color_alias.$error-color-1000;
116
+ path {
117
+ fill: color_alias.$error-color-1000;
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,50 @@
1
+ import { IconButton, IconButtonProps } from './IconButton'
2
+ import { Icon } from './Icon'
3
+ import './Alert.scss'
4
+
5
+ export type Variant = 'info' | 'success' | 'warning' | 'error'
6
+
7
+ export interface AlertProps extends React.ComponentPropsWithoutRef<'div'> {
8
+ id: string
9
+ variant?: Variant
10
+ text: string
11
+ button?: IconButtonProps
12
+ fitContent?: boolean
13
+ }
14
+
15
+ export enum IconVariant {
16
+ info = 'Info',
17
+ success = 'Check',
18
+ warning = 'Warning',
19
+ error = 'Error',
20
+ }
21
+
22
+ export function Alert({
23
+ id,
24
+ variant = 'success',
25
+ className = '',
26
+ fitContent = false,
27
+ text,
28
+ button,
29
+ ...props
30
+ }: AlertProps): React.JSX.Element {
31
+ const fitContentClass = fitContent ? 'fit-content' : ''
32
+ const cssClasses = ['alert', variant, className, fitContentClass].join(' ')
33
+
34
+ return (
35
+ <div
36
+ role="alert"
37
+ aria-labelledby={`${id}-text`}
38
+ className={cssClasses}
39
+ {...props}
40
+ >
41
+ <div className="information-container">
42
+ <Icon name={IconVariant[variant]} className={variant} />
43
+ <span id={`${id}-text`} className="text">
44
+ {text}
45
+ </span>
46
+ </div>
47
+ {button && <IconButton {...button} variant="primary" />}
48
+ </div>
49
+ )
50
+ }
@@ -4,10 +4,21 @@
4
4
  @use '../settings/depth';
5
5
 
6
6
  .modal-container {
7
- display: flex;
8
- justify-content: center;
7
+ position: fixed;
8
+ inset: 0;
9
+ z-index: depth.$z-modal;
10
+
11
+ .backdrop {
12
+ position: fixed;
13
+ height: 100%;
14
+ opacity: 0.4;
15
+ background: color_alias.$neutral-color-900;
16
+ inset: 0;
17
+ }
9
18
 
10
19
  .modal {
20
+ top: 6.25rem;
21
+ margin: auto;
11
22
  display: flex;
12
23
  position: relative;
13
24
  padding: config.$space-5x;
@@ -18,7 +29,6 @@
18
29
  border-radius: config.$corner-radius-xxs;
19
30
  background: color_alias.$neutral-white;
20
31
  max-width: 34.5rem;
21
- z-index: depth.$z-modal;
22
32
  box-shadow:
23
33
  0px 3px 6px -4px rgba(0, 0, 0, 0.12),
24
34
  0px 6px 16px 0px rgba(0, 0, 0, 0.08),
@@ -117,18 +127,5 @@
117
127
  }
118
128
  }
119
129
  }
120
-
121
- + .backdrop {
122
- position: fixed;
123
- top: 0;
124
- bottom: 0;
125
- left: 0;
126
- right: 0;
127
- width: 100%;
128
- height: 100%;
129
- opacity: 0.4;
130
- background: color_alias.$neutral-color-900;
131
- z-index: depth.$z-modal-backdrop;
132
- }
133
130
  }
134
131
  }
@@ -1,4 +1,3 @@
1
- import { useState } from 'react'
2
1
  import { Button, ButtonProps } from './Button'
3
2
  import { Icon } from './Icon'
4
3
  import './Modal.scss'
@@ -18,7 +17,6 @@ export interface ModalProps extends React.ComponentPropsWithoutRef<'div'> {
18
17
  variant?: Variant
19
18
  title: string
20
19
  buttons: ButtonProps[]
21
- showModal?: boolean
22
20
  }
23
21
 
24
22
  export function Modal({
@@ -26,55 +24,36 @@ export function Modal({
26
24
  variant = 'info',
27
25
  title,
28
26
  buttons,
29
- showModal = true,
30
27
  children,
31
28
  ...props
32
29
  }: ModalProps): React.JSX.Element {
33
- const [isModalShown, setIsModalShown] = useState(showModal)
34
-
35
30
  const cssClasses = ['modal', variant].join(' ')
36
31
 
37
- function withHideModal(onClick?: React.MouseEventHandler | undefined) {
38
- return function (event: React.MouseEvent<Element, MouseEvent>) {
39
- setIsModalShown(false)
40
-
41
- if (onClick !== undefined) onClick(event)
42
- }
43
- }
44
-
45
32
  return (
46
- <>
47
- {isModalShown && (
48
- <div className="modal-container">
49
- <div
50
- role="dialog"
51
- aria-labelledby={`${id}-title`}
52
- aria-describedby={`${id}-body`}
53
- className={cssClasses}
54
- {...props}
55
- >
56
- <div className="header">
57
- <Icon name={IconVariant[variant]} className={variant} />
58
- <h4 id={`${id}-title`} className="title">
59
- {title}
60
- </h4>
61
- </div>
62
- <div id={`${id}-body`} className="body">
63
- {children}
64
- </div>
65
- <div className="footer">
66
- {buttons.map(({ onClick, ...button }) => (
67
- <Button
68
- key={button.label}
69
- {...button}
70
- onClick={withHideModal(onClick)}
71
- />
72
- ))}
73
- </div>
74
- </div>
75
- <div className="backdrop"></div>
33
+ <div className="modal-container">
34
+ <div className="backdrop"></div>
35
+ <div
36
+ role="dialog"
37
+ aria-labelledby={`${id}-title`}
38
+ aria-describedby={`${id}-body`}
39
+ className={cssClasses}
40
+ {...props}
41
+ >
42
+ <div className="header">
43
+ <Icon name={IconVariant[variant]} className={variant} />
44
+ <h4 id={`${id}-title`} className="title">
45
+ {title}
46
+ </h4>
47
+ </div>
48
+ <div id={`${id}-body`} className="body">
49
+ {children}
50
+ </div>
51
+ <div className="footer">
52
+ {buttons.map(({ ...button }) => (
53
+ <Button key={button.label} {...button} />
54
+ ))}
76
55
  </div>
77
- )}
78
- </>
56
+ </div>
57
+ </div>
79
58
  )
80
59
  }
@@ -6,5 +6,5 @@ $above: 1; // use this for all values above the base
6
6
  $below: -1; // and this for all values below the base
7
7
 
8
8
  $z-dropdown-options: $base + $above;
9
- $z-modal-backdrop: $z-dropdown-options + $above;
10
- $z-modal: $z-modal-backdrop + $above;
9
+ $z-alert: $z-dropdown-options + $above;
10
+ $z-modal: $z-alert + $above;
@@ -0,0 +1,78 @@
1
+ import { Alert } from '../atoms/Alert'
2
+ import { StoryObj } from '@storybook/react'
3
+
4
+ const meta = {
5
+ title: 'Design System/Atoms/Alert',
6
+ component: Alert,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ id: {
10
+ description: 'Id for aria purposes',
11
+ },
12
+ text: {
13
+ description: 'Text to be shown on the component.',
14
+ },
15
+ variant: {
16
+ description: 'Variant used.',
17
+ },
18
+ button: {
19
+ description: 'Add an IconButton component to have the close button.',
20
+ },
21
+ },
22
+ }
23
+
24
+ const figmaPrimaryDesign = {
25
+ design: {
26
+ type: 'figma',
27
+ url: 'https://www.figma.com/file/DN2ova21vWqCRvPspBXgI1/Design-System?type=design&node-id=570-118&mode=dev',
28
+ },
29
+ }
30
+
31
+ export default meta
32
+ type Story = StoryObj<typeof meta>
33
+
34
+ export const Info: Story = {
35
+ args: {
36
+ id: 'info-alert',
37
+ variant: 'info',
38
+ text: 'Thank you! But our princess is in another castle!',
39
+ },
40
+ parameters: figmaPrimaryDesign,
41
+ }
42
+
43
+ export const Success: Story = {
44
+ args: {
45
+ id: 'success-alert',
46
+ variant: 'success',
47
+ text: 'Thank you! But our princess is in another castle!',
48
+ },
49
+ parameters: figmaPrimaryDesign,
50
+ }
51
+
52
+ export const Warning: Story = {
53
+ args: {
54
+ id: 'warning-alert',
55
+ variant: 'warning',
56
+ text: 'Thank you! But our princess is in another castle!',
57
+ button: {
58
+ accessibilityLabel: 'Close alert',
59
+ onClick: () => alert('click'),
60
+ icon: 'Close',
61
+ },
62
+ },
63
+ parameters: figmaPrimaryDesign,
64
+ }
65
+
66
+ export const Error: Story = {
67
+ args: {
68
+ id: 'error-alert',
69
+ variant: 'error',
70
+ text: 'Thank you! But our princess is in another castle!',
71
+ button: {
72
+ accessibilityLabel: 'Close alert',
73
+ onClick: () => alert('click'),
74
+ icon: 'Close',
75
+ },
76
+ },
77
+ parameters: figmaPrimaryDesign,
78
+ }
@@ -3,6 +3,19 @@ import { Meta } from "@storybook/addon-docs";
3
3
  <Meta title="Changelog" />
4
4
  # Changelog
5
5
 
6
+ ## 0.9.2
7
+
8
+ - Alert component can be rendered with fit-content width or 100% depending on the container.
9
+
10
+ ## 0.9.1
11
+
12
+ - Modal and Alert components position fixed has been added.
13
+ - Modal show/hide logic has been removed.
14
+
15
+ ## 0.9.0
16
+
17
+ - Alert component is added to Storybook.
18
+
6
19
  ## 0.8.0
7
20
 
8
21
  - Badge component is added to Storybook.
@@ -26,9 +26,6 @@ const meta = {
26
26
  buttons: {
27
27
  description: 'Array of button to be shown on the footer',
28
28
  },
29
- showModal: {
30
- description: 'Boolean to control the component visibility state',
31
- },
32
29
  },
33
30
  parameters: figmaPrimaryDesign,
34
31
  }
@@ -36,84 +33,97 @@ const meta = {
36
33
  export default meta
37
34
 
38
35
  export const Info = {
39
- render: () =>
40
- <Modal
41
- id='info-dangerous-alone'
42
- variant='info'
43
- title= "It's dangerous to go alone!"
44
- buttons= {[{
45
- label: 'Done',
46
- onClick: () => alert('click'),
47
- }]}
36
+ render: () => (
37
+ <Modal
38
+ id="info-dangerous-alone"
39
+ variant="info"
40
+ title="It's dangerous to go alone!"
41
+ buttons={[
42
+ {
43
+ label: 'Done',
44
+ onClick: () => alert('click'),
45
+ },
46
+ ]}
48
47
  >
49
- Take this 🗡️
48
+ Take this 🗡️
50
49
  </Modal>
50
+ ),
51
51
  }
52
52
 
53
53
  export const Success = {
54
- render: () =>
55
- <Modal
56
- id='success-dangerous-alone'
57
- variant='success'
58
- title= "It's dangerous to go alone!"
59
- buttons= {[{
60
- label: 'Done',
61
- onClick: () => alert('click'),
62
- }]}
54
+ render: () => (
55
+ <Modal
56
+ id="success-dangerous-alone"
57
+ variant="success"
58
+ title="It's dangerous to go alone!"
59
+ buttons={[
60
+ {
61
+ label: 'Done',
62
+ onClick: () => alert('click'),
63
+ },
64
+ ]}
63
65
  >
64
- Take this 🗡️
66
+ Take this 🗡️
65
67
  </Modal>
68
+ ),
66
69
  }
67
70
 
68
71
  export const Warning = {
69
- render: () =>
70
- <Modal
71
- id='warning-dangerous-alone'
72
- variant='warning'
73
- title= "It's dangerous to go alone!"
74
- buttons= {[{
75
- label: 'Done',
76
- onClick: () => alert('click'),
77
- }]}
72
+ render: () => (
73
+ <Modal
74
+ id="warning-dangerous-alone"
75
+ variant="warning"
76
+ title="It's dangerous to go alone!"
77
+ buttons={[
78
+ {
79
+ label: 'Done',
80
+ onClick: () => alert('click'),
81
+ },
82
+ ]}
78
83
  >
79
- Take this 🗡️
84
+ Take this 🗡️
80
85
  </Modal>
86
+ ),
81
87
  }
82
88
 
83
89
  export const Error = {
84
- render: () =>
85
- <Modal
86
- id='error-dangerous-alone'
87
- variant='error'
88
- title= "It's dangerous to go alone!"
89
- buttons= {[{
90
- label: 'Done',
91
- onClick: () => alert('click'),
92
- }]}
90
+ render: () => (
91
+ <Modal
92
+ id="error-dangerous-alone"
93
+ variant="error"
94
+ title="It's dangerous to go alone!"
95
+ buttons={[
96
+ {
97
+ label: 'Done',
98
+ onClick: () => alert('click'),
99
+ },
100
+ ]}
93
101
  >
94
- Take this 🗡️
102
+ Take this 🗡️
95
103
  </Modal>
104
+ ),
96
105
  }
97
106
 
98
107
  export const DeleteOrDiscard = {
99
- render: () =>
100
- <Modal
101
- id='discard-dangerous-alone'
102
- variant='discard'
103
- title= "It's dangerous to go alone!"
104
- buttons= {[{
105
- label: 'Cancel',
106
- variant: 'neutral',
107
- onClick: () => alert('click'),
108
- },
109
- {
110
- label: 'Delete',
111
- variant: 'error',
112
- onClick: () => alert('click'),
113
- }]}
108
+ render: () => (
109
+ <Modal
110
+ id="discard-dangerous-alone"
111
+ variant="discard"
112
+ title="It's dangerous to go alone!"
113
+ buttons={[
114
+ {
115
+ label: 'Cancel',
116
+ variant: 'neutral',
117
+ onClick: () => alert('click'),
118
+ },
119
+ {
120
+ label: 'Delete',
121
+ variant: 'error',
122
+ onClick: () => alert('click'),
123
+ },
124
+ ]}
114
125
  >
115
- Take this 🗡️
126
+ Take this 🗡️
116
127
  </Modal>
128
+ ),
117
129
  }
118
-
119
-
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { screen, render } from '@testing-library/react'
3
+ import { Alert, Variant } from '@/atoms/Alert'
4
+
5
+ describe('Alert', () => {
6
+ const variants = ['info', 'success', 'warning', 'error']
7
+ it.each(variants)(
8
+ 'renders the %s variant with text, expected styles and button',
9
+ (variant) => {
10
+ const text = `${variant} text`
11
+ const { getByRole, getByText } = render(
12
+ <Alert
13
+ id={`${variant}-modal`}
14
+ text={text}
15
+ variant={variant as Variant}
16
+ button={{
17
+ accessibilityLabel: 'Close alert',
18
+ onClick: () => alert('click'),
19
+ icon: 'Close',
20
+ }}
21
+ />,
22
+ )
23
+ expect(getByRole('alert')).toHaveClass(`alert ${variant}`)
24
+ expect(screen.getAllByRole('img')[0]).toHaveClass(`icon ${variant}`)
25
+ expect(getByText(text)).toBeInTheDocument()
26
+ expect(getByRole('button')).toBeInTheDocument()
27
+ },
28
+ )
29
+
30
+ it.each(variants)(
31
+ 'renders the %s variant with text and expected styles',
32
+ (variant) => {
33
+ const text = `${variant} text`
34
+ const { getByRole, getByText, queryByRole } = render(
35
+ <Alert
36
+ id={`${variant}-modal`}
37
+ text={text}
38
+ variant={variant as Variant}
39
+ />,
40
+ )
41
+ expect(getByRole('alert')).toHaveClass(`alert ${variant}`)
42
+ expect(screen.getAllByRole('img')[0]).toHaveClass(`icon ${variant}`)
43
+ expect(getByText(text)).toBeInTheDocument()
44
+ expect(queryByRole('button')).not.toBeInTheDocument()
45
+ },
46
+ )
47
+ })
@@ -60,27 +60,4 @@ describe('Modal', () => {
60
60
  expect(screen.getAllByRole('button')[1]).toHaveTextContent('Delete')
61
61
  expect(screen.getAllByRole('button')[1]).toHaveClass('error')
62
62
  })
63
-
64
- it('does not render the modal when showModal is false', () => {
65
- const title = 'Info modal'
66
- const content = 'Info modal content'
67
- const { queryByText, queryByRole } = render(
68
- <Modal
69
- id="shy-modal"
70
- title={title}
71
- showModal={false}
72
- buttons={[
73
- {
74
- label: 'Done',
75
- },
76
- ]}
77
- >
78
- {content}
79
- </Modal>,
80
- )
81
- expect(queryByRole('img')).not.toBeInTheDocument()
82
- expect(queryByText(title)).not.toBeInTheDocument()
83
- expect(queryByText(content)).not.toBeInTheDocument()
84
- expect(queryByRole('button')).not.toBeInTheDocument()
85
- })
86
63
  })