@keenmate/pure-admin-core 2.8.0 → 2.9.0-rc03

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.
@@ -0,0 +1,210 @@
1
+ <!-- ================================
2
+ SPLITTER
3
+ Pure Admin Visual Framework
4
+
5
+ Resizable container with two or more panes. Drag a gutter to resize,
6
+ double-click to collapse / restore, arrow keys nudge size when the
7
+ gutter is focused.
8
+
9
+ CSS + structure here; drag, keyboard, persistence behaviour is wired up
10
+ by packages/core/src/js/splitter.js. The script auto-initializes on
11
+ [data-pa-splitter] at DOMContentLoaded.
12
+
13
+ Two markup flavours:
14
+ * 2-pane shorthand — uses --start / --end modifiers and root-level
15
+ min-start / max-start / default attributes (sections 1–3 below).
16
+ * N-pane (N ≥ 2) — generic panes with per-pane size / min / max
17
+ attributes (section 4 below). Use this when you have 3+ panes
18
+ or want more than one minimizable pane.
19
+
20
+ Orientation modifiers (match flexbox direction of the panes):
21
+ --horizontal panes side-by-side, vertical gutter (default)
22
+ --vertical panes stacked, horizontal gutter
23
+
24
+ The splitter takes whatever width/height its parent gives it — make
25
+ sure the parent has a sized cross axis (height for --horizontal,
26
+ width for --vertical) or the panes will collapse.
27
+ ================================ -->
28
+
29
+
30
+ <!-- ================================
31
+ HORIZONTAL SPLIT
32
+ Side-by-side panes; vertical gutter. Start pane has a 200px floor
33
+ and 60% ceiling, defaults to 280px, persists under "demo-sidebar".
34
+ ================================ -->
35
+
36
+ <div class="pa-splitter pa-splitter--horizontal"
37
+ data-pa-splitter
38
+ data-pa-splitter-id="demo-sidebar"
39
+ data-pa-splitter-min-start="200px"
40
+ data-pa-splitter-max-start="60%"
41
+ data-pa-splitter-default="280px"
42
+ style="height: 400px;">
43
+ <div class="pa-splitter__pane pa-splitter__pane--start">
44
+ <!-- Your sidebar / list / nav content -->
45
+ </div>
46
+ <div class="pa-splitter__gutter"
47
+ role="separator"
48
+ aria-orientation="vertical"
49
+ tabindex="0"></div>
50
+ <div class="pa-splitter__pane pa-splitter__pane--end">
51
+ <!-- Your main / detail content -->
52
+ </div>
53
+ </div>
54
+
55
+
56
+ <!-- ================================
57
+ VERTICAL SPLIT
58
+ Stacked panes; horizontal gutter. Useful for editor + console / log
59
+ pane patterns.
60
+ ================================ -->
61
+
62
+ <div class="pa-splitter pa-splitter--vertical"
63
+ data-pa-splitter
64
+ data-pa-splitter-id="demo-console"
65
+ data-pa-splitter-min-start="80px"
66
+ data-pa-splitter-max-start="80%"
67
+ data-pa-splitter-default="60%"
68
+ style="height: 400px;">
69
+ <div class="pa-splitter__pane pa-splitter__pane--start">
70
+ <!-- Editor / primary content -->
71
+ </div>
72
+ <div class="pa-splitter__gutter"
73
+ role="separator"
74
+ aria-orientation="horizontal"
75
+ tabindex="0"></div>
76
+ <div class="pa-splitter__pane pa-splitter__pane--end">
77
+ <!-- Console / log / secondary -->
78
+ </div>
79
+ </div>
80
+
81
+
82
+ <!-- ================================
83
+ SPACED CARDS
84
+ Set `gap` on the splitter root to put breathing room between the
85
+ panes and the gutter — the JS subtracts it from the available
86
+ space so percent constraints still resolve correctly. Override
87
+ gutter thickness via the --pa-splitter-gutter-size custom property.
88
+ ================================ -->
89
+
90
+ <div class="pa-splitter pa-splitter--horizontal"
91
+ data-pa-splitter
92
+ style="height: 280px; gap: 1.6rem; --pa-splitter-gutter-size: 1rem;">
93
+ <div class="pa-splitter__pane pa-splitter__pane--start" style="padding: 0;">
94
+ <div class="pa-card" style="height: 100%; margin: 0;">…</div>
95
+ </div>
96
+ <div class="pa-splitter__gutter"
97
+ role="separator"
98
+ aria-orientation="vertical"
99
+ tabindex="0"></div>
100
+ <div class="pa-splitter__pane pa-splitter__pane--end" style="padding: 0;">
101
+ <div class="pa-card" style="height: 100%; margin: 0;">…</div>
102
+ </div>
103
+ </div>
104
+
105
+
106
+ <!-- ================================
107
+ N-PANE LAYOUT (3 panes — sidebar + content + inspector)
108
+ Per-pane attributes replace the root-level --start ones. Only the
109
+ first and last panes honour the minimize marker. Panes without a
110
+ data-pa-splitter-size share the leftover equally.
111
+ ================================ -->
112
+
113
+ <div class="pa-splitter pa-splitter--horizontal"
114
+ data-pa-splitter
115
+ data-pa-splitter-id="demo-three-pane"
116
+ style="height: 400px;">
117
+ <div class="pa-splitter__pane"
118
+ data-pa-splitter-size="240px"
119
+ data-pa-splitter-min="180px"
120
+ data-pa-splitter-max="360px"
121
+ data-pa-splitter-minimize>
122
+ <!-- File tree / nav (minimizable to a left rail) -->
123
+ </div>
124
+ <div class="pa-splitter__gutter"
125
+ role="separator"
126
+ aria-orientation="vertical"
127
+ tabindex="0"></div>
128
+ <div class="pa-splitter__pane"
129
+ data-pa-splitter-min="240px">
130
+ <!-- Editor / main content (fills the rest) -->
131
+ </div>
132
+ <div class="pa-splitter__gutter"
133
+ role="separator"
134
+ aria-orientation="vertical"
135
+ tabindex="0"></div>
136
+ <div class="pa-splitter__pane"
137
+ data-pa-splitter-size="280px"
138
+ data-pa-splitter-min="220px"
139
+ data-pa-splitter-max="420px"
140
+ data-pa-splitter-minimize>
141
+ <!-- Inspector / detail (minimizable to a right rail) -->
142
+ </div>
143
+ </div>
144
+
145
+
146
+ <!-- ================================
147
+ DATA ATTRIBUTES — root (both modes)
148
+ data-pa-splitter marker; required
149
+ data-pa-splitter-id enables localStorage persistence
150
+ under "pa-splitter:<id>"
151
+ data-pa-splitter-step keyboard step in px (default: 10)
152
+ data-pa-splitter-rail-size rail width in px (default: 40)
153
+ data-pa-splitter-minimize-threshold drag-to-rail snap ratio (default: 0.40)
154
+
155
+ DATA ATTRIBUTES — root (legacy 2-pane only)
156
+ data-pa-splitter-min-start "200px" or "20%" (default: 0)
157
+ data-pa-splitter-max-start "60%" or "800px" (default: available)
158
+ data-pa-splitter-default initial size when no saved state
159
+ data-pa-splitter-minimize "start" or "end" — opt into rail collapse
160
+
161
+ DATA ATTRIBUTES — per pane (N-pane only)
162
+ data-pa-splitter-size "240px" or "30%" (default: shared leftover)
163
+ data-pa-splitter-min "150px" or "10%" (default: 0)
164
+ data-pa-splitter-max "400px" or "50%" (default: available)
165
+ data-pa-splitter-minimize marker — only honoured on the first
166
+ and last panes
167
+
168
+ CHILD ATTRIBUTES (both modes)
169
+ data-pa-splitter-toggle put on any element inside the splitter
170
+ (typically a button in the card header)
171
+ to trigger collapse / restore on click.
172
+ In N-pane mode, toggles the enclosing
173
+ pane (if it's minimizable).
174
+
175
+ RAIL-TITLE HOOK (non-card content)
176
+ Any element marked with `[data-pa-splitter-rail-title]` inside a
177
+ minimized pane rotates to vertical writing (sideways-rl). Use this
178
+ when you put plain content (not a `.pa-card`) in a minimizable pane:
179
+
180
+ <div class="pa-splitter__pane" data-pa-splitter-minimize>
181
+ <div data-pa-splitter-rail-title>📁 Files</div>
182
+ <!-- The rest of your pane content here -->
183
+ </div>
184
+
185
+ Cards adapt automatically — `_cards.scss` rotates the card header
186
+ inside any `.pa-splitter__pane--minimized` without needing the
187
+ attribute. No markup change needed for the card snippets above.
188
+
189
+ CSS CUSTOMIZATION
190
+ --pa-splitter-gutter-size gutter thickness (default: 6px)
191
+ --pa-splitter-rail-size rail width when a pane is minimized
192
+ (default: 40px / $splitter-rail-size).
193
+ JS picks this up via getComputedStyle, so
194
+ setting it on `:root` or per-instance via
195
+ inline style overrides the rail width
196
+ without also setting the data-attr.
197
+ gap space between panes and gutter
198
+ (native flexbox property)
199
+
200
+ MODIFIER CLASSES
201
+ pa-splitter--minimize-mirror flip the minimized title 180°
202
+ (transform: scale(-1,-1) on the heading
203
+ inside any `[data-pa-splitter-rail-title]`
204
+ or `.pa-card__header`)
205
+
206
+ JAVASCRIPT API (window.PaSplitter)
207
+ PaSplitter.init(el) initialize a single element (idempotent)
208
+ PaSplitter.initAll(root?) initialize all uninitialized splitters
209
+ under root (default: document)
210
+ ================================ -->
@@ -47,14 +47,6 @@
47
47
  --base-disabled-bg: #{$base-disabled-bg};
48
48
  --base-elevated-bg: #{$base-elevated-bg};
49
49
 
50
- // === Background Colors (legacy aliases) ===
51
- --base-surface-1: #{$base-surface-1};
52
- --base-surface-2: #{$base-surface-2};
53
- --base-surface-3: #{$base-surface-3};
54
- --base-surface-inverse: #{$base-surface-inverse};
55
- --base-primary-bg: #{$base-primary-bg};
56
- --base-primary-bg-hover: #{$base-primary-bg-hover};
57
-
58
50
  // === Border Colors ===
59
51
  --base-border-color: #{$base-border-color};
60
52
  --base-border: #{$base-border};
@@ -265,6 +257,18 @@
265
257
  // ============================================================================
266
258
 
267
259
  @mixin output-pa-css-variables {
260
+ // =========================================================================
261
+ // COLOR SCHEME SIGNAL
262
+ // Tells the browser whether this scope is light or dark so native UA
263
+ // elements (scrollbars, form controls) and `light-dark()` resolve
264
+ // correctly. Drives web components (e.g. <web-multiselect>) that ship
265
+ // adaptive palettes via `light-dark()`. Override $theme-color-scheme
266
+ // before importing variables for always-dark themes; for dual-mode
267
+ // themes leave default `light` here and add `color-scheme: dark;` to
268
+ // the `.pa-mode-dark` block.
269
+ // =========================================================================
270
+ color-scheme: #{$theme-color-scheme};
271
+
268
272
  // =========================================================================
269
273
  // CORE COLORS
270
274
  // =========================================================================
@@ -111,6 +111,9 @@
111
111
  // Settings Panel
112
112
  @use 'core-components/settings-panel' as *;
113
113
 
114
+ // Splitter (resizable two-pane container)
115
+ @use 'core-components/splitter' as *;
116
+
114
117
  // Data Display (read-only fields)
115
118
  @use 'core-components/data-display' as *;
116
119
 
@@ -44,6 +44,21 @@
44
44
  }
45
45
  }
46
46
 
47
+ // Chromeless action button — no fill, no border, just the label / icon.
48
+ // Used for inline affordances inside dense surfaces (card-header actions,
49
+ // notification dismiss, toast close, splitter minimize) where a full button
50
+ // would compete with the content.
51
+ &--ghost {
52
+ background-color: transparent;
53
+ border-color: transparent;
54
+ color: var(--pa-text-secondary);
55
+
56
+ &:hover {
57
+ background-color: var(--pa-surface-hover);
58
+ color: var(--pa-text-color-1);
59
+ }
60
+ }
61
+
47
62
  &--xs {
48
63
  height: $btn-height-xs;
49
64
  padding: $btn-padding-xs-v $btn-padding-xs-h;
@@ -23,8 +23,13 @@
23
23
  &__header {
24
24
  padding: $card-header-padding-v $card-header-padding-h;
25
25
  min-height: $card-header-min-height;
26
- border-top-left-radius: $card-border-radius;
27
- border-top-right-radius: $card-border-radius;
26
+ // No own border-top-radius — the card's `overflow: hidden` + outer
27
+ // border-radius clips the header's square top corners to match. Setting
28
+ // a radius here makes the header curve at 8px while the card's INNER
29
+ // corner is ~7px (outer 8px minus the 1px border), leaving a thin
30
+ // wedge of card background visible at each top corner — most obvious
31
+ // on coloured variants (--primary/--success/--warning/--danger/--color-*)
32
+ // where the wedge shows as a white sliver against the variant colour.
28
33
  border-bottom: $border-width-base solid var(--pa-border-color);
29
34
  background: var(--pa-card-header-bg);
30
35
  display: flex;
@@ -95,6 +100,24 @@
95
100
  flex-shrink: 0;
96
101
  }
97
102
 
103
+ // …unless it's the progressive-overflow variant, which intentionally
104
+ // shrinks below content so the JS can read `scrollWidth > clientWidth`
105
+ // as the "needs overflow" signal. Nested under `&__header` so the
106
+ // selector matches `.pa-card__header .pa-card__actions--overflow` —
107
+ // the same specificity as the rule above, but later in source order
108
+ // and explicit on the modifier.
109
+ .pa-card__actions--overflow {
110
+ flex-shrink: 1;
111
+ }
112
+
113
+ // When the actions wrapper can shrink (overflow variant), give the title
114
+ // a floor so it can't be squeezed to 0 — icon + a few chars + ellipsis
115
+ // stay visible no matter how narrow. `:has()` scopes the override so
116
+ // ordinary cards keep `min-width: 0` and their full ellipsis range.
117
+ &:has(> .pa-card__actions--overflow) > .pa-card__title {
118
+ min-width: 6rem;
119
+ }
120
+
98
121
  // Buttons in card headers - negative margin to prevent header height growth
99
122
  .pa-btn {
100
123
  margin-top: -0.25rem;
@@ -232,6 +255,61 @@
232
255
  display: flex;
233
256
  gap: $spacing-sm;
234
257
  align-items: center;
258
+
259
+ // Responsive actions — render both the spread button list AND a
260
+ // collapsed split-button form; a container query on the header swaps
261
+ // which is visible. Stays CSS-only (no ResizeObserver / layout reads)
262
+ // but the markup carries both forms.
263
+ //
264
+ // <div class="pa-card__header">
265
+ // <div class="pa-card__title">…</div>
266
+ // <div class="pa-card__actions pa-card__actions--responsive">
267
+ // <div class="pa-card__actions-full">
268
+ // …spread buttons…
269
+ // </div>
270
+ // <div class="pa-card__actions-collapsed">
271
+ // <div class="pa-btn-split">…same actions as a menu…</div>
272
+ // </div>
273
+ // </div>
274
+ // </div>
275
+ //
276
+ // Container is the header; threshold is $card-actions-collapse-at.
277
+ // Wired up via :has() (Baseline 2023) so authors don't need a second
278
+ // modifier on the header.
279
+ &--responsive {
280
+ > .pa-card__actions-full {
281
+ display: flex;
282
+ gap: $spacing-sm;
283
+ align-items: center;
284
+ }
285
+ > .pa-card__actions-collapsed {
286
+ display: none;
287
+ }
288
+ }
289
+
290
+ // Progressive overflow variant — driven by card-actions-overflow.js.
291
+ // `min-width: 0` lets flex shrink the wrapper below its content; with
292
+ // `overflow: hidden` the wrapper clips its children when narrow, which
293
+ // is what makes `scrollWidth > clientWidth` a reliable overflow signal
294
+ // for the JS. Buttons themselves get `flex-shrink: 0` so they don't
295
+ // squeeze to fit — they either fit fully or get moved to the menu.
296
+ &--overflow {
297
+ min-width: 0;
298
+ overflow: hidden;
299
+
300
+ > * {
301
+ flex-shrink: 0;
302
+ }
303
+ }
304
+ }
305
+
306
+ // Enable the container query on any header that contains a responsive
307
+ // actions wrapper. Container query rule itself is emitted at the bottom
308
+ // of this file (outside the .pa-card block) since `@container` can't
309
+ // hide inside an arbitrarily-nested selector cleanly.
310
+ &__header:has(> .pa-card__actions--responsive) {
311
+ container-type: inline-size;
312
+ container-name: pa-card-header;
235
313
  }
236
314
 
237
315
  &__meta {
@@ -240,11 +318,15 @@
240
318
  }
241
319
 
242
320
  // Card variants
321
+ // Coloured variants override the header's border-bottom-color to match the
322
+ // variant — otherwise the default light-gray hairline shows as a visible
323
+ // strip against the coloured surroundings (header bg + card border above).
243
324
  &--primary {
244
325
  border-color: var(--pa-accent);
245
326
 
246
327
  .pa-card__header {
247
328
  background-color: var(--pa-accent);
329
+ border-bottom-color: var(--pa-accent);
248
330
  color: var(--pa-btn-primary-text);
249
331
 
250
332
  h1,
@@ -263,6 +345,7 @@
263
345
 
264
346
  .pa-card__header {
265
347
  background-color: var(--pa-success-bg);
348
+ border-bottom-color: var(--pa-success-bg);
266
349
  color: var(--pa-btn-success-text);
267
350
 
268
351
  h1,
@@ -281,6 +364,7 @@
281
364
 
282
365
  .pa-card__header {
283
366
  background-color: var(--pa-warning-bg);
367
+ border-bottom-color: var(--pa-warning-bg);
284
368
  color: var(--pa-btn-warning-text);
285
369
 
286
370
  h1,
@@ -299,6 +383,7 @@
299
383
 
300
384
  .pa-card__header {
301
385
  background-color: var(--pa-danger-bg);
386
+ border-bottom-color: var(--pa-danger-bg);
302
387
  color: var(--pa-btn-danger-text);
303
388
 
304
389
  h1,
@@ -366,6 +451,7 @@
366
451
 
367
452
  .pa-card__header {
368
453
  background-color: var(--pa-color-#{$i});
454
+ border-bottom-color: var(--pa-color-#{$i});
369
455
  color: var(--pa-color-#{$i}-text);
370
456
 
371
457
  h1,
@@ -486,3 +572,79 @@ a.pa-card {
486
572
  border-bottom: $border-width-medium solid var(--pa-accent);
487
573
  padding-bottom: $spacing-sm;
488
574
  }
575
+
576
+ // Responsive card actions — container query for the spread/collapsed swap.
577
+ // Container context set by `.pa-card__header:has(> .pa-card__actions--responsive)`
578
+ // above. Threshold needs explicit interpolation in `@container` (the Sass
579
+ // auto-interpolation that works in `@media` doesn't apply here).
580
+ @container pa-card-header (max-width: #{$card-actions-collapse-at}) {
581
+ .pa-card__actions--responsive {
582
+ > .pa-card__actions-full {
583
+ display: none;
584
+ }
585
+ > .pa-card__actions-collapsed {
586
+ display: flex;
587
+ align-items: center;
588
+ }
589
+ }
590
+ }
591
+
592
+ // Card adaptation inside a minimized splitter pane.
593
+ // Lives here (not in `_splitter.scss`) so the splitter stays unaware of card
594
+ // internals — the rotation hook on the splitter side is `[data-pa-splitter-rail-title]`
595
+ // and this rule applies that attribute's contract to the card header.
596
+ //
597
+ // Layout: pa-card already has `height: 100%` etc., we just hide body/footer,
598
+ // promote the header to fill the rail, and pin the title to the top.
599
+ .pa-splitter__pane--minimized > .pa-card {
600
+ height: 100%;
601
+ display: flex;
602
+ flex-direction: column;
603
+ margin: 0;
604
+
605
+ > .pa-card__body,
606
+ > .pa-card__footer {
607
+ display: none;
608
+ }
609
+
610
+ > .pa-card__header {
611
+ flex: 1 1 auto;
612
+ // Pin title to the rail's top (inline-start in sideways-rl writing mode).
613
+ justify-content: flex-start;
614
+ writing-mode: sideways-rl;
615
+
616
+ // Icons reset to natural orientation (descendant combinator covers both
617
+ // flat markup and the BEM `pa-card__title > i` nesting).
618
+ i,
619
+ svg {
620
+ writing-mode: initial;
621
+ }
622
+
623
+ // Vertically align the icon between expanded and minimized states.
624
+ // In expanded mode, `align-items: center` centers the title vertically
625
+ // (icon center at header-min-height / 2 from top). In minimized mode,
626
+ // `justify-content: flex-start` pins the title to the rail's top edge.
627
+ // Push the title down by the difference so the icon sits at the same
628
+ // visual y-position in both states — no "jump" when toggling.
629
+ .pa-card__title {
630
+ padding-top: calc((#{$card-header-min-height} - #{$font-size-base}) / 2 - #{$card-header-padding-v});
631
+ }
632
+
633
+ // Hide interactive controls inside the header — they'd be sideways and
634
+ // unreadable. The list here is intentional: cards know their own header
635
+ // contents better than the splitter ever could.
636
+ .pa-btn,
637
+ .pa-btn-group,
638
+ button,
639
+ input {
640
+ display: none;
641
+ }
642
+ }
643
+ }
644
+
645
+ // Mirror modifier — when the splitter root carries `pa-splitter--minimize-mirror`,
646
+ // the card heading flips 180° (matches the generic mirror behaviour, scoped
647
+ // to the card title heading).
648
+ .pa-splitter--minimize-mirror .pa-splitter__pane--minimized > .pa-card > .pa-card__header :is(h1, h2, h3, h4, h5, h6) {
649
+ transform: scale(-1, -1);
650
+ }
@@ -162,20 +162,12 @@
162
162
  white-space: nowrap;
163
163
  }
164
164
 
165
- &__role {
166
- display: inline-block;
167
- padding: $btn-padding-v $btn-padding-h;
168
- // Tinted bg derived from the header's name color so it reads on any
169
- // header dark or light. Fallback kept for older browsers.
170
- background-color: var(--pa-accent-light);
171
- background-color: color-mix(in srgb, var(--pa-header-profile-name-color) 15%, transparent);
172
- color: var(--pa-header-profile-name-color);
173
- font-size: $font-size-xs;
174
- font-weight: $font-weight-medium;
175
- border-radius: var(--pa-border-radius);
176
- text-transform: uppercase;
177
- letter-spacing: $profile-role-letter-spacing;
178
- }
165
+ // Role badge: use the standard `.pa-badge` component in markup instead of
166
+ // a custom `__role` element. Previously this declared its own bg/colour/
167
+ // padding/uppercase styling — duplicating badge work that already existed
168
+ // in the framework, and coupling to `--pa-header-profile-name-color` which
169
+ // went invisible inside dark-mode panel bodies. Snippets and demo markup
170
+ // updated accordingly in 2.9.0.
179
171
 
180
172
  &__close {
181
173
  position: absolute;