agroptima-design-system 0.28.4-beta.3 → 0.28.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/atoms/Alert/Alert.scss +0 -41
- package/src/atoms/Alert/Alert.tsx +1 -1
- package/src/atoms/{DatePicker/DateRangePicker.scss → DatePicker.scss} +5 -5
- package/src/atoms/DatePicker.tsx +66 -0
- package/src/atoms/Form/{Form.scss → Actions.scss} +0 -27
- package/src/atoms/Form/Actions.tsx +2 -2
- package/src/atoms/Form/Form.tsx +5 -16
- package/src/atoms/Form/FormContainer.scss +35 -0
- package/src/atoms/Form/FormContainer.tsx +25 -0
- package/src/atoms/Form/index.ts +4 -3
- package/src/atoms/Icon.scss +22 -16
- package/src/atoms/Icon.tsx +4 -1
- package/src/atoms/Modal/Modal.scss +139 -0
- package/src/atoms/Modal/Modal.tsx +74 -0
- package/src/atoms/Modal/ModalBody.tsx +12 -0
- package/src/atoms/Modal/ModalCloseButton.tsx +23 -0
- package/src/atoms/Modal/ModalDialog.tsx +57 -0
- package/src/atoms/Modal/ModalFooter.tsx +17 -0
- package/src/atoms/Modal/ModalHeader.tsx +17 -0
- package/src/atoms/Modal/ModalTitle.tsx +12 -0
- package/src/atoms/Modal/index.tsx +24 -0
- package/src/settings/_mixins.scss +14 -0
- package/src/stories/Changelog.mdx +6 -1
- package/src/stories/Collapsible.stories.js +55 -66
- package/src/stories/DatePicker.stories.ts +6 -14
- package/src/stories/Drawer.stories.js +3 -2
- package/src/stories/Modal.stories.js +169 -73
- package/src/utils/LoremIpsum.tsx +15 -0
- package/tests/{DateRangePicker.spec.tsx → DatePicker.spec.tsx} +4 -3
- package/tests/Modal.spec.tsx +4 -3
- package/src/atoms/DatePicker/DateRangePicker.tsx +0 -102
- package/src/atoms/DatePicker/translations.ts +0 -16
- package/src/atoms/Modal.scss +0 -118
- package/src/atoms/Modal.tsx +0 -73
- package/src/utils/dateHelpers.ts +0 -19
package/package.json
CHANGED
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
.icon {
|
|
47
47
|
width: config.$icon-size-3x;
|
|
48
48
|
height: config.$icon-size-3x;
|
|
49
|
-
|
|
50
49
|
> svg {
|
|
51
50
|
fill: color_alias.$neutral-color-400;
|
|
52
51
|
path {
|
|
@@ -73,61 +72,21 @@
|
|
|
73
72
|
&.info {
|
|
74
73
|
border: 1px solid color_alias.$info-color-600;
|
|
75
74
|
background: color_alias.$info-color-50;
|
|
76
|
-
.information-container {
|
|
77
|
-
.icon {
|
|
78
|
-
> svg {
|
|
79
|
-
fill: color_alias.$info-color-1000;
|
|
80
|
-
path {
|
|
81
|
-
fill: color_alias.$info-color-1000;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
75
|
}
|
|
87
76
|
|
|
88
77
|
&.success {
|
|
89
78
|
border: 1px solid color_alias.$success-color-300;
|
|
90
79
|
background: color_alias.$success-color-50;
|
|
91
|
-
.information-container {
|
|
92
|
-
.icon {
|
|
93
|
-
> svg {
|
|
94
|
-
fill: color_alias.$success-color-1000;
|
|
95
|
-
path {
|
|
96
|
-
fill: color_alias.$success-color-1000;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
80
|
}
|
|
102
81
|
|
|
103
82
|
&.warning {
|
|
104
83
|
border: 1px solid color_alias.$warning-color-300;
|
|
105
84
|
background: color_alias.$warning-color-50;
|
|
106
|
-
.information-container {
|
|
107
|
-
.icon {
|
|
108
|
-
> svg {
|
|
109
|
-
fill: color_alias.$warning-color-1000;
|
|
110
|
-
path {
|
|
111
|
-
fill: color_alias.$warning-color-1000;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
85
|
}
|
|
117
86
|
|
|
118
87
|
&.error {
|
|
119
88
|
border: 1px solid color_alias.$error-color-1000;
|
|
120
89
|
background: color_alias.$error-color-50;
|
|
121
|
-
.information-container {
|
|
122
|
-
.icon {
|
|
123
|
-
> svg {
|
|
124
|
-
fill: color_alias.$error-color-1000;
|
|
125
|
-
path {
|
|
126
|
-
fill: color_alias.$error-color-1000;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
90
|
}
|
|
132
91
|
|
|
133
92
|
@keyframes fadeOut {
|
|
@@ -46,7 +46,7 @@ export function Alert({
|
|
|
46
46
|
{...props}
|
|
47
47
|
>
|
|
48
48
|
<div className="information-container">
|
|
49
|
-
<Icon name={IconVariant[variant]}
|
|
49
|
+
<Icon name={IconVariant[variant]} variant={variant} />
|
|
50
50
|
<span id={`${id}-text`} className="text">
|
|
51
51
|
{text}
|
|
52
52
|
</span>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
@use '
|
|
2
|
-
@use '
|
|
3
|
-
@use '
|
|
4
|
-
@use '
|
|
5
|
-
@use '
|
|
1
|
+
@use '../settings/color_alias';
|
|
2
|
+
@use '../settings/typography/content' as typography;
|
|
3
|
+
@use '../settings/config';
|
|
4
|
+
@use '../settings/depth';
|
|
5
|
+
@use '../settings/breakpoints';
|
|
6
6
|
|
|
7
7
|
// Interpolation applied: https://sass-lang.com/documentation/breaking-changes/css-vars/
|
|
8
8
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import 'react-day-picker/style.css'
|
|
2
|
+
import './DatePicker.scss'
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { type DateRange, DayPicker, type Locale } from 'react-day-picker'
|
|
5
|
+
import { enGB, es } from 'react-day-picker/locale'
|
|
6
|
+
import { classNames } from '../utils/classNames'
|
|
7
|
+
|
|
8
|
+
export type Variant = 'primary'
|
|
9
|
+
|
|
10
|
+
type DivPropsWithoutOnSelect = Omit<
|
|
11
|
+
React.ComponentPropsWithoutRef<'div'>,
|
|
12
|
+
'onSelect'
|
|
13
|
+
>
|
|
14
|
+
|
|
15
|
+
interface AvailableLocale {
|
|
16
|
+
[index: string]: Locale
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const availableLocales: AvailableLocale = {
|
|
20
|
+
es: es,
|
|
21
|
+
en: enGB,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DatePickerProps extends DivPropsWithoutOnSelect {
|
|
25
|
+
variant?: Variant
|
|
26
|
+
onSelect: (dateRange: DateRange | undefined) => void
|
|
27
|
+
footer: string
|
|
28
|
+
selected?: DateRange
|
|
29
|
+
lng: keyof typeof availableLocales
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function DatePicker({
|
|
33
|
+
className,
|
|
34
|
+
variant = 'primary',
|
|
35
|
+
onSelect = () => {},
|
|
36
|
+
footer = 'Pick a day',
|
|
37
|
+
selected: preselected,
|
|
38
|
+
lng,
|
|
39
|
+
}: DatePickerProps): React.JSX.Element {
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
setSelected(preselected)
|
|
42
|
+
}, [preselected])
|
|
43
|
+
|
|
44
|
+
const cssClasses = classNames('date-picker', variant, className)
|
|
45
|
+
|
|
46
|
+
const [selected, setSelected] = useState<DateRange | undefined>(preselected)
|
|
47
|
+
|
|
48
|
+
function selectDate(dateRange: DateRange | undefined) {
|
|
49
|
+
setSelected(dateRange)
|
|
50
|
+
onSelect(dateRange)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={cssClasses}>
|
|
55
|
+
<DayPicker
|
|
56
|
+
locale={availableLocales[lng]}
|
|
57
|
+
mode="range"
|
|
58
|
+
min={1}
|
|
59
|
+
selected={selected}
|
|
60
|
+
onSelect={(dateRange) => selectDate(dateRange)}
|
|
61
|
+
footer={footer}
|
|
62
|
+
defaultMonth={selected?.from}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -4,26 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
$gap: config.$space-4x;
|
|
6
6
|
|
|
7
|
-
.form {
|
|
8
|
-
display: flex;
|
|
9
|
-
flex-direction: column;
|
|
10
|
-
margin: 0 auto;
|
|
11
|
-
gap: $gap;
|
|
12
|
-
padding: 0;
|
|
13
|
-
|
|
14
|
-
width: breakpoints.$medium;
|
|
15
|
-
max-width: breakpoints.$medium;
|
|
16
|
-
|
|
17
|
-
&.full-width {
|
|
18
|
-
width: 100%;
|
|
19
|
-
max-width: 100%;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
&:has(.footer-actions) {
|
|
23
|
-
padding-bottom: 6.25rem;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
7
|
.footer-actions {
|
|
28
8
|
display: flex;
|
|
29
9
|
justify-content: flex-end;
|
|
@@ -39,13 +19,6 @@ $gap: config.$space-4x;
|
|
|
39
19
|
}
|
|
40
20
|
|
|
41
21
|
@media only screen and (max-width: breakpoints.$large) {
|
|
42
|
-
.form {
|
|
43
|
-
max-width: 100%;
|
|
44
|
-
width: 100%;
|
|
45
|
-
&:has(.footer-actions) {
|
|
46
|
-
padding-bottom: 9rem;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
22
|
.footer-actions {
|
|
50
23
|
background-color: color_alias.$neutral-white;
|
|
51
24
|
border-top: 1px solid color_alias.$neutral-color-200;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './Actions.scss'
|
|
2
2
|
import { classNames } from '../../utils/classNames'
|
|
3
3
|
|
|
4
4
|
export interface ActionsProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
5
5
|
children: React.ReactNode
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export
|
|
8
|
+
export function Actions({ className, children }: ActionsProps) {
|
|
9
9
|
return (
|
|
10
10
|
<div className={classNames('footer-actions', className)}>{children}</div>
|
|
11
11
|
)
|
package/src/atoms/Form/Form.tsx
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
|
-
import './
|
|
2
|
-
import { classNames } from '../../utils/classNames'
|
|
1
|
+
import { FormContainer } from './FormContainer'
|
|
3
2
|
|
|
4
3
|
export interface FormProps extends React.ComponentPropsWithoutRef<'form'> {
|
|
5
|
-
|
|
4
|
+
fluid?: boolean
|
|
6
5
|
children: React.ReactNode
|
|
7
6
|
}
|
|
8
7
|
|
|
9
|
-
export
|
|
10
|
-
className,
|
|
11
|
-
fullWidth,
|
|
12
|
-
children,
|
|
13
|
-
...props
|
|
14
|
-
}: FormProps) {
|
|
8
|
+
export function Form({ fluid, children, ...props }: FormProps) {
|
|
15
9
|
return (
|
|
16
|
-
<form
|
|
17
|
-
|
|
18
|
-
'full-width': fullWidth,
|
|
19
|
-
})}
|
|
20
|
-
{...props}
|
|
21
|
-
>
|
|
22
|
-
{children}
|
|
10
|
+
<form {...props}>
|
|
11
|
+
<FormContainer fluid={fluid}>{children}</FormContainer>
|
|
23
12
|
</form>
|
|
24
13
|
)
|
|
25
14
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@use '../../settings/config';
|
|
2
|
+
@use '../../settings/breakpoints';
|
|
3
|
+
@use '../../settings/color_alias';
|
|
4
|
+
|
|
5
|
+
$gap: config.$space-4x;
|
|
6
|
+
|
|
7
|
+
.form-container {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
margin: 0 auto;
|
|
11
|
+
gap: $gap;
|
|
12
|
+
padding: 0;
|
|
13
|
+
|
|
14
|
+
width: breakpoints.$medium;
|
|
15
|
+
max-width: breakpoints.$medium;
|
|
16
|
+
|
|
17
|
+
&.fluid {
|
|
18
|
+
width: 100%;
|
|
19
|
+
max-width: 100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&:has(.footer-actions) {
|
|
23
|
+
padding-bottom: 6.25rem;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media only screen and (max-width: breakpoints.$large) {
|
|
28
|
+
.form-container {
|
|
29
|
+
max-width: 100%;
|
|
30
|
+
width: 100%;
|
|
31
|
+
&:has(.footer-actions) {
|
|
32
|
+
padding-bottom: 9rem;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import './FormContainer.scss'
|
|
2
|
+
import { classNames } from '../../utils/classNames'
|
|
3
|
+
|
|
4
|
+
export interface FormContainerProps
|
|
5
|
+
extends React.ComponentPropsWithoutRef<'div'> {
|
|
6
|
+
fluid?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function FormContainer({
|
|
10
|
+
fluid,
|
|
11
|
+
className,
|
|
12
|
+
children,
|
|
13
|
+
...props
|
|
14
|
+
}: FormContainerProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
className={classNames(className, 'form-container', {
|
|
18
|
+
fluid,
|
|
19
|
+
})}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
package/src/atoms/Form/index.ts
CHANGED
package/src/atoms/Icon.scss
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@use '../settings/color_alias';
|
|
2
2
|
@use '../settings/config';
|
|
3
|
+
@use '../settings/mixins';
|
|
3
4
|
|
|
4
5
|
.icon {
|
|
5
6
|
display: inline-flex;
|
|
@@ -12,36 +13,41 @@
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
&.size-1 {
|
|
15
|
-
|
|
16
|
-
height: config.$icon-size-1x;
|
|
16
|
+
@include mixins.size(config.$icon-size-1x);
|
|
17
17
|
}
|
|
18
18
|
&.size-2 {
|
|
19
|
-
|
|
20
|
-
height: config.$icon-size-2x;
|
|
19
|
+
@include mixins.size(config.$icon-size-2x);
|
|
21
20
|
}
|
|
22
21
|
&.size-3 {
|
|
23
|
-
|
|
24
|
-
height: config.$icon-size-3x;
|
|
22
|
+
@include mixins.size(config.$icon-size-3x);
|
|
25
23
|
}
|
|
26
24
|
&.size-4 {
|
|
27
|
-
|
|
28
|
-
height: config.$icon-size-4x;
|
|
25
|
+
@include mixins.size(config.$icon-size-4x);
|
|
29
26
|
}
|
|
30
27
|
&.size-5 {
|
|
31
|
-
|
|
32
|
-
height: config.$icon-size-5x;
|
|
28
|
+
@include mixins.size(config.$icon-size-5x);
|
|
33
29
|
}
|
|
34
30
|
&.size-6 {
|
|
35
|
-
|
|
36
|
-
height: config.$icon-size-6x;
|
|
31
|
+
@include mixins.size(config.$icon-size-6x);
|
|
37
32
|
}
|
|
38
33
|
&.size-7 {
|
|
39
|
-
|
|
40
|
-
height: config.$icon-size-7x;
|
|
34
|
+
@include mixins.size(config.$icon-size-7x);
|
|
41
35
|
}
|
|
42
36
|
&.size-8 {
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
@include mixins.size(config.$icon-size-8x);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&.info {
|
|
41
|
+
@include mixins.svg-color(color_alias.$info-color-1000);
|
|
42
|
+
}
|
|
43
|
+
&.success {
|
|
44
|
+
@include mixins.svg-color(color_alias.$success-color-1000);
|
|
45
|
+
}
|
|
46
|
+
&.warning {
|
|
47
|
+
@include mixins.svg-color(color_alias.$warning-color-1000);
|
|
48
|
+
}
|
|
49
|
+
&.error {
|
|
50
|
+
@include mixins.svg-color(color_alias.$error-color-1000);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
@keyframes rotate {
|
package/src/atoms/Icon.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { classNames } from '../utils/classNames'
|
|
|
5
5
|
export type IconType = keyof typeof icons
|
|
6
6
|
|
|
7
7
|
export type IconSize = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8'
|
|
8
|
+
export type Variant = 'info' | 'success' | 'warning' | 'error'
|
|
8
9
|
|
|
9
10
|
export interface IconProps extends React.SVGAttributes<HTMLOrSVGElement> {
|
|
10
11
|
name: IconType
|
|
@@ -12,18 +13,20 @@ export interface IconProps extends React.SVGAttributes<HTMLOrSVGElement> {
|
|
|
12
13
|
title?: string
|
|
13
14
|
visible?: boolean
|
|
14
15
|
size?: IconSize
|
|
16
|
+
variant?: Variant
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export const Icon: React.FC<IconProps> = ({
|
|
18
20
|
name,
|
|
19
21
|
className,
|
|
22
|
+
variant,
|
|
20
23
|
size = '5',
|
|
21
24
|
visible = true,
|
|
22
25
|
...props
|
|
23
26
|
}) => {
|
|
24
27
|
if (!visible) return null
|
|
25
28
|
|
|
26
|
-
const cssClasses = classNames('icon', `size-${size}`, className, {
|
|
29
|
+
const cssClasses = classNames('icon', `size-${size}`, variant, className, {
|
|
27
30
|
rotate: name === 'Loading',
|
|
28
31
|
})
|
|
29
32
|
return (
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
@use '../../settings/mixins';
|
|
2
|
+
@use '../../settings/typography/content' as typography;
|
|
3
|
+
@use '../../settings/color_alias';
|
|
4
|
+
@use '../../settings/breakpoints';
|
|
5
|
+
@use '../../settings/depth';
|
|
6
|
+
@use '../../settings/config';
|
|
7
|
+
|
|
8
|
+
$modal-margin: 3rem;
|
|
9
|
+
$modal-width: 34.5rem;
|
|
10
|
+
$modal-detail-width: 50rem;
|
|
11
|
+
$modal-background-color: color_alias.$neutral-white;
|
|
12
|
+
$modal-boder-color: color_alias.$neutral-color-200;
|
|
13
|
+
$backdrop-opacity: 0.4;
|
|
14
|
+
$backdrop-background-color: color_alias.$neutral-color-900;
|
|
15
|
+
|
|
16
|
+
.modal {
|
|
17
|
+
position: fixed;
|
|
18
|
+
top: 0;
|
|
19
|
+
left: 0;
|
|
20
|
+
z-index: depth.$z-modal;
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
overflow-x: hidden;
|
|
24
|
+
overflow-y: auto;
|
|
25
|
+
outline: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.modal-dialog {
|
|
29
|
+
transition: transform 0.3s ease-out;
|
|
30
|
+
max-width: $modal-width;
|
|
31
|
+
position: relative;
|
|
32
|
+
width: auto;
|
|
33
|
+
margin-block: $modal-margin;
|
|
34
|
+
margin-inline: auto;
|
|
35
|
+
padding-inline: config.$space-4x;
|
|
36
|
+
pointer-events: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.modal-content {
|
|
40
|
+
position: relative;
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
width: 100%;
|
|
44
|
+
pointer-events: auto;
|
|
45
|
+
background-color: $modal-background-color;
|
|
46
|
+
background-clip: padding-box;
|
|
47
|
+
outline: 0;
|
|
48
|
+
border-radius: config.$corner-radius-xxs;
|
|
49
|
+
padding: config.$space-4x;
|
|
50
|
+
box-shadow:
|
|
51
|
+
0px 3px 6px -4px rgba(0, 0, 0, 0.12),
|
|
52
|
+
0px 6px 16px 0px rgba(0, 0, 0, 0.08),
|
|
53
|
+
0px 9px 28px 8px rgba(0, 0, 0, 0.05);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.modal-header {
|
|
57
|
+
gap: config.$space-2x;
|
|
58
|
+
padding-bottom: config.$space-1x;
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-shrink: 0;
|
|
61
|
+
align-items: center;
|
|
62
|
+
button {
|
|
63
|
+
margin-inline-start: auto;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.modal-title {
|
|
68
|
+
@include typography.h4;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.modal-body {
|
|
72
|
+
padding-top: config.$space-1x;
|
|
73
|
+
position: relative;
|
|
74
|
+
flex: 1 1 auto;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.modal-footer {
|
|
78
|
+
padding-top: config.$space-2x;
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-shrink: 0;
|
|
81
|
+
flex-wrap: wrap;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: flex-end;
|
|
84
|
+
gap: config.$space-2x;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.modal-backdrop {
|
|
88
|
+
opacity: $backdrop-opacity;
|
|
89
|
+
position: fixed;
|
|
90
|
+
top: 0;
|
|
91
|
+
left: 0;
|
|
92
|
+
z-index: depth.$z-modal;
|
|
93
|
+
width: 100vw;
|
|
94
|
+
height: 100vh;
|
|
95
|
+
background-color: $backdrop-background-color;
|
|
96
|
+
transition: opacity 0.15s linear;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.modal-details {
|
|
100
|
+
.modal-dialog {
|
|
101
|
+
max-width: 50rem;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.modal-header {
|
|
105
|
+
border-bottom: 1px solid $modal-boder-color;
|
|
106
|
+
padding-bottom: config.$space-3x;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.modal-body {
|
|
110
|
+
padding-top: config.$space-3x;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.modal-footer {
|
|
114
|
+
padding-top: config.$space-3x;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.modal-dialog-scrollable {
|
|
119
|
+
height: calc(100vh - $modal-margin * 2);
|
|
120
|
+
.modal-content {
|
|
121
|
+
max-height: 100%;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
}
|
|
124
|
+
.modal-body {
|
|
125
|
+
overflow-y: auto;
|
|
126
|
+
margin-inline: config.$space-4x * -1;
|
|
127
|
+
padding-inline: config.$space-4x;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.modal-icon {
|
|
132
|
+
margin-top: config.$space-1x;
|
|
133
|
+
align-self: flex-start;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
body:has(.modal-backdrop) {
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
padding-right: 0px;
|
|
139
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import './Modal.scss'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { ButtonProps } from '../Button'
|
|
4
|
+
import { Button } from '../Button'
|
|
5
|
+
import { Icon } from '../Icon'
|
|
6
|
+
import {
|
|
7
|
+
ModalBody,
|
|
8
|
+
ModalCloseButton,
|
|
9
|
+
ModalDialog,
|
|
10
|
+
ModalFooter,
|
|
11
|
+
ModalHeader,
|
|
12
|
+
ModalTitle,
|
|
13
|
+
} from '.'
|
|
14
|
+
|
|
15
|
+
export type Variant =
|
|
16
|
+
| 'info'
|
|
17
|
+
| 'success'
|
|
18
|
+
| 'warning'
|
|
19
|
+
| 'error'
|
|
20
|
+
| 'discard'
|
|
21
|
+
| 'details'
|
|
22
|
+
|
|
23
|
+
export interface ModalProps {
|
|
24
|
+
id: string
|
|
25
|
+
title: string
|
|
26
|
+
variant?: Variant
|
|
27
|
+
closeButton?: boolean
|
|
28
|
+
scrollable?: boolean
|
|
29
|
+
className?: string
|
|
30
|
+
onClose?: () => void
|
|
31
|
+
buttons: ButtonProps[]
|
|
32
|
+
children: ReactNode
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ICONS: { [key: string]: ReactNode } = {
|
|
36
|
+
info: <Icon className="modal-icon" name="Info" variant="info" />,
|
|
37
|
+
success: <Icon className="modal-icon" name="Check" variant="success" />,
|
|
38
|
+
warning: <Icon className="modal-icon" name="Warning" variant="warning" />,
|
|
39
|
+
error: <Icon className="modal-icon" name="Error" variant="error" />,
|
|
40
|
+
discard: <Icon className="modal-icon" name="Warning" variant="warning" />,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function Modal({
|
|
44
|
+
id,
|
|
45
|
+
title,
|
|
46
|
+
buttons,
|
|
47
|
+
onClose,
|
|
48
|
+
children,
|
|
49
|
+
closeButton = false,
|
|
50
|
+
variant = 'details',
|
|
51
|
+
...props
|
|
52
|
+
}: ModalProps) {
|
|
53
|
+
return (
|
|
54
|
+
<ModalDialog
|
|
55
|
+
aria-labelledby={`${id}-title`}
|
|
56
|
+
aria-describedby={`${id}-body`}
|
|
57
|
+
onClose={onClose}
|
|
58
|
+
details={variant === 'details'}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<ModalHeader>
|
|
62
|
+
{ICONS[variant]}
|
|
63
|
+
<ModalTitle id={`${id}-title`}>{title}</ModalTitle>
|
|
64
|
+
{closeButton && <ModalCloseButton onClick={onClose} />}
|
|
65
|
+
</ModalHeader>
|
|
66
|
+
<ModalBody id={`${id}-body`}>{children}</ModalBody>
|
|
67
|
+
<ModalFooter>
|
|
68
|
+
{buttons.map(({ ...button }) => (
|
|
69
|
+
<Button key={button.label} {...button} />
|
|
70
|
+
))}
|
|
71
|
+
</ModalFooter>
|
|
72
|
+
</ModalDialog>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import './Modal.scss'
|
|
2
|
+
import { classNames } from '../../utils/classNames'
|
|
3
|
+
|
|
4
|
+
export interface ModalBodyProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
5
|
+
|
|
6
|
+
export function ModalBody({ className, children, ...props }: ModalBodyProps) {
|
|
7
|
+
return (
|
|
8
|
+
<div className={classNames('modal-body', className)} {...props}>
|
|
9
|
+
{children}
|
|
10
|
+
</div>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import './Modal.scss'
|
|
2
|
+
import { IconButton } from '../Button'
|
|
3
|
+
|
|
4
|
+
export interface ModalCloseButtonProps {
|
|
5
|
+
label?: string
|
|
6
|
+
onClick?: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ModalCloseButton({
|
|
10
|
+
label = 'Close',
|
|
11
|
+
onClick,
|
|
12
|
+
}: ModalCloseButtonProps) {
|
|
13
|
+
return (
|
|
14
|
+
<IconButton
|
|
15
|
+
type="button"
|
|
16
|
+
variant="tertiary"
|
|
17
|
+
accessibilityLabel={label}
|
|
18
|
+
icon="Close"
|
|
19
|
+
size="4"
|
|
20
|
+
onClick={onClick}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|