@keenmate/pure-admin-core 2.4.0 → 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 (44) hide show
  1. package/README.md +11 -6
  2. package/dist/css/main.css +47 -130
  3. package/package.json +1 -1
  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/_tables.scss +2 -134
  44. package/src/scss/variables/_components.scss +17 -2
@@ -1,41 +1,63 @@
1
1
  <!-- ================================
2
2
  PROFILE PANEL SNIPPETS
3
3
  Pure Admin Visual Framework
4
+
5
+ A slide-in side panel anchored to the inline-end edge of the
6
+ viewport. Toggled via JS by swapping a --open modifier on the
7
+ container. Width is driven by two CSS custom properties exposed
8
+ at :root, so utility classes can override without specificity
9
+ battles.
4
10
  ================================ -->
5
11
 
12
+
6
13
  <!-- ================================
7
- PROFILE BUTTON (NAVBAR)
14
+ PROFILE BUTTON (in the navbar)
15
+ Shows the avatar + user name next to the burger/brand; acts as
16
+ the trigger for the panel. Hidden text on mobile (only icon shows).
8
17
  ================================ -->
9
18
 
10
- <!-- Profile Button (goes in header/navbar) -->
11
19
  <button class="pa-header__profile-btn" onclick="toggleProfilePanel()" aria-label="User Profile">
12
20
  <span class="pa-btn__icon">👤</span>
13
21
  <span class="pa-header__profile-name">John Doe</span>
14
22
  </button>
15
23
 
16
- <!-- Profile Button with Font Awesome -->
24
+ <!-- With Font Awesome -->
17
25
  <button class="pa-header__profile-btn" onclick="toggleProfilePanel()" aria-label="User Profile">
18
26
  <span class="pa-btn__icon"><i class="fa-solid fa-user"></i></span>
19
27
  <span class="pa-header__profile-name">John Doe</span>
20
28
  </button>
21
29
 
30
+ <!--
31
+ Responsive: below $mobile-breakpoint (768px), .pa-header__profile-name
32
+ is hidden via `display: none` — the button collapses to just the icon
33
+ to save horizontal space in the navbar.
34
+ -->
35
+
22
36
 
23
37
  <!-- ================================
24
- PROFILE PANEL - BASIC
38
+ BASIC PROFILE PANEL
39
+ Place the panel markup near </body> (outside the layout), because
40
+ it's position: fixed and shouldn't inherit layout container state.
41
+ The `id="profilePanel"` is what the example JS below looks up;
42
+ any id works as long as your toggle/close functions match.
25
43
  ================================ -->
26
44
 
27
- <!-- Complete Profile Panel (place before closing </body> tag) -->
28
- <!-- Note: Use title attributes on name/email for tooltips when text may be truncated -->
29
45
  <div class="pa-profile-panel" id="profilePanel">
46
+ <!-- Click-to-close overlay (fills the viewport behind the content) -->
30
47
  <div class="pa-profile-panel__overlay" onclick="closeProfilePanel()"></div>
48
+
49
+ <!-- The panel body; width is driven by --pa-local-profile-panel-* vars -->
31
50
  <div class="pa-profile-panel__content">
51
+
52
+ <!-- Header: avatar + info + close button -->
32
53
  <div class="pa-profile-panel__header">
33
54
  <div class="pa-profile-panel__avatar">
34
55
  <span class="pa-profile-panel__avatar-icon">👤</span>
35
56
  </div>
36
57
  <div class="pa-profile-panel__info">
37
- <h3 class="pa-profile-panel__name" title="John Doe">John Doe</h3>
38
- <p class="pa-profile-panel__email" title="john.doe@company.com">john.doe@company.com</p>
58
+ <!-- title= attrs give a tooltip when the text truncates with ellipsis -->
59
+ <h3 class="pa-profile-panel__name" title="John Doe">John Doe</h3>
60
+ <p class="pa-profile-panel__email" title="john.doe@company.com">john.doe@company.com</p>
39
61
  <span class="pa-profile-panel__role">Administrator</span>
40
62
  </div>
41
63
  <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">
@@ -43,6 +65,7 @@
43
65
  </button>
44
66
  </div>
45
67
 
68
+ <!-- Body: scrollable; nav items + in-body actions -->
46
69
  <div class="pa-profile-panel__body">
47
70
  <nav class="pa-profile-panel__nav">
48
71
  <ul>
@@ -64,18 +87,15 @@
64
87
  </a></li>
65
88
  <li><a href="#help" class="pa-profile-panel__nav-item">
66
89
  <span class="pa-profile-panel__nav-icon">❓</span>
67
- Help & Support
90
+ Help &amp; Support
68
91
  </a></li>
69
92
  </ul>
70
93
  </nav>
71
94
 
95
+ <!-- Actions inside __body: scroll with the nav above them -->
72
96
  <div class="pa-profile-panel__actions">
73
- <button class="pa-btn pa-btn--secondary pa-btn--block">
74
- Switch Account
75
- </button>
76
- <button class="pa-btn pa-btn--danger pa-btn--block">
77
- Sign Out
78
- </button>
97
+ <button class="pa-btn pa-btn--secondary pa-btn--block">Switch Account</button>
98
+ <button class="pa-btn pa-btn--danger pa-btn--block">Sign Out</button>
79
99
  </div>
80
100
  </div>
81
101
  </div>
@@ -83,76 +103,63 @@
83
103
 
84
104
 
85
105
  <!-- ================================
86
- PROFILE PANEL - WITH FIXED FOOTER
87
- Actions fixed at bottom, content scrolls above
106
+ PANEL WITH A FIXED FOOTER
107
+ Moves the action buttons out of __body into __footer, which sits
108
+ outside the scroll area and stays pinned to the bottom of the
109
+ panel regardless of content length.
110
+
111
+ Rule of thumb:
112
+ - Short nav + actions that shouldn't scroll away → __footer
113
+ - Long nav + related actions at end of the list → __actions
88
114
  ================================ -->
89
115
 
90
116
  <div class="pa-profile-panel" id="profilePanel">
91
117
  <div class="pa-profile-panel__overlay" onclick="closeProfilePanel()"></div>
92
118
  <div class="pa-profile-panel__content">
119
+
93
120
  <div class="pa-profile-panel__header">
94
121
  <div class="pa-profile-panel__avatar">
95
122
  <span class="pa-profile-panel__avatar-icon">👤</span>
96
123
  </div>
97
124
  <div class="pa-profile-panel__info">
98
- <h3 class="pa-profile-panel__name" title="John Doe">John Doe</h3>
99
- <p class="pa-profile-panel__email" title="john.doe@company.com">john.doe@company.com</p>
125
+ <h3 class="pa-profile-panel__name">John Doe</h3>
126
+ <p class="pa-profile-panel__email">john.doe@company.com</p>
100
127
  <span class="pa-profile-panel__role">Administrator</span>
101
128
  </div>
102
- <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">
103
-
104
- </button>
129
+ <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">✕</button>
105
130
  </div>
106
131
 
132
+ <!-- Scrolls -->
107
133
  <div class="pa-profile-panel__body">
108
- <!-- Body content scrolls -->
109
134
  <nav class="pa-profile-panel__nav">
110
135
  <ul>
111
136
  <li><a href="#profile" class="pa-profile-panel__nav-item">
112
- <span class="pa-profile-panel__nav-icon">👤</span>
113
- Profile Settings
137
+ <span class="pa-profile-panel__nav-icon">👤</span> Profile
114
138
  </a></li>
115
139
  <li><a href="#security" class="pa-profile-panel__nav-item">
116
- <span class="pa-profile-panel__nav-icon">🔒</span>
117
- Security
118
- </a></li>
119
- <li><a href="#notifications" class="pa-profile-panel__nav-item">
120
- <span class="pa-profile-panel__nav-icon">🔔</span>
121
- Notifications
122
- </a></li>
123
- <li><a href="#preferences" class="pa-profile-panel__nav-item">
124
- <span class="pa-profile-panel__nav-icon">⚙️</span>
125
- Preferences
126
- </a></li>
127
- <li><a href="#help" class="pa-profile-panel__nav-item">
128
- <span class="pa-profile-panel__nav-icon">❓</span>
129
- Help & Support
140
+ <span class="pa-profile-panel__nav-icon">🔒</span> Security
130
141
  </a></li>
142
+ <!-- … many items … -->
131
143
  </ul>
132
144
  </nav>
133
- <!-- NO __actions here - they go in __footer -->
134
145
  </div>
135
146
 
136
- <!-- Fixed footer - always visible at bottom -->
147
+ <!-- Stays pinned to the bottom of the panel -->
137
148
  <div class="pa-profile-panel__footer">
138
- <button class="pa-btn pa-btn--secondary pa-btn--block">
139
- Switch Account
140
- </button>
141
- <button class="pa-btn pa-btn--danger pa-btn--block">
142
- Sign Out
143
- </button>
149
+ <button class="pa-btn pa-btn--secondary pa-btn--block">Switch Account</button>
150
+ <button class="pa-btn pa-btn--danger pa-btn--block">Sign Out</button>
144
151
  </div>
145
152
  </div>
146
153
  </div>
147
154
 
148
155
 
149
156
  <!-- ================================
150
- PROFILE PANEL - NO AVATAR (CORPORATE)
151
- For apps without user photos
157
+ NO-AVATAR VARIANT (corporate apps)
158
+ Some apps don't have user photos. Add --no-avatar to __header and
159
+ the SCSS hides the avatar element entirely. Keep the avatar markup
160
+ in place so you can re-enable it later by removing the modifier.
152
161
  ================================ -->
153
162
 
154
- <!-- Profile Panel without avatar (for corporate apps that don't have user photos) -->
155
- <!-- Add --no-avatar modifier to header to hide the avatar -->
156
163
  <div class="pa-profile-panel" id="profilePanel">
157
164
  <div class="pa-profile-panel__overlay" onclick="closeProfilePanel()"></div>
158
165
  <div class="pa-profile-panel__content">
@@ -161,25 +168,20 @@
161
168
  <span class="pa-profile-panel__avatar-icon">👤</span>
162
169
  </div>
163
170
  <div class="pa-profile-panel__info">
164
- <h3 class="pa-profile-panel__name" title="John Doe">John Doe</h3>
165
- <p class="pa-profile-panel__email" title="john.doe@company.com">john.doe@company.com</p>
166
- <span class="pa-profile-panel__role">Administrator</span>
171
+ <h3 class="pa-profile-panel__name">Jane Smith</h3>
172
+ <p class="pa-profile-panel__email">jane.smith@corp.example</p>
173
+ <span class="pa-profile-panel__role">Analyst</span>
167
174
  </div>
168
- <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">
169
-
170
- </button>
175
+ <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">✕</button>
171
176
  </div>
172
-
173
177
  <div class="pa-profile-panel__body">
174
178
  <nav class="pa-profile-panel__nav">
175
179
  <ul>
176
180
  <li><a href="#profile" class="pa-profile-panel__nav-item">
177
- <span class="pa-profile-panel__nav-icon">👤</span>
178
- Profile Settings
181
+ <span class="pa-profile-panel__nav-icon">👤</span> Profile
179
182
  </a></li>
180
183
  <li><a href="#logout" class="pa-profile-panel__nav-item">
181
- <span class="pa-profile-panel__nav-icon">🚪</span>
182
- Sign Out
184
+ <span class="pa-profile-panel__nav-icon">🚪</span> Sign Out
183
185
  </a></li>
184
186
  </ul>
185
187
  </nav>
@@ -189,27 +191,28 @@
189
191
 
190
192
 
191
193
  <!-- ================================
192
- PROFILE PANEL - WITH TABS (PROFILE + FAVORITES)
194
+ PANEL WITH TABS (Profile + Favorites)
195
+ Uses the shared .pa-tabs component (see tabs.html for full details).
196
+ Tab buttons carry `data-profile-tab="foo"`, matching panels carry
197
+ `data-profile-panel="foo"`. The JS at the bottom wires up the
198
+ switching by toggling --active on both.
193
199
  ================================ -->
194
200
 
195
201
  <div class="pa-profile-panel" id="profilePanel">
196
202
  <div class="pa-profile-panel__overlay" onclick="closeProfilePanel()"></div>
197
203
  <div class="pa-profile-panel__content">
204
+
198
205
  <div class="pa-profile-panel__header">
199
- <div class="pa-profile-panel__avatar">
200
- <span class="pa-profile-panel__avatar-icon">👤</span>
201
- </div>
206
+ <div class="pa-profile-panel__avatar"><span class="pa-profile-panel__avatar-icon">👤</span></div>
202
207
  <div class="pa-profile-panel__info">
203
208
  <h3 class="pa-profile-panel__name">John Doe</h3>
204
- <p class="pa-profile-panel__email">john.doe@company.com</p>
209
+ <p class="pa-profile-panel__email">john.doe@company.com</p>
205
210
  <span class="pa-profile-panel__role">Administrator</span>
206
211
  </div>
207
- <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">
208
-
209
- </button>
212
+ <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">✕</button>
210
213
  </div>
211
214
 
212
- <!-- Profile Panel Tabs (with text labels) -->
215
+ <!-- Tab strip sits between header and body -->
213
216
  <div class="pa-profile-panel__tabs">
214
217
  <div class="pa-tabs pa-tabs--full">
215
218
  <button class="pa-tabs__item pa-tabs__item--active" data-profile-tab="profile">
@@ -224,47 +227,32 @@
224
227
  </div>
225
228
 
226
229
  <div class="pa-profile-panel__body">
227
- <!-- Profile Tab -->
230
+ <!-- Profile tab -->
228
231
  <div class="pa-tabs__panel pa-tabs__panel--active" data-profile-panel="profile">
229
232
  <nav class="pa-profile-panel__nav">
230
233
  <ul>
231
234
  <li><a href="#profile" class="pa-profile-panel__nav-item">
232
- <span class="pa-profile-panel__nav-icon">👤</span>
233
- Profile Settings
235
+ <span class="pa-profile-panel__nav-icon">👤</span> Profile Settings
234
236
  </a></li>
235
237
  <li><a href="#security" class="pa-profile-panel__nav-item">
236
- <span class="pa-profile-panel__nav-icon">🔒</span>
237
- Security
238
- </a></li>
239
- <li><a href="#notifications" class="pa-profile-panel__nav-item">
240
- <span class="pa-profile-panel__nav-icon">🔔</span>
241
- Notifications
242
- </a></li>
243
- <li><a href="#preferences" class="pa-profile-panel__nav-item">
244
- <span class="pa-profile-panel__nav-icon">⚙️</span>
245
- Preferences
246
- </a></li>
247
- <li><a href="#help" class="pa-profile-panel__nav-item">
248
- <span class="pa-profile-panel__nav-icon">❓</span>
249
- Help & Support
238
+ <span class="pa-profile-panel__nav-icon">🔒</span> Security
250
239
  </a></li>
251
240
  </ul>
252
241
  </nav>
253
-
254
242
  <div class="pa-profile-panel__actions">
255
- <button class="pa-btn pa-btn--secondary pa-btn--block">
256
- Switch Account
257
- </button>
258
- <button class="pa-btn pa-btn--danger pa-btn--block">
259
- Sign Out
260
- </button>
243
+ <button class="pa-btn pa-btn--danger pa-btn--block">Sign Out</button>
261
244
  </div>
262
245
  </div>
263
246
 
264
- <!-- Favorites Tab -->
247
+ <!-- Favorites tab -->
265
248
  <div class="pa-tabs__panel" data-profile-panel="favorites">
266
249
  <div class="pa-profile-panel__favorites">
267
250
  <ul>
251
+ <!-- __favorite-item is a <div> (not an <a>) because the
252
+ whole row is clickable via data-href + JS. The
253
+ __favorite-remove button is opacity: 0 until the
254
+ row is hovered — it fades in when a user is over
255
+ the favorite. -->
268
256
  <li>
269
257
  <div class="pa-profile-panel__favorite-item" data-href="/dashboard">
270
258
  <span class="pa-profile-panel__favorite-icon">📊</span>
@@ -279,19 +267,10 @@
279
267
  <button class="pa-profile-panel__favorite-remove" title="Remove from favorites">✕</button>
280
268
  </div>
281
269
  </li>
282
- <li>
283
- <div class="pa-profile-panel__favorite-item" data-href="/settings">
284
- <span class="pa-profile-panel__favorite-icon">⚙️</span>
285
- <span class="pa-profile-panel__favorite-label">Settings</span>
286
- <button class="pa-profile-panel__favorite-remove" title="Remove from favorites">✕</button>
287
- </div>
288
- </li>
289
270
  </ul>
290
271
  </div>
291
272
  <div class="pa-profile-panel__favorites-add">
292
- <button class="pa-btn pa-btn--sm pa-btn--outline-secondary pa-btn--block">
293
- + Add Current Page
294
- </button>
273
+ <button class="pa-btn pa-btn--sm pa-btn--outline-secondary pa-btn--block">+ Add Current Page</button>
295
274
  </div>
296
275
  </div>
297
276
  </div>
@@ -299,214 +278,115 @@
299
278
  </div>
300
279
 
301
280
 
302
- <!-- ================================
303
- PROFILE PANEL - WITH ICON-ONLY TABS
304
- Add --icon-only modifier to hide text labels
305
- ================================ -->
306
-
307
- <div class="pa-profile-panel" id="profilePanel">
308
- <div class="pa-profile-panel__overlay" onclick="closeProfilePanel()"></div>
309
- <div class="pa-profile-panel__content">
310
- <div class="pa-profile-panel__header">
311
- <div class="pa-profile-panel__avatar">
312
- <span class="pa-profile-panel__avatar-icon"><i class="fa-solid fa-user"></i></span>
313
- </div>
314
- <div class="pa-profile-panel__info">
315
- <h3 class="pa-profile-panel__name">John Doe</h3>
316
- <p class="pa-profile-panel__email">john.doe@company.com</p>
317
- <span class="pa-profile-panel__role">Administrator</span>
318
- </div>
319
- <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">
320
- <i class="fa-solid fa-xmark"></i>
321
- </button>
322
- </div>
323
-
324
- <!-- Profile Panel Tabs (icon-only) - add --icon-only modifier -->
325
- <div class="pa-profile-panel__tabs pa-profile-panel__tabs--icon-only">
326
- <div class="pa-tabs pa-tabs--full">
327
- <button class="pa-tabs__item pa-tabs__item--active" data-profile-tab="profile" title="Profile">
328
- <i class="fa-solid fa-user"></i>
329
- <span class="pa-profile-panel__tab-text">Profile</span>
330
- </button>
331
- <button class="pa-tabs__item" data-profile-tab="favorites" title="Favorites">
332
- <i class="fa-solid fa-star"></i>
333
- <span class="pa-profile-panel__tab-text">Favorites</span>
334
- </button>
335
- </div>
336
- </div>
337
-
338
- <div class="pa-profile-panel__body">
339
- <!-- Content same as regular tabs variant -->
340
- </div>
281
+ <!-- Icon-only tab variant — hides the .pa-profile-panel__tab-text via
282
+ --icon-only on the tabs container. Keep the <span class="__tab-text">
283
+ markup in place so removing the modifier re-enables labels. -->
284
+ <div class="pa-profile-panel__tabs pa-profile-panel__tabs--icon-only">
285
+ <div class="pa-tabs pa-tabs--full">
286
+ <button class="pa-tabs__item pa-tabs__item--active" data-profile-tab="profile" title="Profile">
287
+ <i class="fa-solid fa-user"></i>
288
+ <span class="pa-profile-panel__tab-text">Profile</span>
289
+ </button>
290
+ <button class="pa-tabs__item" data-profile-tab="favorites" title="Favorites">
291
+ <i class="fa-solid fa-star"></i>
292
+ <span class="pa-profile-panel__tab-text">Favorites</span>
293
+ </button>
341
294
  </div>
342
295
  </div>
343
296
 
344
297
 
345
298
  <!-- ================================
346
- PROFILE PANEL - WITH FONT AWESOME
299
+ WIDTH CONTROL
300
+ The panel's content width is driven by two CSS custom properties
301
+ set at :root by the framework. The rule that applies them uses
302
+ :where() so the selector has ZERO specificity — any utility class
303
+ applied to .pa-profile-panel__content will win.
304
+
305
+ Default values:
306
+ --pa-local-profile-panel-width (set from $profile-panel-width)
307
+ --pa-local-profile-panel-max-width (set from $profile-panel-max-width)
308
+
309
+ Override strategies:
310
+ 1. Utility class on __content — wr-*, minwr-*, maxwr-*
311
+ 2. Inline style on __content — quick one-offs
312
+ 3. Update the root CSS variable — app-wide change
347
313
  ================================ -->
348
314
 
349
- <div class="pa-profile-panel" id="profilePanel">
350
- <div class="pa-profile-panel__overlay" onclick="closeProfilePanel()"></div>
351
- <div class="pa-profile-panel__content">
352
- <div class="pa-profile-panel__header">
353
- <div class="pa-profile-panel__avatar">
354
- <span class="pa-profile-panel__avatar-icon"><i class="fa-solid fa-user"></i></span>
355
- </div>
356
- <div class="pa-profile-panel__info">
357
- <h3 class="pa-profile-panel__name">John Doe</h3>
358
- <p class="pa-profile-panel__email">john.doe@company.com</p>
359
- <span class="pa-profile-panel__role">Administrator</span>
360
- </div>
361
- <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">
362
- <i class="fa-solid fa-xmark"></i>
363
- </button>
364
- </div>
365
-
366
- <div class="pa-profile-panel__body">
367
- <nav class="pa-profile-panel__nav">
368
- <ul>
369
- <li><a href="#profile" class="pa-profile-panel__nav-item">
370
- <span class="pa-profile-panel__nav-icon"><i class="fa-solid fa-user"></i></span>
371
- Profile Settings
372
- </a></li>
373
- <li><a href="#security" class="pa-profile-panel__nav-item">
374
- <span class="pa-profile-panel__nav-icon"><i class="fa-solid fa-lock"></i></span>
375
- Security
376
- </a></li>
377
- <li><a href="#notifications" class="pa-profile-panel__nav-item">
378
- <span class="pa-profile-panel__nav-icon"><i class="fa-solid fa-bell"></i></span>
379
- Notifications
380
- </a></li>
381
- <li><a href="#preferences" class="pa-profile-panel__nav-item">
382
- <span class="pa-profile-panel__nav-icon"><i class="fa-solid fa-gear"></i></span>
383
- Preferences
384
- </a></li>
385
- <li><a href="#help" class="pa-profile-panel__nav-item">
386
- <span class="pa-profile-panel__nav-icon"><i class="fa-solid fa-circle-question"></i></span>
387
- Help & Support
388
- </a></li>
389
- </ul>
390
- </nav>
391
-
392
- <div class="pa-profile-panel__actions">
393
- <button class="pa-btn pa-btn--secondary pa-btn--block">
394
- <i class="fa-solid fa-right-left"></i> Switch Account
395
- </button>
396
- <button class="pa-btn pa-btn--danger pa-btn--block">
397
- <i class="fa-solid fa-right-from-bracket"></i> Sign Out
398
- </button>
399
- </div>
400
- </div>
401
- </div>
315
+ <!-- 1. Fixed width — 30rem (300px). Wins because :where() = 0 specificity. -->
316
+ <div class="pa-profile-panel__content wr-30">
317
+ <!-- … -->
402
318
  </div>
403
319
 
320
+ <!-- 2. Clamp between floor and ceiling — good for resizable panels -->
321
+ <div class="pa-profile-panel__content minwr-25 maxwr-50">
322
+ <!-- width is free between 25rem and 50rem -->
323
+ </div>
404
324
 
405
- <!-- ================================
406
- PROFILE PANEL - MINIMAL (NO ACTIONS)
407
- ================================ -->
408
-
409
- <div class="pa-profile-panel" id="profilePanel">
410
- <div class="pa-profile-panel__overlay" onclick="closeProfilePanel()"></div>
411
- <div class="pa-profile-panel__content">
412
- <div class="pa-profile-panel__header">
413
- <div class="pa-profile-panel__avatar">
414
- <span class="pa-profile-panel__avatar-icon">👤</span>
415
- </div>
416
- <div class="pa-profile-panel__info">
417
- <h3 class="pa-profile-panel__name">John Doe</h3>
418
- <p class="pa-profile-panel__email">john.doe@company.com</p>
419
- <span class="pa-profile-panel__role">Administrator</span>
420
- </div>
421
- <button class="pa-profile-panel__close" onclick="closeProfilePanel()" aria-label="Close Profile">
422
-
423
- </button>
424
- </div>
325
+ <!-- 3. App-wide default — set the CSS variable at :root (or on <html>) -->
326
+ <style>
327
+ :root {
328
+ --pa-local-profile-panel-width: 36rem;
329
+ --pa-local-profile-panel-max-width: 56rem;
330
+ }
331
+ </style>
425
332
 
426
- <div class="pa-profile-panel__body">
427
- <nav class="pa-profile-panel__nav">
428
- <ul>
429
- <li><a href="#profile" class="pa-profile-panel__nav-item">
430
- <span class="pa-profile-panel__nav-icon">👤</span>
431
- Profile Settings
432
- </a></li>
433
- <li><a href="#logout" class="pa-profile-panel__nav-item">
434
- <span class="pa-profile-panel__nav-icon">🚪</span>
435
- Sign Out
436
- </a></li>
437
- </ul>
438
- </nav>
439
- </div>
440
- </div>
441
- </div>
333
+ <!--
334
+ Responsive behaviour (automatic, below $mobile-breakpoint / 768px):
335
+ .pa-profile-panel__content → width: 85vw
336
+ max-width: $profile-panel-mobile-max-width
337
+ On mobile the panel fills most of the viewport regardless of the
338
+ desktop width-control setting.
339
+ -->
442
340
 
443
341
 
444
342
  <!-- ================================
445
- JAVASCRIPT - PROFILE PANEL TOGGLE
343
+ JAVASCRIPT toggle, close, tabs, favorites
446
344
  ================================ -->
447
345
 
448
346
  <script>
449
- /**
450
- * Toggle profile panel open/closed
451
- */
452
347
  function toggleProfilePanel() {
453
- const profilePanel = document.getElementById('profilePanel');
454
- profilePanel.classList.toggle('pa-profile-panel--open');
348
+ document.getElementById('profilePanel').classList.toggle('pa-profile-panel--open');
455
349
  }
456
350
 
457
- /**
458
- * Close profile panel
459
- */
460
351
  function closeProfilePanel() {
461
- const profilePanel = document.getElementById('profilePanel');
462
- profilePanel.classList.remove('pa-profile-panel--open');
352
+ document.getElementById('profilePanel').classList.remove('pa-profile-panel--open');
463
353
  }
464
354
 
465
- /**
466
- * Close profile panel when clicking outside
467
- */
355
+ // Click-outside-to-close — checks against the profile BUTTON as well
356
+ // as the panel itself so toggling via the button doesn't immediately
357
+ // re-close it.
468
358
  document.addEventListener('click', (e) => {
469
- const profilePanel = document.getElementById('profilePanel');
470
- const profileBtn = document.querySelector('.pa-header__profile-btn');
471
-
472
- if (!profilePanel || !profileBtn) return;
359
+ const panel = document.getElementById('profilePanel');
360
+ const btn = document.querySelector('.pa-header__profile-btn');
361
+ if (!panel || !btn) return;
473
362
 
474
- if (!profilePanel.contains(e.target) && !profileBtn.contains(e.target) &&
475
- profilePanel.classList.contains('pa-profile-panel--open')) {
363
+ if (!panel.contains(e.target) && !btn.contains(e.target) &&
364
+ panel.classList.contains('pa-profile-panel--open')) {
476
365
  closeProfilePanel();
477
366
  }
478
367
  });
479
368
 
480
- /**
481
- * Profile panel tab switching
482
- */
369
+ // Tab switching — flips --active on buttons + panels in lockstep.
483
370
  document.querySelectorAll('[data-profile-tab]').forEach(tab => {
484
371
  tab.addEventListener('click', () => {
485
- const tabId = tab.dataset.profileTab;
486
- // Update active tab
372
+ const id = tab.dataset.profileTab;
487
373
  document.querySelectorAll('[data-profile-tab]').forEach(t =>
488
- t.classList.toggle('pa-tabs__item--active', t.dataset.profileTab === tabId));
489
- // Update active panel
374
+ t.classList.toggle('pa-tabs__item--active', t.dataset.profileTab === id));
490
375
  document.querySelectorAll('[data-profile-panel]').forEach(p =>
491
- p.classList.toggle('pa-tabs__panel--active', p.dataset.profilePanel === tabId));
376
+ p.classList.toggle('pa-tabs__panel--active', p.dataset.profilePanel === id));
492
377
  });
493
378
  });
494
379
 
495
- /**
496
- * Favorite item click - navigate using data-href
497
- */
380
+ // Favorite click → navigate via data-href (unless the remove button was clicked)
498
381
  document.querySelectorAll('.pa-profile-panel__favorite-item').forEach(item => {
499
382
  item.addEventListener('click', (e) => {
500
- // Skip if clicking the remove button
501
383
  if (e.target.closest('.pa-profile-panel__favorite-remove')) return;
502
384
  const href = item.dataset.href;
503
385
  if (href) window.location.href = href;
504
386
  });
505
387
  });
506
388
 
507
- /**
508
- * Favorite remove button - remove item from list
509
- */
389
+ // Favorite remove → drop the <li>
510
390
  document.querySelectorAll('.pa-profile-panel__favorite-remove').forEach(btn => {
511
391
  btn.addEventListener('click', (e) => {
512
392
  e.stopPropagation();
@@ -517,67 +397,147 @@ document.querySelectorAll('.pa-profile-panel__favorite-remove').forEach(btn => {
517
397
 
518
398
 
519
399
  <!-- ================================
520
- USAGE NOTES
400
+ COMPONENT REFERENCE
521
401
  ================================ -->
522
402
 
523
403
  <!--
524
- PROFILE BUTTON (HEADER):
525
- - .pa-header__profile-btn - Profile button in header
526
- - .pa-btn__icon - Icon container
527
- - .pa-header__profile-name - User name text
528
-
529
- PROFILE PANEL STRUCTURE:
530
- - .pa-profile-panel - Main panel container
531
- - #profilePanel - Required ID for JavaScript
532
- - .pa-profile-panel--open - Open state (toggled by JS)
533
- - .pa-profile-panel__overlay - Click-to-close overlay
534
- - .pa-profile-panel__content - Panel content container
535
-
536
- PANEL HEADER:
537
- - .pa-profile-panel__header - Header container
538
- - .pa-profile-panel__header--no-avatar - Modifier to hide avatar (for corporate apps)
539
- - .pa-profile-panel__avatar - Avatar container
540
- - .pa-profile-panel__avatar-icon - Avatar icon/image
541
- - .pa-profile-panel__info - User info container
542
- - .pa-profile-panel__name - User name (truncates with ellipsis, add title for tooltip)
543
- - .pa-profile-panel__email - User email (truncates with ellipsis, add title for tooltip)
544
- - .pa-profile-panel__role - User role badge
545
- - .pa-profile-panel__close - Close button
546
-
547
- PANEL TABS (optional):
548
- - .pa-profile-panel__tabs - Tabs container
549
- - .pa-profile-panel__tabs--icon-only - Modifier to hide text labels (icon-only mode)
550
- - .pa-profile-panel__tab-text - Text label element (hidden when --icon-only is applied)
551
- - .pa-tabs.pa-tabs--full - Full-width tabs
552
- - [data-profile-tab] - Tab button attribute
553
- - [data-profile-panel] - Tab panel attribute
554
- - Add title attribute to tab buttons for tooltip when using icon-only mode
555
-
556
- PANEL BODY:
557
- - .pa-profile-panel__body - Body container (scrollable)
558
- - .pa-profile-panel__nav - Navigation menu
559
- - .pa-profile-panel__nav-item - Navigation link
560
- - .pa-profile-panel__nav-icon - Navigation icon
561
- - .pa-profile-panel__actions - Action buttons (inside body, scrolls with content)
562
- - .pa-profile-panel__footer - Fixed footer (outside body, always visible at bottom)
563
-
564
- FAVORITES SECTION:
565
- - .pa-profile-panel__favorites - Favorites container
566
- - .pa-profile-panel__favorite-item - Favorite link (use div with data-href)
567
- - .pa-profile-panel__favorite-icon - Favorite icon
568
- - .pa-profile-panel__favorite-label - Favorite text label
569
- - .pa-profile-panel__favorite-remove - Remove button (appears on hover)
570
- - .pa-profile-panel__favorites-add - Add button container
571
-
572
- JAVASCRIPT FUNCTIONS:
573
- - toggleProfilePanel() - Toggle panel open/closed
574
- - closeProfilePanel() - Close panel
575
- - Auto-closes when clicking outside
576
- - Tab switching via data-profile-tab/data-profile-panel attributes
577
- - Favorite navigation via data-href attribute
578
-
579
- PLACEMENT:
580
- - Profile button goes in header/navbar
581
- - Profile panel goes before closing </body> tag
582
- - Panel slides in from right side of screen
404
+ HEADER BUTTON:
405
+ - .pa-header__profile-btn The trigger button; lives in .pa-header__end.
406
+ - .pa-header__profile-name User's name text inside the button.
407
+ Hidden via `display: none` below 768px so
408
+ only the icon shows on mobile.
409
+
410
+ PANEL STRUCTURE:
411
+ - .pa-profile-panel Fixed-position container. Anchored to
412
+ inline-end (right in LTR, left in RTL).
413
+ Hidden by default (opacity: 0 + visibility).
414
+ - .pa-profile-panel--open Set by JS to reveal the panel.
415
+ - .pa-profile-panel__overlay Click-to-close backdrop (fills viewport).
416
+ - .pa-profile-panel__content The visible panel surface. Slides in
417
+ from the inline-end (translateX 100% → 0;
418
+ [dir="rtl"] flips to -100%).
419
+
420
+ HEADER:
421
+ - .pa-profile-panel__header Flex row: avatar + info + close.
422
+ - .pa-profile-panel__header--no-avatar Hides the avatar element (corporate apps).
423
+ - .pa-profile-panel__avatar Circular avatar wrapper.
424
+ - .pa-profile-panel__avatar-icon Emoji / FA icon inside the avatar.
425
+ - .pa-profile-panel__info Text column (name + email + role).
426
+ - .pa-profile-panel__name User name (ellipsis on overflow).
427
+ - .pa-profile-panel__email Email (ellipsis on overflow).
428
+ - .pa-profile-panel__role Role badge (uppercase, tinted bg).
429
+ - .pa-profile-panel__close Close button (top-end corner).
430
+
431
+ Theme-colour design choices (why they're like this):
432
+ - __name and __email use var(--pa-header-profile-name-color) the same
433
+ colour the header uses for the user's name. This guarantees contrast
434
+ against dark / coloured header backgrounds across all themes (body
435
+ text colours would read wrong on a dark header).
436
+ - __role paints its background with color-mix(in srgb,
437
+ var(--pa-header-profile-name-color) 15%, transparent) so the tint is
438
+ derived from the header colour and works on any theme. An older
439
+ --pa-accent-light fallback is declared first for browsers without
440
+ color-mix.
441
+
442
+ TABS (optional sits between __header and __body):
443
+ - .pa-profile-panel__tabs Tabs container (border-bottom + header-bg).
444
+ - .pa-profile-panel__tabs--icon-only Hide text labels, keep icons (tooltips
445
+ via `title` attrs recommended).
446
+ - .pa-profile-panel__tab-text Text label element inside a tab
447
+ (kept in markup; hidden by --icon-only).
448
+ - Uses the shared .pa-tabs / .pa-tabs--full / .pa-tabs__item / .pa-tabs__panel
449
+ component — see tabs.html for the full tab reference.
450
+
451
+ BODY + NAV:
452
+ - .pa-profile-panel__body flex: 1; overflow-y: auto. Scrollable.
453
+ - .pa-profile-panel__nav Navigation menu wrapper (ul reset).
454
+ - .pa-profile-panel__nav-item Menu item link (flex icon + label).
455
+ - .pa-profile-panel__nav-icon Fixed-width icon slot (sidebar icon size).
456
+
457
+ ACTIONS two placements, mutually exclusive per action:
458
+ - .pa-profile-panel__actions Inside __body. Scrolls with the nav
459
+ above. Use for actions that belong to
460
+ the end of a list.
461
+ - .pa-profile-panel__footer Sibling of __body inside __content;
462
+ flex-shrink: 0. Always visible at the
463
+ bottom of the panel regardless of body
464
+ scroll position.
465
+
466
+ FAVORITES (inside a tab panel):
467
+ - .pa-profile-panel__favorites Wrapper (ul reset).
468
+ - .pa-profile-panel__favorite-item Row <div> with data-href (JS
469
+ navigates on click). Hover
470
+ reveals the __favorite-remove
471
+ button.
472
+ - .pa-profile-panel__favorite-icon Fixed-width icon slot.
473
+ - .pa-profile-panel__favorite-label flex: 1 label text.
474
+ - .pa-profile-panel__favorite-remove × button; opacity: 0 by default,
475
+ fades in on __favorite-item:hover.
476
+ - .pa-profile-panel__favorites-add Bottom wrapper for the "+ Add" button.
477
+
478
+ WIDTH CONTROL:
479
+ Two CSS custom properties, applied via a :where() rule on __content so
480
+ utility classes beat the default:
481
+
482
+ :root {
483
+ --pa-local-profile-panel-width: $profile-panel-width;
484
+ --pa-local-profile-panel-max-width: $profile-panel-max-width;
485
+ }
486
+ :where(.pa-profile-panel__content) {
487
+ width: var(--pa-local-profile-panel-width);
488
+ max-width: var(--pa-local-profile-panel-max-width);
489
+ }
490
+
491
+ Override options:
492
+ - Utility class on __content — .wr-{n}, .minwr-{n}, .maxwr-{n}
493
+ (n = 1..10, 15, 20, 25, 30, 35, 40, 45, 50 rem)
494
+ - Inline style — quick one-off width / max-width
495
+ - Root CSS variable override — app-wide default change
496
+
497
+ RESPONSIVE BEHAVIOUR (automatic):
498
+ Below $mobile-breakpoint (768px):
499
+ .pa-profile-panel__content → width: 85vw,
500
+ max-width: $profile-panel-mobile-max-width
501
+ .pa-header__profile-name → display: none (icon-only button)
502
+
503
+ JAVASCRIPT CONTRACT:
504
+ - toggleProfilePanel() — flips --open on the panel
505
+ - closeProfilePanel() — removes --open
506
+ - Click-outside-to-close — registered on document, checks both the
507
+ panel AND the trigger button so toggling
508
+ via the button doesn't immediately re-close.
509
+ - Tabs — [data-profile-tab] on buttons pairs with
510
+ [data-profile-panel] on panels; both get
511
+ --active toggled in lockstep.
512
+ - Favorite nav — data-href attribute on __favorite-item; JS
513
+ navigates on click except when the remove
514
+ button is the target.
515
+
516
+ STRUCTURE PATTERNS:
517
+
518
+ Minimal panel (basic nav + in-body actions):
519
+ <div class="pa-profile-panel" id="profilePanel">
520
+ <div class="pa-profile-panel__overlay" onclick="closeProfilePanel()"></div>
521
+ <div class="pa-profile-panel__content">
522
+ <div class="pa-profile-panel__header">
523
+ <div class="pa-profile-panel__avatar">…</div>
524
+ <div class="pa-profile-panel__info">…</div>
525
+ <button class="pa-profile-panel__close">✕</button>
526
+ </div>
527
+ <div class="pa-profile-panel__body">
528
+ <nav class="pa-profile-panel__nav">…</nav>
529
+ <div class="pa-profile-panel__actions">…</div>
530
+ </div>
531
+ </div>
532
+ </div>
533
+
534
+ With fixed footer (actions pinned, body scrolls):
535
+ <div class="pa-profile-panel__content">
536
+ <div class="pa-profile-panel__header">…</div>
537
+ <div class="pa-profile-panel__body">…</div>
538
+ <div class="pa-profile-panel__footer">…</div>
539
+ </div>
540
+
541
+ Width override (fixed 30rem):
542
+ <div class="pa-profile-panel__content wr-30">…</div>
583
543
  -->