@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.
Files changed (47) hide show
  1. package/README.md +23 -29
  2. package/dist/css/main.css +68 -148
  3. package/package.json +1 -5
  4. package/snippets/AUDIT.md +94 -0
  5. package/snippets/alerts.html +264 -89
  6. package/snippets/badges.html +193 -61
  7. package/snippets/buttons.html +178 -0
  8. package/snippets/callouts.html +210 -129
  9. package/snippets/cards.html +383 -200
  10. package/snippets/checkbox-lists.html +199 -65
  11. package/snippets/code.html +55 -11
  12. package/snippets/command-palette.html +401 -111
  13. package/snippets/comparison.html +144 -93
  14. package/snippets/customization.html +311 -104
  15. package/snippets/data-display.html +584 -0
  16. package/snippets/detail-panel.html +470 -138
  17. package/snippets/filter-card.html +246 -0
  18. package/snippets/forms.html +408 -308
  19. package/snippets/grid.html +253 -141
  20. package/snippets/layout.html +379 -480
  21. package/snippets/lists.html +144 -47
  22. package/snippets/loaders.html +64 -39
  23. package/snippets/manifest.json +330 -280
  24. package/snippets/modal-dialogs.html +137 -64
  25. package/snippets/modals.html +221 -151
  26. package/snippets/notifications.html +285 -0
  27. package/snippets/popconfirm.html +213 -19
  28. package/snippets/profile.html +290 -330
  29. package/snippets/statistics.html +247 -0
  30. package/snippets/tables.html +359 -150
  31. package/snippets/tabs.html +129 -45
  32. package/snippets/timeline.html +123 -56
  33. package/snippets/toasts.html +179 -31
  34. package/snippets/tooltips.html +199 -81
  35. package/snippets/typography.html +183 -58
  36. package/snippets/utilities.html +511 -415
  37. package/snippets/virtual-scroll.html +201 -75
  38. package/snippets/web-daterangepicker.html +369 -189
  39. package/snippets/web-multiselect.html +360 -124
  40. package/src/scss/core-components/_alerts.scss +51 -12
  41. package/src/scss/core-components/_pagers.scss +1 -1
  42. package/src/scss/core-components/_popconfirm.scss +35 -13
  43. package/src/scss/core-components/_profile.scss +18 -8
  44. package/src/scss/core-components/_statistics.scss +12 -12
  45. package/src/scss/core-components/_tables.scss +2 -134
  46. package/src/scss/variables/_components.scss +17 -2
  47. package/scripts/download-themes.js +0 -351
@@ -0,0 +1,285 @@
1
+ <!-- ================================
2
+ NOTIFICATIONS
3
+ Two pieces under one BEM block:
4
+
5
+ 1. Notification bell + dropdown panel (lives in the navbar)
6
+ 2. Full-page notification list (--page modifier on the list)
7
+
8
+ Source: core-components/_notifications.scss
9
+
10
+ Same z-index as the profile panel ($z-index-profile-panel ≈ 5000),
11
+ so the panel overlays normal content but sits below modal dialogs.
12
+ ================================ -->
13
+
14
+
15
+ <!-- ================================
16
+ 1. BELL + DROPDOWN PANEL
17
+ This is the canonical "bell in the top bar" pattern. The wrapper
18
+ (.pa-notifications) is the positioning anchor; the button has an
19
+ icon + a small red badge counter; the panel toggles via the
20
+ `.is-open` state class.
21
+
22
+ Toggle visibility from JS — there's no hover/focus auto-open
23
+ because the button + panel pair is too easy to accidentally
24
+ trigger by mouse-over.
25
+ ================================ -->
26
+
27
+ <div class="pa-notifications">
28
+
29
+ <button class="pa-notifications__btn" onclick="toggleNotifications()" aria-label="Notifications">
30
+ <span class="pa-notifications__icon">🔔</span>
31
+ <span class="pa-notifications__badge">3</span>
32
+ </button>
33
+
34
+ <div class="pa-notifications__panel" id="notificationsPanel">
35
+
36
+ <div class="pa-notifications__header">
37
+ <h3>Notifications</h3>
38
+ <button class="pa-notifications__mark-read" onclick="markAllRead()">Mark all as read</button>
39
+ </div>
40
+
41
+ <ul class="pa-notifications__list">
42
+
43
+ <li class="pa-notifications__item pa-notifications__item--unread">
44
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--primary">📝</div>
45
+ <div class="pa-notifications__content">
46
+ <h4>New task assigned</h4>
47
+ <p>Review the Q4 report by end of week</p>
48
+ <span class="pa-notifications__time">2 minutes ago</span>
49
+ </div>
50
+ </li>
51
+
52
+ <li class="pa-notifications__item pa-notifications__item--unread">
53
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--success">✓</div>
54
+ <div class="pa-notifications__content">
55
+ <h4>Build completed</h4>
56
+ <p>Production deployment finished successfully</p>
57
+ <span class="pa-notifications__time">15 minutes ago</span>
58
+ </div>
59
+ </li>
60
+
61
+ <li class="pa-notifications__item pa-notifications__item--unread">
62
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--warning">⚠️</div>
63
+ <div class="pa-notifications__content">
64
+ <h4>Server maintenance</h4>
65
+ <p>Scheduled downtime tonight at 11 PM</p>
66
+ <span class="pa-notifications__time">1 hour ago</span>
67
+ </div>
68
+ </li>
69
+
70
+ <!-- Read item — drops the --unread modifier -->
71
+ <li class="pa-notifications__item">
72
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--secondary">💬</div>
73
+ <div class="pa-notifications__content">
74
+ <h4>New comment</h4>
75
+ <p>Sarah replied to your comment on Issue #432</p>
76
+ <span class="pa-notifications__time">3 hours ago</span>
77
+ </div>
78
+ </li>
79
+
80
+ </ul>
81
+
82
+ <div class="pa-notifications__footer">
83
+ <a href="/notifications">View all notifications</a>
84
+ </div>
85
+
86
+ </div>
87
+ </div>
88
+
89
+ <script>
90
+ function toggleNotifications() {
91
+ document.getElementById('notificationsPanel').classList.toggle('is-open');
92
+ }
93
+ function markAllRead() {
94
+ document.querySelectorAll('.pa-notifications__item--unread')
95
+ .forEach(el => el.classList.remove('pa-notifications__item--unread'));
96
+ // Reset the bell badge counter or hide it entirely
97
+ const badge = document.querySelector('.pa-notifications__badge');
98
+ if (badge) badge.style.display = 'none';
99
+ }
100
+
101
+ // Close on outside click (typical pattern; not provided by SCSS):
102
+ document.addEventListener('click', (e) => {
103
+ const wrap = document.querySelector('.pa-notifications');
104
+ if (!wrap.contains(e.target)) {
105
+ document.getElementById('notificationsPanel').classList.remove('is-open');
106
+ }
107
+ });
108
+ </script>
109
+
110
+
111
+ <!-- ================================
112
+ ICON-WRAPPER COLOUR VARIANTS
113
+ Five colours. Note: there's no --info variant on the icon
114
+ wrapper — defaults to the accent colour if you omit the
115
+ modifier entirely. For an info-coloured slot, fall back to
116
+ the default (no modifier).
117
+ ================================ -->
118
+
119
+ <div class="pa-notifications__icon-wrapper"> 📌</div>
120
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--primary"> 📌</div>
121
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--success"> 📌</div>
122
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--warning"> 📌</div>
123
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--danger"> 📌</div>
124
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--secondary"> 📌</div>
125
+
126
+
127
+ <!-- ================================
128
+ 2. PAGE VIEW (--page list modifier)
129
+ A full-page notification list. Same building blocks; the list
130
+ gets a --page modifier that scales padding/icon/font up and
131
+ drops the max-height + scrolling so the list flows with the
132
+ page.
133
+
134
+ Items support an optional .pa-notifications__actions slot that
135
+ hover-reveals action buttons on desktop, and is always visible
136
+ on mobile (wrapping to a new row).
137
+
138
+ A leading .pa-checkbox is a typical addition for bulk actions.
139
+ ================================ -->
140
+
141
+ <div class="pa-card">
142
+ <div class="pa-card__header">
143
+ <h3>All Notifications</h3>
144
+ </div>
145
+ <div class="pa-card__body p-0">
146
+ <ul class="pa-notifications__list pa-notifications__list--page">
147
+
148
+ <li class="pa-notifications__item pa-notifications__item--unread">
149
+ <label class="pa-checkbox">
150
+ <input type="checkbox">
151
+ <span class="pa-checkbox__box"></span>
152
+ </label>
153
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--primary">👤</div>
154
+ <div class="pa-notifications__content">
155
+ <h4>New user registration</h4>
156
+ <p>John Doe has registered a new account and is awaiting approval.</p>
157
+ <span class="pa-notifications__time">2 minutes ago</span>
158
+ </div>
159
+ <div class="pa-notifications__actions">
160
+ <button class="pa-btn pa-btn--ghost pa-btn--sm" title="Mark as read">✓</button>
161
+ <button class="pa-btn pa-btn--ghost pa-btn--sm" title="Delete">✕</button>
162
+ </div>
163
+ </li>
164
+
165
+ <li class="pa-notifications__item">
166
+ <label class="pa-checkbox">
167
+ <input type="checkbox">
168
+ <span class="pa-checkbox__box"></span>
169
+ </label>
170
+ <div class="pa-notifications__icon-wrapper pa-notifications__icon-wrapper--warning">⚠️</div>
171
+ <div class="pa-notifications__content">
172
+ <h4>High CPU usage</h4>
173
+ <p>Server CPU usage has exceeded 85%. Consider scaling resources.</p>
174
+ <span class="pa-notifications__time">15 minutes ago</span>
175
+ </div>
176
+ <div class="pa-notifications__actions">
177
+ <button class="pa-btn pa-btn--ghost pa-btn--sm" title="Mark as read">✓</button>
178
+ <button class="pa-btn pa-btn--ghost pa-btn--sm" title="Delete">✕</button>
179
+ </div>
180
+ </li>
181
+
182
+ </ul>
183
+ </div>
184
+ </div>
185
+
186
+
187
+ <!-- ================================
188
+ COMPONENT REFERENCE
189
+ ================================ -->
190
+
191
+ <!--
192
+ SUB-ELEMENTS:
193
+
194
+ Bell button + panel anchor
195
+ .pa-notifications Wrapper (positioning anchor for
196
+ the absolutely-positioned panel).
197
+ .pa-notifications__btn Round bell button (no border).
198
+ .pa-notifications__icon The bell glyph itself.
199
+ .pa-notifications__badge Red counter pill, top-end of btn.
200
+
201
+ Dropdown panel
202
+ .pa-notifications__panel Floating panel. display:none until
203
+ you add `.is-open`. Uses
204
+ z-index-profile-panel (= 5000),
205
+ same as the profile panel.
206
+ .pa-notifications__header Header row inside the panel.
207
+ .pa-notifications__mark-read Right-aligned text button in header.
208
+ .pa-notifications__list <ul> wrapping items.
209
+ .pa-notifications__item <li>; flex row with icon + content.
210
+ .pa-notifications__icon-wrapper Round icon tile on the inline-start.
211
+ .pa-notifications__content Title (h4) + body (p) + time.
212
+ .pa-notifications__time Small muted timestamp.
213
+ .pa-notifications__footer Centered link row at the bottom.
214
+
215
+ ITEM STATE:
216
+ --unread Lighter accent-tinted background. Hover deepens it.
217
+ (no class) Read; default background, accent-tint on hover only.
218
+
219
+ ICON-WRAPPER COLOURS:
220
+ (no modifier) accent (--pa-accent)
221
+ --primary
222
+ --success
223
+ --warning
224
+ --danger
225
+ --secondary
226
+ Note: there is NO --info variant; use the default (no modifier) for
227
+ the accent slot.
228
+
229
+ PAGE-VIEW MODIFIER:
230
+ .pa-notifications__list--page
231
+ - Removes the panel max-height + overflow.
232
+ - Bigger padding, larger icon (4rem vs 3.2rem), larger text.
233
+ - Pairs with .pa-notifications__actions inside each item:
234
+ opacity: 0; reveals on .pa-notifications__item:hover
235
+ on mobile: opacity: 1 always, wraps to a new row with a
236
+ top divider.
237
+
238
+ PANEL OPEN/CLOSE:
239
+ No SCSS animation — `display: none` ↔ `display: flex` via .is-open.
240
+ Three things you'll wire up yourself:
241
+ 1. Click the bell → toggle .is-open on .pa-notifications__panel
242
+ 2. Click outside → remove .is-open
243
+ 3. Mark all read → strip --unread from items + zero/hide badge
244
+
245
+ Common alternative: replace .is-open toggle with the `hidden`
246
+ attribute on the panel — works as long as you also add
247
+ `&[hidden] { display: none !important; }` somewhere, since the
248
+ base rule already forces `display: none`.
249
+
250
+ RESPONSIVE BEHAVIOUR (panel only, ≤ $mobile-breakpoint):
251
+ Width fills the viewport (minus 3.2rem) up to a 35.2rem cap.
252
+ Aligned to the inline-end edge with -$spacing-base offset.
253
+ Page-view items: actions always visible, wrapping to a new row
254
+ with a top border separator.
255
+
256
+ STRUCTURE PATTERNS:
257
+
258
+ Bell + dropdown (the navbar pattern):
259
+ <div class="pa-notifications">
260
+ <button class="pa-notifications__btn">
261
+ <span class="pa-notifications__icon">🔔</span>
262
+ <span class="pa-notifications__badge">3</span>
263
+ </button>
264
+ <div class="pa-notifications__panel">
265
+ <div class="pa-notifications__header">…</div>
266
+ <ul class="pa-notifications__list">
267
+ <li class="pa-notifications__item pa-notifications__item--unread">
268
+ <div class="pa-notifications__icon-wrapper …">…</div>
269
+ <div class="pa-notifications__content">…</div>
270
+ </li>
271
+ </ul>
272
+ <div class="pa-notifications__footer">…</div>
273
+ </div>
274
+ </div>
275
+
276
+ Full-page list:
277
+ <ul class="pa-notifications__list pa-notifications__list--page">
278
+ <li class="pa-notifications__item …">
279
+ <label class="pa-checkbox">…</label> <!-- bulk select -->
280
+ <div class="pa-notifications__icon-wrapper …">…</div>
281
+ <div class="pa-notifications__content">…</div>
282
+ <div class="pa-notifications__actions">…</div> <!-- hover-revealed -->
283
+ </li>
284
+ </ul>
285
+ -->
@@ -15,11 +15,11 @@ BENEFITS OVER MODALS:
15
15
  - Better for quick decisions
16
16
  - Ideal for table row actions
17
17
 
18
- POSITION CLASSES:
18
+ POSITION CLASSES (logical — mirror in RTL):
19
19
  - pa-popconfirm--bottom: Arrow on top, popconfirm below button (default)
20
- - pa-popconfirm--top: Arrow on bottom, popconfirm above button
21
- - pa-popconfirm--right: Arrow on left, popconfirm to the right
22
- - pa-popconfirm--left: Arrow on right, popconfirm to the left
20
+ - pa-popconfirm--top: Arrow on bottom, popconfirm above button
21
+ - pa-popconfirm--end: Popconfirm on inline-end side (right in LTR, left in RTL)
22
+ - pa-popconfirm--start: Popconfirm on inline-start side (left in LTR, right in RTL)
23
23
 
24
24
  SIZE VARIANTS:
25
25
  - pa-popconfirm--compact: Smaller padding and dimensions
@@ -102,6 +102,98 @@ JAVASCRIPT REQUIRED:
102
102
  </div>
103
103
 
104
104
 
105
+ <!-- ICON VARIANTS -->
106
+
107
+ <!--
108
+ Note: the base __icon class (no modifier) already prepends ⚠️ via a
109
+ ::before, so <div class="…__message …__icon"> gives you a warning icon
110
+ for free. Use --danger/--warning/--info to swap the emoji.
111
+ -->
112
+
113
+ <!-- Default icon (warning emoji via base __icon) -->
114
+ <div id="popconfirm-default-icon" class="pa-popconfirm pa-popconfirm--bottom">
115
+ <div class="pa-popconfirm__arrow"></div>
116
+ <div class="pa-popconfirm__content">
117
+ <div class="pa-popconfirm__message pa-popconfirm__icon">
118
+ <p>Heads up — this will affect all members of the group.</p>
119
+ </div>
120
+ <div class="pa-popconfirm__actions">
121
+ <button class="pa-btn pa-btn--secondary" onclick="closePopconfirm('popconfirm-default-icon')">Cancel</button>
122
+ <button class="pa-btn pa-btn--primary" onclick="confirmAction('popconfirm-default-icon')">Proceed</button>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <!-- Warning icon (same emoji as default, but explicit) -->
128
+ <div id="popconfirm-warning" class="pa-popconfirm pa-popconfirm--bottom">
129
+ <div class="pa-popconfirm__arrow"></div>
130
+ <div class="pa-popconfirm__content">
131
+ <div class="pa-popconfirm__message pa-popconfirm__icon pa-popconfirm__icon--warning">
132
+ <p>Archive this item? It will be moved to the archive folder.</p>
133
+ </div>
134
+ <div class="pa-popconfirm__actions">
135
+ <button class="pa-btn pa-btn--secondary" onclick="closePopconfirm('popconfirm-warning')">Cancel</button>
136
+ <button class="pa-btn pa-btn--warning" onclick="confirmAction('popconfirm-warning')">Archive</button>
137
+ </div>
138
+ </div>
139
+ </div>
140
+
141
+ <!-- Info icon -->
142
+ <div id="popconfirm-info" class="pa-popconfirm pa-popconfirm--bottom">
143
+ <div class="pa-popconfirm__arrow"></div>
144
+ <div class="pa-popconfirm__content">
145
+ <div class="pa-popconfirm__message pa-popconfirm__icon pa-popconfirm__icon--info">
146
+ <p>Reset all settings to their default values?</p>
147
+ </div>
148
+ <div class="pa-popconfirm__actions">
149
+ <button class="pa-btn pa-btn--secondary" onclick="closePopconfirm('popconfirm-info')">Cancel</button>
150
+ <button class="pa-btn pa-btn--primary" onclick="confirmAction('popconfirm-info')">Reset</button>
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+
156
+ <!-- POSITION VARIANTS -->
157
+
158
+ <!--
159
+ The position class is the *initial* placement. The JS below uses Floating
160
+ UI, which runs flip() collision detection on open and swaps the class to
161
+ the actual resolved side. So even if you author with --bottom, users near
162
+ the bottom of the viewport will see --top at runtime — the stylesheet
163
+ supports all four sides.
164
+ -->
165
+
166
+ <!-- Top placement (popconfirm appears above trigger) -->
167
+ <div id="popconfirm-top" class="pa-popconfirm pa-popconfirm--top">
168
+ <div class="pa-popconfirm__arrow"></div>
169
+ <div class="pa-popconfirm__content">
170
+ <div class="pa-popconfirm__message">
171
+ <p>Replace the current selection?</p>
172
+ </div>
173
+ <div class="pa-popconfirm__actions">
174
+ <button class="pa-btn pa-btn--secondary" onclick="closePopconfirm('popconfirm-top')">No</button>
175
+ <button class="pa-btn pa-btn--primary" onclick="confirmAction('popconfirm-top')">Yes</button>
176
+ </div>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- End placement (popconfirm appears on the trigger's inline-end side: right in LTR, left in RTL) -->
181
+ <div id="popconfirm-end" class="pa-popconfirm pa-popconfirm--end pa-popconfirm--compact">
182
+ <div class="pa-popconfirm__arrow"></div>
183
+ <div class="pa-popconfirm__content">
184
+ <div class="pa-popconfirm__message">
185
+ <p>Remove tag?</p>
186
+ </div>
187
+ <div class="pa-popconfirm__actions">
188
+ <button class="pa-btn pa-btn--secondary" onclick="closePopconfirm('popconfirm-end')">No</button>
189
+ <button class="pa-btn pa-btn--danger" onclick="confirmAction('popconfirm-end')">Yes</button>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Pattern: pa-popconfirm--{top|bottom|start|end} (logical sides; mirror in RTL) -->
195
+
196
+
105
197
  <!-- TABLE USAGE EXAMPLE -->
106
198
 
107
199
  <!-- Common pattern: Delete action in table -->
@@ -199,32 +291,45 @@ function confirmAction(popconfirmId) {
199
291
  }
200
292
 
201
293
  async function positionPopconfirm(trigger, popconfirm) {
202
- // Get the initial position class (or default to bottom)
203
- const classMatch = popconfirm.className.match(/pa-popconfirm--(top|bottom|left|right)/);
204
- const placement = classMatch ? classMatch[1] : 'bottom';
294
+ // Popconfirm classes use logical sides (start/end). Floating UI's placement
295
+ // arg is physical (left/right), so translate going in and coming back out.
296
+ const isRtl = document.documentElement.dir === 'rtl';
297
+ const toPhysical = {
298
+ top: 'top',
299
+ bottom: 'bottom',
300
+ start: isRtl ? 'right' : 'left',
301
+ end: isRtl ? 'left' : 'right'
302
+ };
303
+ const toLogical = {
304
+ top: 'top',
305
+ bottom: 'bottom',
306
+ left: isRtl ? 'end' : 'start',
307
+ right: isRtl ? 'start' : 'end'
308
+ };
309
+
310
+ const classMatch = popconfirm.className.match(/pa-popconfirm--(top|bottom|start|end)/);
311
+ const logical = classMatch ? classMatch[1] : 'bottom';
312
+ const placement = toPhysical[logical];
205
313
 
206
- // Use Floating UI for automatic positioning with collision detection
207
314
  const { computePosition, flip, shift, offset } = window.FloatingUIDOM;
208
315
 
209
316
  const { x, y, placement: finalPlacement } = await computePosition(trigger, popconfirm, {
210
317
  placement: placement,
211
318
  middleware: [
212
- offset(8), // 8px gap from trigger
213
- flip(), // Flip to opposite side if not enough space
214
- shift({ padding: 10 }) // Keep 10px from viewport edges
319
+ offset(8),
320
+ flip(),
321
+ shift({ padding: 10 })
215
322
  ]
216
323
  });
217
324
 
218
- // Update position class to match actual placement
325
+ // Map Floating UI's physical answer back to our logical class for the
326
+ // post-flip rewrite (`.split('-')[0]` handles compound forms like `top-start`).
327
+ const finalLogical = toLogical[finalPlacement.split('-')[0]] || 'bottom';
219
328
  popconfirm.className = popconfirm.className
220
- .replace(/pa-popconfirm--(top|bottom|left|right)/g, '')
221
- .trim() + ' pa-popconfirm--' + finalPlacement;
329
+ .replace(/pa-popconfirm--(top|bottom|start|end)/g, '')
330
+ .trim() + ' pa-popconfirm--' + finalLogical;
222
331
 
223
- // Apply positioning
224
- Object.assign(popconfirm.style, {
225
- left: `${x}px`,
226
- top: `${y}px`
227
- });
332
+ Object.assign(popconfirm.style, { left: `${x}px`, top: `${y}px` });
228
333
  }
229
334
 
230
335
  // Close popconfirm when clicking outside
@@ -251,3 +356,92 @@ window.addEventListener('resize', () => {
251
356
  }
252
357
  });
253
358
  </script>
359
+
360
+
361
+ <!-- ================================
362
+ COMPONENT REFERENCE
363
+ ================================ -->
364
+
365
+ <!--
366
+ STRUCTURE (required):
367
+ .pa-popconfirm
368
+ .pa-popconfirm__arrow ← decorative triangle pointing at trigger
369
+ .pa-popconfirm__content ← card surface with border + shadow
370
+ .pa-popconfirm__message ← body copy
371
+ .pa-popconfirm__actions ← button row (right-aligned by default)
372
+
373
+ STATE (note: .is-* class, not a BEM --modifier):
374
+ - .is-open — set by JS to flip display:none → display:block
375
+
376
+ POSITION MODIFIERS (logical — mirror in RTL):
377
+ - .pa-popconfirm--bottom — popconfirm below trigger, arrow on top (default)
378
+ - .pa-popconfirm--top — popconfirm above trigger, arrow on bottom
379
+ - .pa-popconfirm--end — popconfirm on inline-end side (right in LTR, left in RTL)
380
+ - .pa-popconfirm--start — popconfirm on inline-start side (left in LTR, right in RTL)
381
+
382
+ Under `dir="rtl"`, the inline-axis popconfirms (`--start`/`--end`) use the
383
+ same CSS rule as LTR plus a `scaleX(-1)` on the arrow so the triangle still
384
+ points at the trigger. The block-axis popconfirms (`--top`/`--bottom`)
385
+ invert their `translateX` so the arrow stays centered.
386
+
387
+ Runtime note: the JS runs Floating UI's flip() middleware on open and
388
+ rewrites the position class to match where the popconfirm actually fits,
389
+ so the initial class is just a *preferred* side. Floating UI only speaks
390
+ physical placements internally, so the snippet translates `start`/`end`
391
+ ↔ `left`/`right` around the `computePosition` call.
392
+
393
+ SIZE:
394
+ - .pa-popconfirm--compact — tighter padding; min-width drops from 25.6rem to 19.2rem
395
+
396
+ ICON (optional, applied on the __message element):
397
+ - .pa-popconfirm__icon — base icon styling (prepends ⚠️ via ::before)
398
+ - .pa-popconfirm__icon--danger — swaps the emoji to 🗑️
399
+ - .pa-popconfirm__icon--warning — explicit warning (same ⚠️ as base)
400
+ - .pa-popconfirm__icon--info — swaps to ℹ️
401
+
402
+ (The base .pa-popconfirm__icon class alone already shows ⚠️ — the
403
+ --warning modifier just makes the intent explicit. Use --danger for
404
+ destructive actions; the emoji change is the differentiator.)
405
+
406
+ DIMENSIONS:
407
+ - min-width: 25.6rem / max-width: 32rem (default)
408
+ - min-width: 19.2rem (compact)
409
+ - z-index: $z-index-tooltip (9000) — renders above cards, modals' backdrops, etc.
410
+
411
+ JAVASCRIPT CONTRACT:
412
+ - Trigger: toggles .is-open on the target popconfirm element
413
+ - Positioning: Floating UI (loaded globally as window.FloatingUIDOM)
414
+ middleware: offset(8), flip(), shift({ padding: 10 })
415
+ - Dismissal: click outside, close button, or explicit confirm/cancel call
416
+ - The position-class rewrite happens every open + on scroll/resize while open
417
+
418
+ PATTERNS:
419
+
420
+ Basic delete confirm:
421
+ <button onclick="togglePopconfirm(event, 'pc-1')">Delete</button>
422
+ <div id="pc-1" class="pa-popconfirm pa-popconfirm--bottom">
423
+ <div class="pa-popconfirm__arrow"></div>
424
+ <div class="pa-popconfirm__content">
425
+ <div class="pa-popconfirm__message pa-popconfirm__icon pa-popconfirm__icon--danger">
426
+ <p>Delete this item?</p>
427
+ </div>
428
+ <div class="pa-popconfirm__actions">
429
+ <button class="pa-btn pa-btn--secondary" onclick="closePopconfirm('pc-1')">No</button>
430
+ <button class="pa-btn pa-btn--danger" onclick="confirmAction('pc-1')">Yes</button>
431
+ </div>
432
+ </div>
433
+ </div>
434
+
435
+ Compact table-row variant:
436
+ <div class="pa-popconfirm pa-popconfirm--bottom pa-popconfirm--compact"> … </div>
437
+
438
+ Inline-end popconfirm (right-of-trigger in LTR, left-of-trigger in RTL):
439
+ <div class="pa-popconfirm pa-popconfirm--end"> … </div>
440
+
441
+ POPCONFIRM vs MODAL:
442
+ - Popconfirm: anchored to trigger, no backdrop, 1-step Yes/No question,
443
+ cheap to open/close. Use for destructive row actions.
444
+ - Modal: centered, backdrop, multi-field forms, long explanations.
445
+ Use when you need attention or more than a button choice.
446
+ -->
447
+