@keenmate/pure-admin-core 2.3.6 → 2.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/README.md +23 -29
- package/dist/css/main.css +68 -148
- package/package.json +1 -5
- package/snippets/AUDIT.md +94 -0
- package/snippets/alerts.html +264 -89
- package/snippets/badges.html +193 -61
- package/snippets/buttons.html +178 -0
- package/snippets/callouts.html +210 -129
- package/snippets/cards.html +383 -200
- package/snippets/checkbox-lists.html +199 -65
- package/snippets/code.html +55 -11
- package/snippets/command-palette.html +401 -111
- package/snippets/comparison.html +144 -93
- package/snippets/customization.html +311 -104
- package/snippets/data-display.html +584 -0
- package/snippets/detail-panel.html +470 -138
- package/snippets/filter-card.html +246 -0
- package/snippets/forms.html +408 -308
- package/snippets/grid.html +253 -141
- package/snippets/layout.html +379 -480
- package/snippets/lists.html +144 -47
- package/snippets/loaders.html +64 -39
- package/snippets/manifest.json +330 -280
- package/snippets/modal-dialogs.html +137 -64
- package/snippets/modals.html +221 -151
- package/snippets/notifications.html +285 -0
- package/snippets/popconfirm.html +213 -19
- package/snippets/profile.html +290 -330
- package/snippets/statistics.html +247 -0
- package/snippets/tables.html +359 -150
- package/snippets/tabs.html +129 -45
- package/snippets/timeline.html +123 -56
- package/snippets/toasts.html +179 -31
- package/snippets/tooltips.html +199 -81
- package/snippets/typography.html +183 -58
- package/snippets/utilities.html +511 -415
- package/snippets/virtual-scroll.html +201 -75
- package/snippets/web-daterangepicker.html +369 -189
- package/snippets/web-multiselect.html +360 -124
- package/src/scss/core-components/_alerts.scss +51 -12
- package/src/scss/core-components/_pagers.scss +1 -1
- package/src/scss/core-components/_popconfirm.scss +35 -13
- package/src/scss/core-components/_profile.scss +18 -8
- package/src/scss/core-components/_statistics.scss +12 -12
- package/src/scss/core-components/_tables.scss +2 -134
- package/src/scss/variables/_components.scss +17 -2
- package/scripts/download-themes.js +0 -351
package/snippets/modals.html
CHANGED
|
@@ -1,48 +1,23 @@
|
|
|
1
1
|
<!-- ================================
|
|
2
2
|
MODAL SNIPPETS
|
|
3
3
|
Pure Admin Visual Framework
|
|
4
|
+
|
|
5
|
+
Classic overlay-dialog component. Toggle .pa-modal--show to open;
|
|
6
|
+
class off to close. The default body already scrolls when content
|
|
7
|
+
exceeds the container's max-height — use --scrollable only when
|
|
8
|
+
you want a shorter explicit ceiling.
|
|
4
9
|
================================ -->
|
|
5
10
|
|
|
6
|
-
<!--
|
|
7
|
-
MODAL WRAPPER CLASSES:
|
|
8
|
-
- pa-modal: Base modal wrapper
|
|
9
|
-
- pa-modal--show: Show the modal (add/remove via JavaScript)
|
|
10
|
-
- pa-modal--top: Position modal near top instead of center (useful for search/command interfaces)
|
|
11
|
-
- pa-modal--static: Prevent closing via ESC key or backdrop click (must use explicit button)
|
|
12
|
-
- pa-modal--primary: Primary themed modal (blue header)
|
|
13
|
-
- pa-modal--success: Success themed modal (green header)
|
|
14
|
-
- pa-modal--warning: Warning themed modal (orange header)
|
|
15
|
-
- pa-modal--danger: Danger themed modal (red header)
|
|
16
|
-
- pa-modal--info: Info themed modal (cyan header)
|
|
17
|
-
|
|
18
|
-
CONTAINER SIZE CLASSES:
|
|
19
|
-
- pa-modal__container--sm: Small (20rem / 320px)
|
|
20
|
-
- pa-modal__container: Medium/default (30rem / 480px)
|
|
21
|
-
- pa-modal__container--lg: Large (50rem / 800px)
|
|
22
|
-
- pa-modal__container--xl: Extra large (70rem / 1120px)
|
|
23
|
-
- pa-modal__container--xxl: Extra extra large (90rem / 1440px)
|
|
24
|
-
- pa-modal__container--fw: Full width (constrained by viewport margins)
|
|
25
|
-
|
|
26
|
-
BODY CLASSES:
|
|
27
|
-
- pa-modal__body: Base body class
|
|
28
|
-
- pa-modal__body--scrollable: Scrollable body with max-height (header/footer fixed)
|
|
29
|
-
|
|
30
|
-
HEADER THEME CLASSES (alternative to modal-level themes):
|
|
31
|
-
- pa-modal__header--primary: Blue themed header
|
|
32
|
-
- pa-modal__header--success: Green themed header
|
|
33
|
-
- pa-modal__header--warning: Orange themed header
|
|
34
|
-
- pa-modal__header--danger: Red themed header
|
|
35
|
-
-->
|
|
36
11
|
|
|
37
|
-
<!-- BASIC MODAL (
|
|
12
|
+
<!-- BASIC MODAL (centered) -->
|
|
38
13
|
|
|
39
|
-
<!-- Modal Structure -->
|
|
40
14
|
<div class="pa-modal pa-modal--show" id="basicModal">
|
|
41
15
|
<div class="pa-modal__backdrop"></div>
|
|
42
16
|
<div class="pa-modal__container">
|
|
43
17
|
<div class="pa-modal__header">
|
|
44
18
|
<h3 class="pa-modal__title">Modal Title</h3>
|
|
45
|
-
<button class="pa-btn pa-btn--
|
|
19
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--secondary"
|
|
20
|
+
onclick="closeModal('basicModal')" aria-label="Close">✕</button>
|
|
46
21
|
</div>
|
|
47
22
|
<div class="pa-modal__body">
|
|
48
23
|
<p>Modal body content goes here.</p>
|
|
@@ -54,19 +29,21 @@ HEADER THEME CLASSES (alternative to modal-level themes):
|
|
|
54
29
|
</div>
|
|
55
30
|
</div>
|
|
56
31
|
|
|
57
|
-
<!-- TOP-ALIGNED MODAL -->
|
|
58
32
|
|
|
59
|
-
<!--
|
|
33
|
+
<!-- TOP-ALIGNED MODAL (5vh from top instead of centered) -->
|
|
34
|
+
|
|
60
35
|
<div class="pa-modal pa-modal--top pa-modal--show" id="topModal">
|
|
61
36
|
<div class="pa-modal__backdrop"></div>
|
|
62
37
|
<div class="pa-modal__container">
|
|
63
38
|
<div class="pa-modal__header">
|
|
64
39
|
<h3 class="pa-modal__title">Top-Aligned Modal</h3>
|
|
65
|
-
<button class="pa-btn pa-btn--
|
|
40
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--secondary"
|
|
41
|
+
onclick="closeModal('topModal')" aria-label="Close">✕</button>
|
|
66
42
|
</div>
|
|
67
43
|
<div class="pa-modal__body">
|
|
68
|
-
<p>
|
|
69
|
-
|
|
44
|
+
<p>Appears near the top of the viewport. Useful for search
|
|
45
|
+
interfaces, quick-action panels, command palette-style
|
|
46
|
+
overlays.</p>
|
|
70
47
|
</div>
|
|
71
48
|
<div class="pa-modal__footer">
|
|
72
49
|
<button class="pa-btn pa-btn--secondary" onclick="closeModal('topModal')">Close</button>
|
|
@@ -76,175 +53,172 @@ HEADER THEME CLASSES (alternative to modal-level themes):
|
|
|
76
53
|
</div>
|
|
77
54
|
|
|
78
55
|
|
|
79
|
-
<!-- STATIC MODAL (
|
|
56
|
+
<!-- STATIC MODAL (JS-contract, no CSS) -->
|
|
80
57
|
|
|
81
58
|
<!--
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Use for: license
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
59
|
+
`pa-modal--static` is a JS hook, not a CSS rule. The SCSS has no
|
|
60
|
+
.pa-modal--static selector; the framework's example JS below checks
|
|
61
|
+
classList.contains('pa-modal--static') before closing, so backdrop
|
|
62
|
+
clicks and ESC become no-ops.
|
|
63
|
+
|
|
64
|
+
Use for: license acceptances, destructive confirmations that require
|
|
65
|
+
an explicit yes/no, onboarding steps the user shouldn't dismiss by
|
|
66
|
+
accident.
|
|
67
|
+
|
|
68
|
+
- Add .pa-modal--static on the root
|
|
69
|
+
- Don't add an onclick to __backdrop (or your JS will handle it)
|
|
70
|
+
- Usually omit the header close button — force the footer choice
|
|
94
71
|
-->
|
|
95
72
|
|
|
96
73
|
<div class="pa-modal pa-modal--static" id="staticModal">
|
|
97
|
-
<div class="pa-modal__backdrop"></div
|
|
74
|
+
<div class="pa-modal__backdrop"></div>
|
|
98
75
|
<div class="pa-modal__container pa-modal__container--sm">
|
|
99
|
-
|
|
76
|
+
<!-- Theming lives on the root .pa-modal--{variant}; the header
|
|
77
|
+
picks up colour via descendant selector. No --warning modifier
|
|
78
|
+
on the header itself. -->
|
|
79
|
+
<div class="pa-modal__header">
|
|
100
80
|
<h3 class="pa-modal__title">License Agreement</h3>
|
|
101
|
-
<!-- No X close button - user must use footer buttons -->
|
|
102
81
|
</div>
|
|
103
82
|
<div class="pa-modal__body">
|
|
104
83
|
<p>You must accept the terms to continue.</p>
|
|
105
|
-
<p>This modal cannot be dismissed with
|
|
84
|
+
<p>This modal cannot be dismissed with Esc or by clicking outside.</p>
|
|
106
85
|
</div>
|
|
107
86
|
<div class="pa-modal__footer">
|
|
108
87
|
<button class="pa-btn pa-btn--secondary" onclick="closeModal('staticModal')">Decline</button>
|
|
109
|
-
<button class="pa-btn pa-btn--warning"
|
|
88
|
+
<button class="pa-btn pa-btn--warning" onclick="closeModal('staticModal')">Accept</button>
|
|
110
89
|
</div>
|
|
111
90
|
</div>
|
|
112
91
|
</div>
|
|
113
92
|
|
|
114
93
|
|
|
115
|
-
<!-- MODAL SIZES -->
|
|
94
|
+
<!-- MODAL SIZES (on __container) -->
|
|
116
95
|
|
|
117
|
-
<!-- Small Modal -->
|
|
118
96
|
<div class="pa-modal" id="smallModal">
|
|
119
97
|
<div class="pa-modal__backdrop"></div>
|
|
120
98
|
<div class="pa-modal__container pa-modal__container--sm">
|
|
121
|
-
<!--
|
|
99
|
+
<!-- 32rem / 320px -->
|
|
122
100
|
</div>
|
|
123
101
|
</div>
|
|
124
102
|
|
|
125
|
-
<!-- Medium Modal (Default) -->
|
|
126
103
|
<div class="pa-modal" id="mediumModal">
|
|
127
104
|
<div class="pa-modal__backdrop"></div>
|
|
128
105
|
<div class="pa-modal__container">
|
|
129
|
-
<!--
|
|
106
|
+
<!-- Default: 48rem / 480px -->
|
|
130
107
|
</div>
|
|
131
108
|
</div>
|
|
132
109
|
|
|
133
|
-
<!-- Large Modal -->
|
|
134
110
|
<div class="pa-modal" id="largeModal">
|
|
135
111
|
<div class="pa-modal__backdrop"></div>
|
|
136
112
|
<div class="pa-modal__container pa-modal__container--lg">
|
|
137
|
-
<!--
|
|
113
|
+
<!-- 80rem / 800px -->
|
|
138
114
|
</div>
|
|
139
115
|
</div>
|
|
140
116
|
|
|
141
|
-
<!-- Extra Large Modal -->
|
|
142
117
|
<div class="pa-modal" id="xlModal">
|
|
143
118
|
<div class="pa-modal__backdrop"></div>
|
|
144
119
|
<div class="pa-modal__container pa-modal__container--xl">
|
|
145
|
-
<!--
|
|
120
|
+
<!-- 112rem / 1120px -->
|
|
146
121
|
</div>
|
|
147
122
|
</div>
|
|
148
123
|
|
|
149
|
-
<!-- 2XL Modal -->
|
|
150
124
|
<div class="pa-modal" id="xxlModal">
|
|
151
125
|
<div class="pa-modal__backdrop"></div>
|
|
152
126
|
<div class="pa-modal__container pa-modal__container--xxl">
|
|
153
|
-
<!--
|
|
127
|
+
<!-- 144rem / 1440px -->
|
|
154
128
|
</div>
|
|
155
129
|
</div>
|
|
156
130
|
|
|
157
|
-
<!-- Full
|
|
131
|
+
<!-- Full-width: viewport minus $modal-fw-margin (1.6rem default) on every side -->
|
|
158
132
|
<div class="pa-modal" id="fwModal">
|
|
159
133
|
<div class="pa-modal__backdrop"></div>
|
|
160
134
|
<div class="pa-modal__container pa-modal__container--fw">
|
|
161
|
-
<!--
|
|
135
|
+
<!-- Fills viewport with a small gutter -->
|
|
162
136
|
</div>
|
|
163
137
|
</div>
|
|
164
138
|
|
|
165
139
|
|
|
166
|
-
<!-- THEMED MODALS -->
|
|
140
|
+
<!-- THEMED MODALS (colored header) -->
|
|
141
|
+
|
|
142
|
+
<!--
|
|
143
|
+
Theming is applied at the ROOT level (.pa-modal--{variant}) and
|
|
144
|
+
cascades into the header via descendant selector. There's no
|
|
145
|
+
pa-modal__header--{variant} class in SCSS.
|
|
146
|
+
Available variants: primary, success, warning, danger.
|
|
147
|
+
(No --info, --secondary, --light, --dark on modals.)
|
|
148
|
+
-->
|
|
167
149
|
|
|
168
|
-
<!-- Primary Modal -->
|
|
169
150
|
<div class="pa-modal pa-modal--primary" id="primaryModal">
|
|
170
151
|
<div class="pa-modal__backdrop"></div>
|
|
171
152
|
<div class="pa-modal__container">
|
|
172
153
|
<div class="pa-modal__header">
|
|
173
154
|
<h3 class="pa-modal__title">Primary Modal</h3>
|
|
174
|
-
<button class="pa-btn pa-btn--
|
|
175
|
-
</div>
|
|
176
|
-
<div class="pa-modal__body">
|
|
177
|
-
Content here.
|
|
155
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--light" aria-label="Close">✕</button>
|
|
178
156
|
</div>
|
|
157
|
+
<div class="pa-modal__body">Body content.</div>
|
|
179
158
|
</div>
|
|
180
159
|
</div>
|
|
181
160
|
|
|
182
|
-
<!-- Success Modal -->
|
|
183
161
|
<div class="pa-modal pa-modal--success" id="successModal">
|
|
184
162
|
<div class="pa-modal__backdrop"></div>
|
|
185
163
|
<div class="pa-modal__container">
|
|
186
164
|
<div class="pa-modal__header">
|
|
187
165
|
<h3 class="pa-modal__title">Success Modal</h3>
|
|
188
|
-
<button class="pa-btn pa-btn--
|
|
189
|
-
</div>
|
|
190
|
-
<div class="pa-modal__body">
|
|
191
|
-
Content here.
|
|
166
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--light" aria-label="Close">✕</button>
|
|
192
167
|
</div>
|
|
168
|
+
<div class="pa-modal__body">Body content.</div>
|
|
193
169
|
</div>
|
|
194
170
|
</div>
|
|
195
171
|
|
|
196
|
-
<!-- Warning Modal -->
|
|
197
172
|
<div class="pa-modal pa-modal--warning" id="warningModal">
|
|
198
173
|
<div class="pa-modal__backdrop"></div>
|
|
199
174
|
<div class="pa-modal__container">
|
|
200
175
|
<div class="pa-modal__header">
|
|
201
176
|
<h3 class="pa-modal__title">Warning Modal</h3>
|
|
202
|
-
<button class="pa-btn pa-btn--
|
|
203
|
-
</div>
|
|
204
|
-
<div class="pa-modal__body">
|
|
205
|
-
Content here.
|
|
177
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--light" aria-label="Close">✕</button>
|
|
206
178
|
</div>
|
|
179
|
+
<div class="pa-modal__body">Body content.</div>
|
|
207
180
|
</div>
|
|
208
181
|
</div>
|
|
209
182
|
|
|
210
|
-
<!-- Danger Modal -->
|
|
211
183
|
<div class="pa-modal pa-modal--danger" id="dangerModal">
|
|
212
184
|
<div class="pa-modal__backdrop"></div>
|
|
213
185
|
<div class="pa-modal__container">
|
|
214
186
|
<div class="pa-modal__header">
|
|
215
187
|
<h3 class="pa-modal__title">Danger Modal</h3>
|
|
216
|
-
<button class="pa-btn pa-btn--
|
|
217
|
-
</div>
|
|
218
|
-
<div class="pa-modal__body">
|
|
219
|
-
Content here.
|
|
188
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--light" aria-label="Close">✕</button>
|
|
220
189
|
</div>
|
|
190
|
+
<div class="pa-modal__body">Body content.</div>
|
|
221
191
|
</div>
|
|
222
192
|
</div>
|
|
223
193
|
|
|
224
194
|
|
|
225
|
-
<!-- SCROLLABLE
|
|
195
|
+
<!-- SCROLLABLE BODY -->
|
|
196
|
+
|
|
197
|
+
<!--
|
|
198
|
+
The default __body already scrolls — it has overflow-y: auto, and the
|
|
199
|
+
container caps at max-height: 90vh. For most cases you don't need
|
|
200
|
+
--scrollable.
|
|
201
|
+
|
|
202
|
+
Use --scrollable when you want an EXPLICIT shorter max-height on the
|
|
203
|
+
body (default is 60vh). This keeps the footer visible even when the
|
|
204
|
+
viewport is very tall, so Accept / Decline stay on-screen.
|
|
205
|
+
-->
|
|
226
206
|
|
|
227
|
-
<!-- Modal with Scrollable Content -->
|
|
228
207
|
<div class="pa-modal" id="scrollableModal">
|
|
229
208
|
<div class="pa-modal__backdrop"></div>
|
|
230
209
|
<div class="pa-modal__container pa-modal__container--lg">
|
|
231
210
|
<div class="pa-modal__header">
|
|
232
211
|
<h3 class="pa-modal__title">Terms and Conditions</h3>
|
|
233
|
-
<button class="pa-btn pa-btn--
|
|
212
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--secondary" aria-label="Close">✕</button>
|
|
234
213
|
</div>
|
|
235
214
|
<div class="pa-modal__body pa-modal__body--scrollable">
|
|
236
|
-
<!-- Long content that requires scrolling -->
|
|
237
215
|
<h4>1. Introduction</h4>
|
|
238
|
-
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
|
216
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
239
217
|
<h4>2. Terms of Service</h4>
|
|
240
|
-
<p>Ut enim ad minim veniam, quis nostrud exercitation
|
|
218
|
+
<p>Ut enim ad minim veniam, quis nostrud exercitation.</p>
|
|
241
219
|
<h4>3. Privacy Policy</h4>
|
|
242
|
-
<p>Duis aute irure dolor in reprehenderit in voluptate
|
|
243
|
-
|
|
244
|
-
<p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
|
245
|
-
<h4>5. Disclaimer</h4>
|
|
246
|
-
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.</p>
|
|
247
|
-
<!-- More content... -->
|
|
220
|
+
<p>Duis aute irure dolor in reprehenderit in voluptate.</p>
|
|
221
|
+
<!-- … more content … -->
|
|
248
222
|
</div>
|
|
249
223
|
<div class="pa-modal__footer">
|
|
250
224
|
<button class="pa-btn pa-btn--secondary">Decline</button>
|
|
@@ -253,34 +227,26 @@ Implementation:
|
|
|
253
227
|
</div>
|
|
254
228
|
</div>
|
|
255
229
|
|
|
256
|
-
<!--
|
|
257
|
-
SCROLLABLE BODY NOTES:
|
|
258
|
-
- Use pa-modal__body--scrollable for content that may exceed viewport height
|
|
259
|
-
- Body gets max-height and overflow-y: auto
|
|
260
|
-
- Header and footer remain fixed, only body scrolls
|
|
261
|
-
- Recommended for: Terms of service, long forms, data tables, file lists
|
|
262
|
-
-->
|
|
263
230
|
|
|
231
|
+
<!-- MODAL WITH A FORM -->
|
|
264
232
|
|
|
265
|
-
<!-- MODAL WITH FORM -->
|
|
266
|
-
|
|
267
|
-
<!-- Form Modal -->
|
|
268
233
|
<div class="pa-modal" id="formModal">
|
|
269
234
|
<div class="pa-modal__backdrop"></div>
|
|
270
235
|
<div class="pa-modal__container">
|
|
271
236
|
<div class="pa-modal__header">
|
|
272
237
|
<h3 class="pa-modal__title">User Information</h3>
|
|
273
|
-
<button class="pa-btn pa-btn--
|
|
238
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--secondary" aria-label="Close">✕</button>
|
|
274
239
|
</div>
|
|
275
240
|
<div class="pa-modal__body">
|
|
276
|
-
|
|
241
|
+
<!-- Labels auto-style inside .pa-form .pa-form-group; no .pa-form-label class needed. -->
|
|
242
|
+
<form class="pa-form">
|
|
277
243
|
<div class="pa-form-group">
|
|
278
|
-
<label
|
|
279
|
-
<input type="text" class="pa-input">
|
|
244
|
+
<label for="name">Name</label>
|
|
245
|
+
<input type="text" id="name" class="pa-input">
|
|
280
246
|
</div>
|
|
281
247
|
<div class="pa-form-group">
|
|
282
|
-
<label
|
|
283
|
-
<input type="email" class="pa-input">
|
|
248
|
+
<label for="email">Email</label>
|
|
249
|
+
<input type="email" id="email" class="pa-input">
|
|
284
250
|
</div>
|
|
285
251
|
</form>
|
|
286
252
|
</div>
|
|
@@ -292,58 +258,162 @@ SCROLLABLE BODY NOTES:
|
|
|
292
258
|
</div>
|
|
293
259
|
|
|
294
260
|
|
|
295
|
-
<!--
|
|
261
|
+
<!-- ================================
|
|
262
|
+
JAVASCRIPT — open/close with scrollbar-gutter compensation
|
|
263
|
+
================================ -->
|
|
264
|
+
|
|
296
265
|
<script>
|
|
297
266
|
function openModal(modalId) {
|
|
298
267
|
const modal = document.getElementById(modalId);
|
|
299
|
-
if (modal)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
document.body.style.paddingRight = scrollbarWidth + 'px'; // Compensate for scrollbar
|
|
306
|
-
}
|
|
268
|
+
if (!modal) return;
|
|
269
|
+
// Prevent layout shift when the vertical scrollbar disappears.
|
|
270
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
271
|
+
modal.classList.add('pa-modal--show');
|
|
272
|
+
document.body.style.overflow = 'hidden';
|
|
273
|
+
document.body.style.paddingRight = scrollbarWidth + 'px';
|
|
307
274
|
}
|
|
308
275
|
|
|
309
276
|
function closeModal(modalId) {
|
|
310
277
|
const modal = document.getElementById(modalId);
|
|
311
|
-
if (modal)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
278
|
+
if (!modal) return;
|
|
279
|
+
modal.classList.remove('pa-modal--show');
|
|
280
|
+
document.body.style.overflow = '';
|
|
281
|
+
document.body.style.paddingRight = '';
|
|
316
282
|
}
|
|
317
283
|
|
|
318
|
-
//
|
|
319
|
-
document.addEventListener('keydown',
|
|
284
|
+
// Esc closes the topmost open modal, unless it's --static.
|
|
285
|
+
document.addEventListener('keydown', (e) => {
|
|
320
286
|
if (e.key === 'Escape') {
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
closeModal(openModal.id);
|
|
287
|
+
const open = document.querySelector('.pa-modal--show');
|
|
288
|
+
if (open && !open.classList.contains('pa-modal--static')) {
|
|
289
|
+
closeModal(open.id);
|
|
325
290
|
}
|
|
326
291
|
}
|
|
327
292
|
});
|
|
328
293
|
|
|
329
|
-
//
|
|
294
|
+
// Backdrop click closes the modal, unless it's --static.
|
|
330
295
|
document.querySelectorAll('.pa-modal__backdrop').forEach(backdrop => {
|
|
331
|
-
backdrop.addEventListener('click', function() {
|
|
296
|
+
backdrop.addEventListener('click', function () {
|
|
332
297
|
const modal = this.closest('.pa-modal');
|
|
333
|
-
// Don't close static modals on backdrop click
|
|
334
298
|
if (modal && !modal.classList.contains('pa-modal--static')) {
|
|
335
299
|
closeModal(modal.id);
|
|
336
300
|
}
|
|
337
301
|
});
|
|
338
302
|
});
|
|
339
|
-
|
|
340
|
-
// Close modal when clicking close button
|
|
341
|
-
document.querySelectorAll('.pa-modal .pa-btn--icon-only').forEach(btn => {
|
|
342
|
-
btn.addEventListener('click', function() {
|
|
343
|
-
const modal = this.closest('.pa-modal');
|
|
344
|
-
if (modal) {
|
|
345
|
-
closeModal(modal.id);
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
303
|
</script>
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
<!-- ================================
|
|
307
|
+
COMPONENT REFERENCE
|
|
308
|
+
================================ -->
|
|
309
|
+
|
|
310
|
+
<!--
|
|
311
|
+
WRAPPER:
|
|
312
|
+
- .pa-modal Fixed-inset root; display: none by default.
|
|
313
|
+
- .pa-modal--show Visible state (display: flex, centred by default).
|
|
314
|
+
- .pa-modal--top Align container near the top (5vh padding-top);
|
|
315
|
+
use for search/command-palette style modals.
|
|
316
|
+
- .pa-modal--static JS contract only — no CSS rule. The example
|
|
317
|
+
JS skips Esc + backdrop dismissal when this
|
|
318
|
+
class is present. Add it on the root.
|
|
319
|
+
|
|
320
|
+
Colour variants (cascade into __header via descendant selector):
|
|
321
|
+
- .pa-modal--primary
|
|
322
|
+
- .pa-modal--success
|
|
323
|
+
- .pa-modal--warning
|
|
324
|
+
- .pa-modal--danger
|
|
325
|
+
(No --info / --secondary / --light / --dark. No pa-modal__header--*
|
|
326
|
+
modifiers — theme lives on the root.)
|
|
327
|
+
|
|
328
|
+
STRUCTURE:
|
|
329
|
+
- .pa-modal__backdrop Full-viewport overlay behind the dialog.
|
|
330
|
+
- .pa-modal__container The dialog card itself.
|
|
331
|
+
- .pa-modal__header Flex row: __title (start) + close button (end).
|
|
332
|
+
- .pa-modal__title <h3>-weight title text.
|
|
333
|
+
- .pa-modal__body Scrollable content region (flex: 1;
|
|
334
|
+
overflow-y: auto — default scrolls when
|
|
335
|
+
content exceeds container max-height).
|
|
336
|
+
- .pa-modal__footer Right-aligned action buttons row
|
|
337
|
+
(justify-content: flex-end; gap).
|
|
338
|
+
|
|
339
|
+
CONTAINER SIZES (on __container):
|
|
340
|
+
- .pa-modal__container--sm 32rem / 320px
|
|
341
|
+
- (default) 48rem / 480px
|
|
342
|
+
- .pa-modal__container--lg 80rem / 800px
|
|
343
|
+
- .pa-modal__container--xl 112rem / 1120px
|
|
344
|
+
- .pa-modal__container--xxl 144rem / 1440px
|
|
345
|
+
- .pa-modal__container--fw Viewport minus $modal-fw-margin on every side
|
|
346
|
+
|
|
347
|
+
BODY MODIFIER:
|
|
348
|
+
- .pa-modal__body--scrollable Explicit max-height: $modal-body-
|
|
349
|
+
scrollable-max-height (60vh default).
|
|
350
|
+
Use when you want a shorter ceiling than
|
|
351
|
+
the container's own max-height (90vh),
|
|
352
|
+
so long content scrolls earlier and
|
|
353
|
+
__footer stays in view.
|
|
354
|
+
|
|
355
|
+
RESPONSIVE:
|
|
356
|
+
- Below $mobile-breakpoint (768px):
|
|
357
|
+
- __container margin drops to $modal-mobile-margin
|
|
358
|
+
- max-height becomes $modal-mobile-max-height
|
|
359
|
+
- --lg / --xl / --xxl all collapse to
|
|
360
|
+
calc(100vw - $spacing-base)
|
|
361
|
+
- --fw uses the same fw-margin with viewport-derived width/height
|
|
362
|
+
|
|
363
|
+
JAVASCRIPT CONTRACT:
|
|
364
|
+
- openModal(id) / closeModal(id) — toggle pa-modal--show on the root
|
|
365
|
+
- Esc handler skips modals with pa-modal--static
|
|
366
|
+
- Backdrop click handler skips modals with pa-modal--static
|
|
367
|
+
- Scrollbar-gutter compensation on open/close prevents layout shift
|
|
368
|
+
(body gets overflow: hidden + padding-right matching scrollbar width).
|
|
369
|
+
|
|
370
|
+
STRUCTURE PATTERNS:
|
|
371
|
+
|
|
372
|
+
Basic modal:
|
|
373
|
+
<div class="pa-modal" id="x">
|
|
374
|
+
<div class="pa-modal__backdrop"></div>
|
|
375
|
+
<div class="pa-modal__container">
|
|
376
|
+
<div class="pa-modal__header">
|
|
377
|
+
<h3 class="pa-modal__title">Title</h3>
|
|
378
|
+
<button class="pa-btn pa-btn--sm pa-btn--icon-only pa-btn--secondary" aria-label="Close">✕</button>
|
|
379
|
+
</div>
|
|
380
|
+
<div class="pa-modal__body">…</div>
|
|
381
|
+
<div class="pa-modal__footer">
|
|
382
|
+
<button class="pa-btn pa-btn--secondary">Cancel</button>
|
|
383
|
+
<button class="pa-btn pa-btn--primary">Save</button>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
Themed modal (colour on the root, not the header):
|
|
389
|
+
<div class="pa-modal pa-modal--danger">
|
|
390
|
+
<div class="pa-modal__backdrop"></div>
|
|
391
|
+
<div class="pa-modal__container">
|
|
392
|
+
<div class="pa-modal__header">
|
|
393
|
+
<h3 class="pa-modal__title">Delete account?</h3>
|
|
394
|
+
</div>
|
|
395
|
+
…
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
Static modal (no Esc/backdrop dismiss; user must choose):
|
|
400
|
+
<div class="pa-modal pa-modal--static">
|
|
401
|
+
<div class="pa-modal__backdrop"></div>
|
|
402
|
+
<div class="pa-modal__container pa-modal__container--sm">
|
|
403
|
+
<div class="pa-modal__header"><h3 class="pa-modal__title">Confirm</h3></div>
|
|
404
|
+
<div class="pa-modal__body">…</div>
|
|
405
|
+
<div class="pa-modal__footer">
|
|
406
|
+
<button class="pa-btn pa-btn--secondary">Decline</button>
|
|
407
|
+
<button class="pa-btn pa-btn--danger">Accept</button>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
|
|
412
|
+
Modal with a form (labels auto-style inside .pa-form .pa-form-group):
|
|
413
|
+
<form class="pa-form">
|
|
414
|
+
<div class="pa-form-group">
|
|
415
|
+
<label for="x">Field</label>
|
|
416
|
+
<input id="x" class="pa-input">
|
|
417
|
+
</div>
|
|
418
|
+
</form>
|
|
419
|
+
-->
|