@purpurds/modal 3.0.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/dist/LICENSE.txt +478 -0
- package/dist/modal-content.d.ts +69 -0
- package/dist/modal-content.d.ts.map +1 -0
- package/dist/modal-trigger.d.ts +8 -0
- package/dist/modal-trigger.d.ts.map +1 -0
- package/dist/modal.cjs.js +66 -0
- package/dist/modal.cjs.js.map +1 -0
- package/dist/modal.d.ts +22 -0
- package/dist/modal.d.ts.map +1 -0
- package/dist/modal.es.js +2144 -0
- package/dist/modal.es.js.map +1 -0
- package/dist/modal.system.js +66 -0
- package/dist/modal.system.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +66 -0
- package/readme.mdx +81 -0
- package/src/global.d.ts +4 -0
- package/src/modal-content.module.scss +251 -0
- package/src/modal-content.stories.tsx +120 -0
- package/src/modal-content.test.tsx +141 -0
- package/src/modal-content.tsx +304 -0
- package/src/modal-trigger.tsx +27 -0
- package/src/modal.stories.tsx +124 -0
- package/src/modal.test.tsx +74 -0
- package/src/modal.tsx +36 -0
package/dist/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
._purpur-modal-content_1fnes_1{position:fixed;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;overflow:auto;background-color:var(--purpur-color-background-primary);animation:_fadeIn_1fnes_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out)}._purpur-modal-content_1fnes_1:focus{outline:0}@media (min-width: 600px){._purpur-modal-content_1fnes_1{inset:unset;top:50%;left:50%;width:720px;max-width:calc(100% - var(--purpur-spacing-300) * 2);min-height:320px;max-height:80%;overflow:hidden;border-radius:var(--purpur-border-radius-lg);box-sizing:border-box;transform:translate(-50%,-50%);box-shadow:var(--purpur-shadow-lg)}}@media (min-width: 600px){._purpur-modal-content__overlay_1fnes_30{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--purpur-color-overlay-default);animation:_fadeIn_1fnes_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out)}}._purpur-modal-content_1fnes_1 ._purpur-modal-content__close-button_1fnes_37{position:absolute;top:var(--purpur-spacing-100);right:var(--purpur-spacing-100);z-index:2}@media (min-width: 600px){._purpur-modal-content_1fnes_1 ._purpur-modal-content__close-button_1fnes_37{top:var(--purpur-spacing-150)}}._purpur-modal-content__wrapper_1fnes_48{height:100%;overflow:auto}@media (min-width: 600px){._purpur-modal-content__wrapper_1fnes_48{display:flex;flex-direction:column;overflow:hidden}}._purpur-modal-content__wrapper-inner_1fnes_59{display:flex;flex-direction:column}@media (min-width: 600px){._purpur-modal-content__wrapper-inner_1fnes_59{overflow:hidden}}._purpur-modal-content__image-wrapper_1fnes_68{position:relative;flex-shrink:0;order:-1;width:100%;aspect-ratio:2/1;overflow:hidden}@media (((min-width: 600px) and (max-width: 649px) and (max-height: 720px)) or ((min-width: 650px) and (max-height: 820px))){._purpur-modal-content__image-wrapper_1fnes_68{aspect-ratio:3/1}}._purpur-modal-content__image-wrapper_1fnes_68 img{position:absolute;top:50%;left:50%;display:block;width:100%;height:auto;transform:translate(-50%,-50%)}._purpur-modal-content__header_1fnes_91{position:sticky;top:0;flex-grow:0;padding:var(--purpur-spacing-200) calc(var(--purpur-spacing-600) + var(--purpur-spacing-150)) 0 var(--purpur-spacing-200);background:var(--purpur-color-background-primary)}@media (min-width: 600px){._purpur-modal-content__header_1fnes_91{position:static;padding:var(--purpur-spacing-250) calc(var(--purpur-spacing-800) + var(--purpur-spacing-50)) 0 var(--purpur-spacing-300)}}._purpur-modal-content__title_1fnes_104,._purpur-modal-content__description_1fnes_107{margin:0}._purpur-modal-content__body_1fnes_110{padding:var(--purpur-spacing-200) var(--purpur-spacing-200) 0}@media (min-width: 600px){._purpur-modal-content__body_1fnes_110{height:100%;overflow:auto;padding:var(--purpur-spacing-250) var(--purpur-spacing-300) 0}}._purpur-modal-content__body-inner_1fnes_120{position:relative;z-index:-2;display:flex;flex-direction:column;gap:var(--purpur-spacing-400);padding-bottom:var(--purpur-spacing-250)}@media (min-width: 600px){._purpur-modal-content__body-inner_1fnes_120{padding-bottom:var(--purpur-spacing-300)}}._purpur-modal-content__actions_1fnes_133{display:flex;flex-direction:column;gap:var(--purpur-spacing-200);margin-top:auto}@media (min-width: 600px){._purpur-modal-content__actions_1fnes_133{flex-direction:row-reverse;flex-grow:0}}._purpur-modal-content__button_1fnes_145{width:100%}@media (min-width: 600px){._purpur-modal-content__button_1fnes_145{width:auto}._purpur-modal-content__button_1fnes_145:nth-child(3){margin-right:auto}}._purpur-modal-content--with-image_1fnes_156:not(._purpur-modal-content--overflow_1fnes_156) ._purpur-modal-content__header_1fnes_91{padding-top:var(--purpur-spacing-250)}@media (min-width: 600px){._purpur-modal-content--overflow_1fnes_156:not(._purpur-modal-content--with-image_1fnes_156) ._purpur-modal-content__header_1fnes_91{position:relative}._purpur-modal-content--overflow_1fnes_156:not(._purpur-modal-content--with-image_1fnes_156) ._purpur-modal-content__close-button_1fnes_37{top:50%;transform:translateY(-50%)}}._purpur-modal-content--overflow_1fnes_156 ._purpur-modal-content__header_1fnes_91{padding-bottom:var(--purpur-spacing-200);border-bottom:1px solid var(--purpur-color-border-weak)}._purpur-modal-content--overflow_1fnes_156 ._purpur-modal-content__body_1fnes_110{padding-top:var(--purpur-spacing-300)}@media (min-width: 600px){._purpur-modal-content--overflow_1fnes_156 ._purpur-modal-content__body_1fnes_110{padding-top:var(--purpur-spacing-400)}}._purpur-modal-content--overflow_1fnes_156 ._purpur-modal-content__actions_1fnes_133{position:relative}._purpur-modal-content--overflow_1fnes_156 ._purpur-modal-content__actions_1fnes_133:after{content:"";position:absolute;top:-40px;left:0;z-index:-1;width:100%;height:40px;background:linear-gradient(180deg,rgba(255,255,255,0) 0%,var(--purpur-color-background-primary) 100%);pointer-events:none}._purpur-modal-content--overflow_1fnes_156._purpur-modal-content--sticky-footer_1fnes_194 ._purpur-modal-content__actions_1fnes_133{border-top:1px solid var(--purpur-color-border-weak)}._purpur-modal-content--sticky-footer_1fnes_194 ._purpur-modal-content__body-inner_1fnes_120{padding-bottom:var(--purpur-spacing-300)}._purpur-modal-content--sticky-footer_1fnes_194 ._purpur-modal-content__actions_1fnes_133{padding:var(--purpur-spacing-200)}@media (min-width: 600px){._purpur-modal-content--sticky-footer_1fnes_194 ._purpur-modal-content__actions_1fnes_133{padding-left:var(--purpur-spacing-300);padding-right:var(--purpur-spacing-300)}}@keyframes _fadeIn_1fnes_1{0%{opacity:0}to{opacity:1}}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@purpurds/modal",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"license": "AGPL-3.0-only",
|
|
5
|
+
"main": "./dist/modal.cjs.js",
|
|
6
|
+
"types": "./dist/modal.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/modal.cjs.js",
|
|
10
|
+
"systemjs": "./dist/modal.system.js",
|
|
11
|
+
"types": "./dist/modal.d.ts",
|
|
12
|
+
"default": "./dist/modal.es.js"
|
|
13
|
+
},
|
|
14
|
+
"./styles": "./dist/styles.css"
|
|
15
|
+
},
|
|
16
|
+
"source": "src/modal.tsx",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@radix-ui/react-dialog": "~1.0.5",
|
|
19
|
+
"classnames": "~2.5.0",
|
|
20
|
+
"@purpurds/button": "3.0.0",
|
|
21
|
+
"@purpurds/icon": "3.0.0",
|
|
22
|
+
"@purpurds/heading": "3.0.0",
|
|
23
|
+
"@purpurds/text-spacing": "3.0.0",
|
|
24
|
+
"@purpurds/paragraph": "3.0.0",
|
|
25
|
+
"@purpurds/tokens": "3.0.0",
|
|
26
|
+
"@purpurds/visually-hidden": "3.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@rushstack/eslint-patch": "~1.7.0",
|
|
30
|
+
"@storybook/blocks": "~7.6.0",
|
|
31
|
+
"@storybook/react": "~7.6.0",
|
|
32
|
+
"@telia/base-rig": "~8.2.0",
|
|
33
|
+
"@telia/react-rig": "~3.2.0",
|
|
34
|
+
"@testing-library/dom": "~9.3.3",
|
|
35
|
+
"@testing-library/jest-dom": "~6.3.0",
|
|
36
|
+
"@testing-library/react": "~14.1.2",
|
|
37
|
+
"@types/node": "18",
|
|
38
|
+
"@types/react-dom": "~18.2.17",
|
|
39
|
+
"@types/react": "~18.2.42",
|
|
40
|
+
"eslint-plugin-testing-library": "~6.2.0",
|
|
41
|
+
"eslint": "~8.56.0",
|
|
42
|
+
"jsdom": "~22.1.0",
|
|
43
|
+
"lint-staged": "~10.5.3",
|
|
44
|
+
"prettier": "~2.8.8",
|
|
45
|
+
"react-dom": "~18.2.0",
|
|
46
|
+
"react": "~18.2.0",
|
|
47
|
+
"typescript": "~5.2.2",
|
|
48
|
+
"vite": "~5.0.6",
|
|
49
|
+
"vitest": "~1.2.0",
|
|
50
|
+
"@purpurds/component-rig": "1.0.0"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build:dev": "vite",
|
|
54
|
+
"build:watch": "vite build --watch",
|
|
55
|
+
"build": "rm -rf dist && vite build && vite build --mode systemjs",
|
|
56
|
+
"ci:build": "rushx build",
|
|
57
|
+
"coverage": "vitest run --coverage",
|
|
58
|
+
"lint:fix": "eslint . --fix",
|
|
59
|
+
"lint": "lint-staged --no-stash 2>&1",
|
|
60
|
+
"sbdev": "rush sbdev",
|
|
61
|
+
"test:unit": "vitest run --passWithNoTests",
|
|
62
|
+
"test:watch": "vitest --watch",
|
|
63
|
+
"test": "rushx test:unit",
|
|
64
|
+
"typecheck": "tsc -p ./tsconfig.json"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/readme.mdx
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Meta, Stories, ArgTypes, Primary, Subtitle } from "@storybook/blocks";
|
|
2
|
+
|
|
3
|
+
import * as ModalStories from "./src/modal.stories";
|
|
4
|
+
import * as ModalContentStories from "./src/modal-content.stories";
|
|
5
|
+
import packageInfo from "./package.json";
|
|
6
|
+
|
|
7
|
+
<Meta name="Docs" title="Components/Modal" of={ModalStories} />
|
|
8
|
+
|
|
9
|
+
# Modal
|
|
10
|
+
|
|
11
|
+
<Subtitle>Version {packageInfo.version}</Subtitle>
|
|
12
|
+
|
|
13
|
+
### Showcase
|
|
14
|
+
|
|
15
|
+
<Primary />
|
|
16
|
+
|
|
17
|
+
### Properties
|
|
18
|
+
|
|
19
|
+
#### Modal
|
|
20
|
+
|
|
21
|
+
<ArgTypes of={ModalStories} />
|
|
22
|
+
|
|
23
|
+
#### ModalContent
|
|
24
|
+
|
|
25
|
+
<ArgTypes of={ModalContentStories} />
|
|
26
|
+
|
|
27
|
+
### Installation
|
|
28
|
+
|
|
29
|
+
#### Via NPM
|
|
30
|
+
|
|
31
|
+
Add the dependency to your consumer app like `"@purpurds/modal": "x.y.z"`
|
|
32
|
+
|
|
33
|
+
#### From outside the monorepo (build-time)
|
|
34
|
+
|
|
35
|
+
To install this package, you need to setup access to the artifactory. [Click here to go to the guide on how to do that](https://github.com/telia-company/jfrog-documentation/blob/main/doc/JFrog/JFrog_Onboarding.md#getting-access-to-artifactory-and-other-jfrog-applications).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
In MyApp.tsx
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import "@purpurds/tokens/index.css";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
and
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import "@purpurds/modal/styles";
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
In MyComponent.tsx
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { Modal } from "@purpurds/modal";
|
|
55
|
+
|
|
56
|
+
export const MyComponent = () => {
|
|
57
|
+
const actions = [
|
|
58
|
+
{
|
|
59
|
+
label: "Primary action",
|
|
60
|
+
onClick: () => {
|
|
61
|
+
// click event handler
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Modal>
|
|
68
|
+
<Modal.Trigger>Open modal</Modal.Trigger>
|
|
69
|
+
<Modal.Content
|
|
70
|
+
title="A title"
|
|
71
|
+
description="A short optional description of the modal"
|
|
72
|
+
actions={actions}
|
|
73
|
+
showCloseButton
|
|
74
|
+
closeButtonAllyLabel="Close"
|
|
75
|
+
>
|
|
76
|
+
Some content
|
|
77
|
+
</Modal.Content>
|
|
78
|
+
</Modal>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
```
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
@import "@purpurds/tokens/breakpoint/variables";
|
|
2
|
+
|
|
3
|
+
$fadeIn: fadeIn var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out);
|
|
4
|
+
|
|
5
|
+
.purpur-modal-content {
|
|
6
|
+
$root: &;
|
|
7
|
+
|
|
8
|
+
position: fixed;
|
|
9
|
+
inset: 0;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
overflow: auto;
|
|
13
|
+
background-color: var(--purpur-color-background-primary);
|
|
14
|
+
animation: $fadeIn;
|
|
15
|
+
|
|
16
|
+
&:focus {
|
|
17
|
+
outline: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
21
|
+
inset: unset;
|
|
22
|
+
top: 50%;
|
|
23
|
+
left: 50%;
|
|
24
|
+
width: 720px;
|
|
25
|
+
max-width: calc(100% - (var(--purpur-spacing-300) * 2));
|
|
26
|
+
min-height: 320px;
|
|
27
|
+
max-height: 80%;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
border-radius: var(--purpur-border-radius-lg);
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
transform: translate(-50%, -50%);
|
|
32
|
+
box-shadow: var(--purpur-shadow-lg);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&__overlay {
|
|
36
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
37
|
+
position: fixed;
|
|
38
|
+
inset: 0;
|
|
39
|
+
background: var(--purpur-color-overlay-default);
|
|
40
|
+
animation: $fadeIn;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#{$root}__close-button {
|
|
45
|
+
position: absolute;
|
|
46
|
+
top: var(--purpur-spacing-100);
|
|
47
|
+
right: var(--purpur-spacing-100);
|
|
48
|
+
z-index: 2;
|
|
49
|
+
|
|
50
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
51
|
+
top: var(--purpur-spacing-150);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&__wrapper {
|
|
56
|
+
height: 100%;
|
|
57
|
+
overflow: auto;
|
|
58
|
+
|
|
59
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&__wrapper-inner {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
|
|
70
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&__image-wrapper {
|
|
76
|
+
position: relative;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
order: -1;
|
|
79
|
+
width: 100%;
|
|
80
|
+
aspect-ratio: 2 / 1;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
|
|
83
|
+
/* Shrink image height when it's too large on medium sized and large screens */
|
|
84
|
+
@media (((min-width: $purpur-breakpoint-md) and (max-width: 649px) and (max-height: 720px)) or
|
|
85
|
+
((min-width: 650px) and (max-height: 820px))) {
|
|
86
|
+
aspect-ratio: 3 / 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
img {
|
|
90
|
+
position: absolute;
|
|
91
|
+
top: 50%;
|
|
92
|
+
left: 50%;
|
|
93
|
+
display: block;
|
|
94
|
+
width: 100%;
|
|
95
|
+
height: auto;
|
|
96
|
+
transform: translate(-50%, -50%);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&__header {
|
|
101
|
+
position: sticky;
|
|
102
|
+
top: 0;
|
|
103
|
+
flex-grow: 0;
|
|
104
|
+
padding: var(--purpur-spacing-200) calc(var(--purpur-spacing-600) + var(--purpur-spacing-150)) 0
|
|
105
|
+
var(--purpur-spacing-200);
|
|
106
|
+
background: var(--purpur-color-background-primary);
|
|
107
|
+
|
|
108
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
109
|
+
position: static;
|
|
110
|
+
padding: var(--purpur-spacing-250) calc(var(--purpur-spacing-800) + var(--purpur-spacing-50))
|
|
111
|
+
0 var(--purpur-spacing-300);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
&__title {
|
|
116
|
+
margin: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&__description {
|
|
120
|
+
margin: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&__body {
|
|
124
|
+
padding: var(--purpur-spacing-200) var(--purpur-spacing-200) 0;
|
|
125
|
+
|
|
126
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
127
|
+
height: 100%;
|
|
128
|
+
overflow: auto;
|
|
129
|
+
padding: var(--purpur-spacing-250) var(--purpur-spacing-300) 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&__body-inner {
|
|
134
|
+
position: relative;
|
|
135
|
+
z-index: -2;
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: var(--purpur-spacing-400);
|
|
139
|
+
padding-bottom: var(--purpur-spacing-250);
|
|
140
|
+
|
|
141
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
142
|
+
padding-bottom: var(--purpur-spacing-300);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&__actions {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
gap: var(--purpur-spacing-200);
|
|
150
|
+
margin-top: auto;
|
|
151
|
+
|
|
152
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
153
|
+
flex-direction: row-reverse;
|
|
154
|
+
flex-grow: 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&__button {
|
|
159
|
+
width: 100%;
|
|
160
|
+
|
|
161
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
162
|
+
width: auto;
|
|
163
|
+
|
|
164
|
+
&:nth-child(3) {
|
|
165
|
+
margin-right: auto;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&--with-image:not(#{$root}--overflow) #{$root}__header {
|
|
171
|
+
padding-top: var(--purpur-spacing-250);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
&--overflow {
|
|
175
|
+
&:not(#{$root}--with-image) {
|
|
176
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
177
|
+
#{$root}__header {
|
|
178
|
+
position: relative;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#{$root}__close-button {
|
|
182
|
+
top: 50%;
|
|
183
|
+
transform: translateY(-50%);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#{$root}__header {
|
|
189
|
+
padding-bottom: var(--purpur-spacing-200);
|
|
190
|
+
border-bottom: 1px solid var(--purpur-color-border-weak);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#{$root}__body {
|
|
194
|
+
padding-top: var(--purpur-spacing-300);
|
|
195
|
+
|
|
196
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
197
|
+
padding-top: var(--purpur-spacing-400);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#{$root}__actions {
|
|
202
|
+
$overflowFaderHeight: 40px;
|
|
203
|
+
|
|
204
|
+
position: relative;
|
|
205
|
+
|
|
206
|
+
&::after {
|
|
207
|
+
content: "";
|
|
208
|
+
position: absolute;
|
|
209
|
+
top: -$overflowFaderHeight;
|
|
210
|
+
left: 0;
|
|
211
|
+
z-index: -1;
|
|
212
|
+
width: 100%;
|
|
213
|
+
height: $overflowFaderHeight;
|
|
214
|
+
background: linear-gradient(
|
|
215
|
+
180deg,
|
|
216
|
+
rgba(255, 255, 255, 0) 0%,
|
|
217
|
+
var(--purpur-color-background-primary) 100%
|
|
218
|
+
);
|
|
219
|
+
pointer-events: none;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
&#{$root}--sticky-footer #{$root}__actions {
|
|
224
|
+
border-top: 1px solid var(--purpur-color-border-weak);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
&--sticky-footer {
|
|
229
|
+
#{$root}__body-inner {
|
|
230
|
+
padding-bottom: var(--purpur-spacing-300);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#{$root}__actions {
|
|
234
|
+
padding: var(--purpur-spacing-200);
|
|
235
|
+
|
|
236
|
+
@media (min-width: $purpur-breakpoint-md) {
|
|
237
|
+
padding-left: var(--purpur-spacing-300);
|
|
238
|
+
padding-right: var(--purpur-spacing-300);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@keyframes fadeIn {
|
|
245
|
+
from {
|
|
246
|
+
opacity: 0;
|
|
247
|
+
}
|
|
248
|
+
to {
|
|
249
|
+
opacity: 1;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import * as RadixDialog from "@radix-ui/react-dialog";
|
|
3
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
4
|
+
import { Paragraph } from "@purpurds/paragraph";
|
|
5
|
+
|
|
6
|
+
import { ModalContent as ModalContentCmp, primaryActionVariants } from "./modal-content";
|
|
7
|
+
import { TextSpacing } from "@purpurds/text-spacing";
|
|
8
|
+
|
|
9
|
+
import "@purpurds/button/styles";
|
|
10
|
+
import "@purpurds/icon/styles";
|
|
11
|
+
import "@purpurds/text-spacing/styles";
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof ModalContentCmp> = {
|
|
14
|
+
title: "Components/Modal",
|
|
15
|
+
component: ModalContentCmp,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
type Story = StoryObj<typeof ModalContentCmp>;
|
|
20
|
+
|
|
21
|
+
const defaultArgs = {
|
|
22
|
+
actions: [
|
|
23
|
+
{
|
|
24
|
+
label: "Primary button",
|
|
25
|
+
onClick: () => {},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: "Secondary button",
|
|
29
|
+
onClick: () => {},
|
|
30
|
+
},
|
|
31
|
+
{ label: "Tertiary text button", onClick: () => {} },
|
|
32
|
+
],
|
|
33
|
+
description: "This is an optional description",
|
|
34
|
+
primaryActionVariant: primaryActionVariants[0],
|
|
35
|
+
stickyButtons: true,
|
|
36
|
+
title: "Title",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const argTypes = {
|
|
40
|
+
primaryActionVariant: { control: "select", options: primaryActionVariants },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const ModalContent: Story = {
|
|
44
|
+
args: defaultArgs,
|
|
45
|
+
parameters: {
|
|
46
|
+
design: [
|
|
47
|
+
{
|
|
48
|
+
name: "Modal",
|
|
49
|
+
type: "figma",
|
|
50
|
+
url: "https://www.figma.com/file/TggtRkYyKpwgKTU0LxuYFN/Modal-redesign?type=design&node-id=104-731&mode=design&t=sm44NPLlG3tjpFQb-0",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
argTypes,
|
|
55
|
+
render: (args) => {
|
|
56
|
+
return (
|
|
57
|
+
<RadixDialog.Root open>
|
|
58
|
+
<ModalContentCmp {...args}>
|
|
59
|
+
<TextSpacing>
|
|
60
|
+
<Paragraph variant="paragraph-100">
|
|
61
|
+
Lorem ipsum dolor sit amet consectetur. Diam vitae leo amet tortor ut faucibus diam
|
|
62
|
+
faucibus eu. Pellentesque quis pellentesque sit fermentum. Mi id aenean aliquam nibh
|
|
63
|
+
placerat.Lorem ipsum dolor sit amet consectetur. Diam vitae leo amet tortor ut
|
|
64
|
+
faucibus diam faucibus eu.
|
|
65
|
+
</Paragraph>
|
|
66
|
+
<Paragraph variant="paragraph-100">
|
|
67
|
+
Pellentesque quis pellentesque sit fermentum. Mi id aenean aliquam nibh placerat.Lorem
|
|
68
|
+
ipsum dolor sit amet consectetur. Diam vitae leo amet tortor ut faucibus diam faucibus
|
|
69
|
+
eu. Pellentesque quis pellentesque sit fermentum. Mi id aenean aliquam nibh placerat.
|
|
70
|
+
</Paragraph>
|
|
71
|
+
</TextSpacing>
|
|
72
|
+
</ModalContentCmp>
|
|
73
|
+
</RadixDialog.Root>
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const ModalContentWithImage: Story = {
|
|
79
|
+
args: {
|
|
80
|
+
...defaultArgs,
|
|
81
|
+
description: undefined,
|
|
82
|
+
image: (
|
|
83
|
+
<img
|
|
84
|
+
src="https://www.telia.se/images/i15skfqwpurk/5YYelnwdIJGush05RYsE6A/04d4eeb571bca6d5c72b557f6da92c92/Telia_Company_Reinvention_69.jpg"
|
|
85
|
+
alt="Familjens unga ser på tv tillsammans mysig stämning men spännande"
|
|
86
|
+
/>
|
|
87
|
+
),
|
|
88
|
+
},
|
|
89
|
+
parameters: {
|
|
90
|
+
design: [
|
|
91
|
+
{
|
|
92
|
+
name: "Modal",
|
|
93
|
+
type: "figma",
|
|
94
|
+
url: "https://www.figma.com/file/TggtRkYyKpwgKTU0LxuYFN/Modal-redesign?type=design&node-id=104-731&mode=design&t=sm44NPLlG3tjpFQb-0",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
argTypes,
|
|
99
|
+
render: (args) => {
|
|
100
|
+
return (
|
|
101
|
+
<RadixDialog.Root open>
|
|
102
|
+
<ModalContentCmp {...args}>
|
|
103
|
+
<TextSpacing>
|
|
104
|
+
<Paragraph variant="paragraph-100">
|
|
105
|
+
Lorem ipsum dolor sit amet consectetur. Diam vitae leo amet tortor ut faucibus diam
|
|
106
|
+
faucibus eu. Pellentesque quis pellentesque sit fermentum. Mi id aenean aliquam nibh
|
|
107
|
+
placerat.Lorem ipsum dolor sit amet consectetur. Diam vitae leo amet tortor ut
|
|
108
|
+
faucibus diam faucibus eu.
|
|
109
|
+
</Paragraph>
|
|
110
|
+
<Paragraph variant="paragraph-100">
|
|
111
|
+
Pellentesque quis pellentesque sit fermentum. Mi id aenean aliquam nibh placerat.Lorem
|
|
112
|
+
ipsum dolor sit amet consectetur. Diam vitae leo amet tortor ut faucibus diam faucibus
|
|
113
|
+
eu. Pellentesque quis pellentesque sit fermentum. Mi id aenean aliquam nibh placerat.
|
|
114
|
+
</Paragraph>
|
|
115
|
+
</TextSpacing>
|
|
116
|
+
</ModalContentCmp>
|
|
117
|
+
</RadixDialog.Root>
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import * as matchers from "@testing-library/jest-dom/matchers";
|
|
3
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { Modal } from "./modal";
|
|
7
|
+
import { ModalActions, MAX_NUMBER_OF_ACTIONS } from "./modal-content";
|
|
8
|
+
|
|
9
|
+
expect.extend(matchers);
|
|
10
|
+
|
|
11
|
+
describe("ModalContent", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
cleanup();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should render title", () => {
|
|
17
|
+
render(
|
|
18
|
+
<Modal open>
|
|
19
|
+
<Modal.Trigger data-testid="modal trigger">
|
|
20
|
+
<button type="button">Open</button>
|
|
21
|
+
</Modal.Trigger>
|
|
22
|
+
<Modal.Content data-testid="modal content" title="Title">
|
|
23
|
+
Some content
|
|
24
|
+
</Modal.Content>
|
|
25
|
+
</Modal>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(screen.getByTestId("modal content title")).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should render description", () => {
|
|
32
|
+
render(
|
|
33
|
+
<Modal open>
|
|
34
|
+
<Modal.Trigger data-testid="modal trigger">
|
|
35
|
+
<button type="button">Open</button>
|
|
36
|
+
</Modal.Trigger>
|
|
37
|
+
<Modal.Content
|
|
38
|
+
data-testid="modal content"
|
|
39
|
+
title="Title"
|
|
40
|
+
description="This is a description"
|
|
41
|
+
>
|
|
42
|
+
Some content
|
|
43
|
+
</Modal.Content>
|
|
44
|
+
</Modal>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(screen.getByTestId("modal content description")).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should render image", () => {
|
|
51
|
+
render(
|
|
52
|
+
<Modal open>
|
|
53
|
+
<Modal.Trigger data-testid="modal trigger">
|
|
54
|
+
<button type="button">Open</button>
|
|
55
|
+
</Modal.Trigger>
|
|
56
|
+
<Modal.Content
|
|
57
|
+
data-testid="modal content"
|
|
58
|
+
title="Title"
|
|
59
|
+
image={<img src="image.jpg" alt="alt text" />}
|
|
60
|
+
>
|
|
61
|
+
Some content
|
|
62
|
+
</Modal.Content>
|
|
63
|
+
</Modal>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(screen.getByTestId("modal content image")).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should render close button if showCloseButton is set to true", () => {
|
|
70
|
+
render(
|
|
71
|
+
<Modal open>
|
|
72
|
+
<Modal.Trigger data-testid="modal trigger">
|
|
73
|
+
<button type="button">Open</button>
|
|
74
|
+
</Modal.Trigger>
|
|
75
|
+
<Modal.Content
|
|
76
|
+
data-testid="modal content"
|
|
77
|
+
title="Title"
|
|
78
|
+
showCloseButton
|
|
79
|
+
closeButtonAllyLabel="Close"
|
|
80
|
+
>
|
|
81
|
+
Some content
|
|
82
|
+
</Modal.Content>
|
|
83
|
+
</Modal>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(screen.getByTestId("modal content close-button")).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const testActions = [
|
|
91
|
+
{
|
|
92
|
+
label: "Primary",
|
|
93
|
+
onClick: vi.fn(),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: "Secondary",
|
|
97
|
+
onClick: vi.fn(),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
label: "Tertiary",
|
|
101
|
+
onClick: vi.fn(),
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
describe("ModalActions", () => {
|
|
106
|
+
afterEach(() => {
|
|
107
|
+
cleanup();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should render a button for each action", () => {
|
|
111
|
+
render(<ModalActions actions={testActions} />);
|
|
112
|
+
|
|
113
|
+
expect(screen.getAllByTestId("modal actions button").length).toBe(3);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it(`should render a maximum of ${MAX_NUMBER_OF_ACTIONS} buttons`, () => {
|
|
117
|
+
render(
|
|
118
|
+
<ModalActions
|
|
119
|
+
actions={Array(MAX_NUMBER_OF_ACTIONS + 1)
|
|
120
|
+
.fill(null)
|
|
121
|
+
.map((_, i) => ({ label: i.toString(), onClick: vi.fn() }))}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(screen.getAllByTestId("modal actions button").length).toBe(MAX_NUMBER_OF_ACTIONS);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should trigger `onClick` callback function for each button", () => {
|
|
129
|
+
render(<ModalActions actions={testActions} primaryActionVariant="expressive" />);
|
|
130
|
+
|
|
131
|
+
const buttons = screen.getAllByTestId("modal actions button");
|
|
132
|
+
|
|
133
|
+
fireEvent.click(buttons[0]);
|
|
134
|
+
fireEvent.click(buttons[1]);
|
|
135
|
+
fireEvent.click(buttons[2]);
|
|
136
|
+
|
|
137
|
+
expect(testActions[0].onClick).toHaveBeenCalledTimes(1);
|
|
138
|
+
expect(testActions[1].onClick).toHaveBeenCalledTimes(1);
|
|
139
|
+
expect(testActions[2].onClick).toHaveBeenCalledTimes(1);
|
|
140
|
+
});
|
|
141
|
+
});
|