@keenmate/pure-admin-core 2.9.0-rc04 → 2.9.0-rc05

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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Lightweight, data-focused CSS/SCSS admin framework with Corporate theme as default.
4
4
 
5
+ ## What's New in 2.9.0-rc05
6
+
7
+ - **Card header — one canonical structure (`.pa-card__title` + new `.pa-card__description`).** The header no longer has two blessed title shapes (bare `<h3>` vs `.pa-card__title`); `.pa-card__title` is now *the* title contract for every card, with the icon optional inside `.pa-card__title-icon` — so a `<Card>` component emits one DOM tree instead of branching on whether an icon was passed. The description is promoted from a bare `<p>` to a real `.pa-card__description` element, making the header fully `title` / `description` / `actions`.
8
+ - **Card footer — reuses `.pa-card__actions`, auto-pinned to the trailing edge.** Footer buttons go in the same actions slot as the header and hug the right automatically, with or without leading meta/text — no more empty spacer `<div>` to right-align them, and no bare buttons scattering to opposite corners.
9
+ - **Header polish: `--pa-card-description-offset-y` token + descender fix.** The (smaller) description now sits on the title's baseline via a per-theme-tunable nudge, and the title's `line-height` was corrected so glyph descenders (g / y / p) are no longer clipped by the header's overflow.
10
+
5
11
  ## What's New in 2.9.0-rc04
6
12
 
7
13
  - **New: Range Group — a compact multi-range filter (`.pa-range-group` + a reusable `.pa-range` slider).** One toggle summarises several numeric filters inline (`AGE 25–60 / SALARY $40,000–$200,000 / CHILDREN 2+`) and expands into a Floating-UI panel with one slider per dimension — standing in for a row of separate sliders in a filter bar. Per-row dual-thumb range or single-thumb threshold, RTL-correct by construction, with a `--pa-range-*` theming token namespace and a bubbling `change` / `apply` / `reset` event contract keyed by dimension.
@@ -14,15 +20,6 @@ Lightweight, data-focused CSS/SCSS admin framework with Corporate theme as defau
14
20
  - **`.pa-splitter` — tap-without-drag on the gutter no longer restores a rail'd pane.** Restore gestures are now: rail-body click, dblclick on the gutter, focus + `Enter`/`Space`, `[data-pa-splitter-toggle]` button, or drag outward past the snap threshold. Users expected drag-to-be-explicit and didn't connect "tap the gutter" with the rail-pane body as a restore surface.
15
21
  - **`.pa-splitter` — per-pane orientation class** (`pa-splitter__pane--horizontal` / `--vertical`) stamped at registration. Forward-compat hook for theme authors writing nested-splitter-safe rail CSS without walking the DOM to figure out the enclosing splitter's orientation.
16
22
 
17
- ## What's New in 2.9.0-rc03
18
-
19
- - **`.pa-splitter` drag model reworked to "rebalance-on-drag".** Each gutter now resizes only its primary neighbour (edge-closer side, LTR/RTL tiebreaker on ties); the matching delta is absorbed by the immediate adjacent non-rail pane (classic boundary feel), or — when the adjacent pane is rail — tunnels through the rail wall to the contiguous non-rail block beyond. Eliminates the "boundary locks when both sides are rail" deadlock from the prior model — drag-from-rail with a railed neighbour now grows the primary by pulling room from the next section.
20
- - **Middle panes can now minimize to rail** (previously first / last only). Any pane with `data-pa-splitter-minimize` collapses on toggle / dblclick / drag-snap regardless of position. Snap-into-rail enforces an "at least one expanded pane" invariant — you can't drag the last visible pane to rail and end up with nothing to grab.
21
- - **Restoring a minimized pane reclaims layout gaps cleanly.** When a prior minimize hit an absorber's `max` cap and left empty space in the layout, restoring the pane now reaches its remembered size by drawing from the gap (not just from other panes' shrink headroom) — fixes the "restored pane stuck at min width" surprise. Conversely, restoring no longer aggressively inflates a sibling pane to fill remaining gap — empty space stays as empty space and gets consumed as more panes restore. No more "sibling pane suddenly jumps" UX.
22
- - **Rail width in `--pa-splitter-rail-size` now correctly resolves `rem` / non-px units.** Previously `parseFloat("4rem")` returned `4` and snapped rail panes to 4px-wide strips; rebuilt via a hidden probe element so the browser does the unit resolution. The three-step fallback chain (per-instance attribute → CSS var → literal 40) still applies.
23
- - **Gutter highlighting now isolates the dragged gutter only.** During a drag, the splitter `--dragging` modifier suppresses `:hover` / `:focus-visible` on sibling gutters so they don't light up as the cursor passes over them or as a stale focus ring lingers — only the active gutter carries the highlight.
24
- - **Splitter padding subtracted from available space.** Flex children sit inside the splitter's content box but `clientWidth` includes padding — without subtracting it the last pane's `flex-basis` overflowed and got clipped by `overflow: hidden`. Visible in the N-pane demo where the rightmost rail's right edge touched the splitter border.
25
-
26
23
  ## Installation
27
24
 
28
25
  ```bash
package/dist/css/main.css CHANGED
@@ -5471,7 +5471,8 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5471
5471
  text-overflow: ellipsis;
5472
5472
  white-space: nowrap;
5473
5473
  }
5474
- .pa-card__header > p {
5474
+ .pa-card__header > p,
5475
+ .pa-card__header > .pa-card__description {
5475
5476
  flex: 1;
5476
5477
  min-width: 0;
5477
5478
  overflow: hidden;
@@ -5479,6 +5480,8 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5479
5480
  white-space: nowrap;
5480
5481
  color: var(--pa-text-color-2);
5481
5482
  font-size: 1.4rem;
5483
+ position: relative;
5484
+ top: var(--pa-card-description-offset-y, 1px);
5482
5485
  }
5483
5486
  .pa-card__header .pa-card__actions,
5484
5487
  .pa-card__header .pa-btn-group {
@@ -5559,12 +5562,14 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5559
5562
  .pa-card__header--wrap {
5560
5563
  flex-wrap: wrap;
5561
5564
  }
5562
- .pa-card__header--wrap > h1, .pa-card__header--wrap > h2, .pa-card__header--wrap > h3, .pa-card__header--wrap > h4, .pa-card__header--wrap > h5, .pa-card__header--wrap > h6 {
5565
+ .pa-card__header--wrap > h1, .pa-card__header--wrap > h2, .pa-card__header--wrap > h3, .pa-card__header--wrap > h4, .pa-card__header--wrap > h5, .pa-card__header--wrap > h6,
5566
+ .pa-card__header--wrap > .pa-card__title .pa-card__title-text {
5563
5567
  white-space: normal;
5564
5568
  overflow: visible;
5565
5569
  text-overflow: clip;
5566
5570
  }
5567
- .pa-card__header--wrap > p {
5571
+ .pa-card__header--wrap > p,
5572
+ .pa-card__header--wrap > .pa-card__description {
5568
5573
  white-space: normal;
5569
5574
  flex-basis: 100%;
5570
5575
  order: 1;
@@ -5575,7 +5580,7 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5575
5580
  align-items: center;
5576
5581
  gap: 0.8rem;
5577
5582
  min-width: 0;
5578
- flex: 1;
5583
+ flex: 0 1 auto;
5579
5584
  }
5580
5585
  .pa-card__title-icon {
5581
5586
  flex-shrink: 0;
@@ -5590,7 +5595,7 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5590
5595
  color: var(--pa-text-color-1);
5591
5596
  font-size: 1.6rem;
5592
5597
  font-weight: 600;
5593
- line-height: 1;
5598
+ line-height: 1.4;
5594
5599
  }
5595
5600
  .pa-card__body {
5596
5601
  padding: 1.6rem 1rem;
@@ -5611,6 +5616,10 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5611
5616
  display: flex;
5612
5617
  justify-content: space-between;
5613
5618
  align-items: center;
5619
+ gap: 0.8rem;
5620
+ }
5621
+ .pa-card__footer .pa-card__actions {
5622
+ margin-inline-start: auto;
5614
5623
  }
5615
5624
  .pa-card__actions {
5616
5625
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keenmate/pure-admin-core",
3
- "version": "2.9.0-rc04",
3
+ "version": "2.9.0-rc05",
4
4
  "description": "Lightweight, data-focused HTML/CSS admin framework built with PureCSS foundation and comprehensive component system",
5
5
  "style": "dist/css/main.css",
6
6
  "exports": {
@@ -11,10 +11,16 @@
11
11
  </div>
12
12
 
13
13
 
14
- <!-- CARD WITH HEADER -->
14
+ <!-- CARD WITH HEADER
15
+ The title ALWAYS uses .pa-card__title > .pa-card__title-text (the icon
16
+ span is optional). This is the one canonical header title structure —
17
+ a bare <h3> in the header still renders, but is legacy tolerance, not
18
+ the documented shape. -->
15
19
  <div class="pa-card">
16
20
  <div class="pa-card__header">
17
- <h3>Card Title</h3>
21
+ <div class="pa-card__title">
22
+ <h3 class="pa-card__title-text">Card Title</h3>
23
+ </div>
18
24
  </div>
19
25
  <div class="pa-card__body">
20
26
  Card content goes here.
@@ -28,7 +34,9 @@
28
34
  Card content goes here.
29
35
  </div>
30
36
  <div class="pa-card__footer">
31
- <button class="pa-btn pa-btn--primary">Action</button>
37
+ <div class="pa-card__actions">
38
+ <button class="pa-btn pa-btn--primary">Action</button>
39
+ </div>
32
40
  </div>
33
41
  </div>
34
42
 
@@ -36,31 +44,37 @@
36
44
  <!-- COMPLETE CARD (header + body + footer) -->
37
45
  <div class="pa-card">
38
46
  <div class="pa-card__header">
39
- <h3>Complete Card</h3>
47
+ <div class="pa-card__title">
48
+ <h3 class="pa-card__title-text">Complete Card</h3>
49
+ </div>
40
50
  </div>
41
51
  <div class="pa-card__body">
42
52
  <p>This card has all three sections: header, body, and footer.</p>
43
53
  </div>
44
54
  <div class="pa-card__footer">
45
- <button class="pa-btn pa-btn--secondary">Cancel</button>
46
- <button class="pa-btn pa-btn--primary">Save</button>
55
+ <div class="pa-card__actions">
56
+ <button class="pa-btn pa-btn--secondary">Cancel</button>
57
+ <button class="pa-btn pa-btn--primary">Save</button>
58
+ </div>
47
59
  </div>
48
60
  </div>
49
61
 
50
62
 
51
63
  <!-- ================================
52
64
  THREE-PART HEADER LAYOUT
53
- .pa-card__header is a flex row with automatic behaviour:
54
- [title (truncates)] — [description <p> (flexes and truncates)] — [actions (fixed)]
55
- Any h1..h6 / <p> children get ellipsis on overflow so narrow cards
65
+ .pa-card__header is a flex row with three canonical slots:
66
+ [.pa-card__title (truncates)] — [.pa-card__description (flexes, truncates)] — [.pa-card__actions (fixed)]
67
+ The title-text and description get ellipsis on overflow so narrow cards
56
68
  never push content out of the header. Use .pa-card__header--wrap
57
69
  if you'd rather have the text wrap to multiple lines instead.
58
70
  ================================ -->
59
71
 
60
72
  <div class="pa-card">
61
73
  <div class="pa-card__header">
62
- <h3>Dashboard</h3>
63
- <p>Live metrics across every connected service — updated every 30 seconds.</p>
74
+ <div class="pa-card__title">
75
+ <h3 class="pa-card__title-text">Dashboard</h3>
76
+ </div>
77
+ <p class="pa-card__description">Live metrics across every connected service — updated every 30 seconds.</p>
64
78
  <div class="pa-card__actions">
65
79
  <button class="pa-btn pa-btn--sm pa-btn--secondary">Refresh</button>
66
80
  </div>
@@ -71,8 +85,10 @@
71
85
  <!-- Wrap instead of truncate -->
72
86
  <div class="pa-card">
73
87
  <div class="pa-card__header pa-card__header--wrap">
74
- <h3>Wrapping Header</h3>
75
- <p>This description wraps to multiple lines when the header gets narrow, instead of being clipped with an ellipsis.</p>
88
+ <div class="pa-card__title">
89
+ <h3 class="pa-card__title-text">Wrapping Header</h3>
90
+ </div>
91
+ <p class="pa-card__description">This description wraps to multiple lines when the header gets narrow, instead of being clipped with an ellipsis.</p>
76
92
  <div class="pa-card__actions">
77
93
  <button class="pa-btn pa-btn--sm pa-btn--secondary">Action</button>
78
94
  </div>
@@ -90,7 +106,9 @@
90
106
  <!-- Default (accent-coloured underline) -->
91
107
  <div class="pa-card">
92
108
  <div class="pa-card__header pa-card__header--underlined">
93
- <h3>Section</h3>
109
+ <div class="pa-card__title">
110
+ <h3 class="pa-card__title-text">Section</h3>
111
+ </div>
94
112
  </div>
95
113
  <div class="pa-card__body">Body content.</div>
96
114
  </div>
@@ -98,14 +116,18 @@
98
116
  <!-- Semantic colour variants -->
99
117
  <div class="pa-card">
100
118
  <div class="pa-card__header pa-card__header--underlined pa-card__header--underline-success">
101
- <h3>Success underline</h3>
119
+ <div class="pa-card__title">
120
+ <h3 class="pa-card__title-text">Success underline</h3>
121
+ </div>
102
122
  </div>
103
123
  <div class="pa-card__body">Body.</div>
104
124
  </div>
105
125
 
106
126
  <div class="pa-card">
107
127
  <div class="pa-card__header pa-card__header--underlined pa-card__header--underline-danger">
108
- <h3>Danger underline</h3>
128
+ <div class="pa-card__title">
129
+ <h3 class="pa-card__title-text">Danger underline</h3>
130
+ </div>
109
131
  </div>
110
132
  <div class="pa-card__body">Body.</div>
111
133
  </div>
@@ -113,7 +135,9 @@
113
135
  <!-- Theme colour slot (1..9) -->
114
136
  <div class="pa-card">
115
137
  <div class="pa-card__header pa-card__header--underlined pa-card__header--underline-color-3">
116
- <h3>Theme colour 3 underline</h3>
138
+ <div class="pa-card__title">
139
+ <h3 class="pa-card__title-text">Theme colour 3 underline</h3>
140
+ </div>
117
141
  </div>
118
142
  <div class="pa-card__body">Body.</div>
119
143
  </div>
@@ -124,7 +148,7 @@
124
148
  -->
125
149
 
126
150
 
127
- <!-- CARD WITH TITLE COMPONENTS (icon + truncating text) -->
151
+ <!-- CARD WITH TITLE ICON (the icon span is the only optional part) -->
128
152
  <div class="pa-card">
129
153
  <div class="pa-card__header">
130
154
  <div class="pa-card__title">
@@ -136,8 +160,9 @@
136
160
  </div>
137
161
  </div>
138
162
  <div class="pa-card__body">
139
- Use .pa-card__title when you need the title-icon combo + __actions
140
- alignment; h3 alone works too when no icon is needed.
163
+ The title always uses .pa-card__title > .pa-card__title-text; add the
164
+ .pa-card__title-icon span when you want a leading icon. Same structure
165
+ with or without the icon — never a bare <h3>.
141
166
  </div>
142
167
  </div>
143
168
 
@@ -145,7 +170,9 @@
145
170
  <!-- CARD WITH METADATA -->
146
171
  <div class="pa-card">
147
172
  <div class="pa-card__header">
148
- <h3>Article Title</h3>
173
+ <div class="pa-card__title">
174
+ <h3 class="pa-card__title-text">Article Title</h3>
175
+ </div>
149
176
  <span class="pa-card__meta">Published 2 hours ago</span>
150
177
  </div>
151
178
  <div class="pa-card__body">Article content.</div>
@@ -159,7 +186,9 @@
159
186
  Reach for this by default. -->
160
187
  <div class="pa-card">
161
188
  <div class="pa-card__header">
162
- <h3>Card with Actions</h3>
189
+ <div class="pa-card__title">
190
+ <h3 class="pa-card__title-text">Card with Actions</h3>
191
+ </div>
163
192
  <div class="pa-card__actions">
164
193
  <button class="pa-btn pa-btn--sm pa-btn--secondary">Cancel</button>
165
194
  <button class="pa-btn pa-btn--sm pa-btn--success">Save</button>
@@ -176,7 +205,9 @@
176
205
  the attached-button styling in whether you want it or not. -->
177
206
  <div class="pa-card">
178
207
  <div class="pa-card__header">
179
- <h3>Card with Segmented Actions</h3>
208
+ <div class="pa-card__title">
209
+ <h3 class="pa-card__title-text">Card with Segmented Actions</h3>
210
+ </div>
180
211
  <div class="pa-card__actions">
181
212
  <div class="pa-btn-group">
182
213
  <button class="pa-btn pa-btn--sm pa-btn--secondary">Day</button>
@@ -192,11 +223,12 @@
192
223
  <!-- CARD WITH FOOTER ACTIONS -->
193
224
  <div class="pa-card">
194
225
  <div class="pa-card__header">
195
- <h3>Form Card</h3>
226
+ <div class="pa-card__title">
227
+ <h3 class="pa-card__title-text">Form Card</h3>
228
+ </div>
196
229
  </div>
197
230
  <div class="pa-card__body">Form content here.</div>
198
231
  <div class="pa-card__footer">
199
- <div></div>
200
232
  <div class="pa-card__actions">
201
233
  <button class="pa-btn pa-btn--secondary">Cancel</button>
202
234
  <button class="pa-btn pa-btn--success">Save</button>
@@ -208,7 +240,9 @@
208
240
  <!-- CARD WITH NO-PADDING BODY (for flush tables / lists) -->
209
241
  <div class="pa-card">
210
242
  <div class="pa-card__header">
211
- <h3>User List</h3>
243
+ <div class="pa-card__title">
244
+ <h3 class="pa-card__title-text">User List</h3>
245
+ </div>
212
246
  </div>
213
247
  <div class="pa-card__body pa-card__body--no-padding">
214
248
  <table class="pa-table">
@@ -378,7 +412,9 @@
378
412
  <!-- Inline tabs — pills that live in the header row -->
379
413
  <div class="pa-card">
380
414
  <div class="pa-card__header">
381
- <h3>Sales</h3>
415
+ <div class="pa-card__title">
416
+ <h3 class="pa-card__title-text">Sales</h3>
417
+ </div>
382
418
  <div class="pa-card__tabs pa-card__tabs--inline">
383
419
  <button class="pa-card__tab pa-card__tab--active">Day</button>
384
420
  <button class="pa-card__tab">Week</button>
@@ -396,7 +432,9 @@
396
432
  identical, but the entire area becomes clickable. -->
397
433
  <a href="/products/42" class="pa-card">
398
434
  <div class="pa-card__header">
399
- <h3>Widget Pro</h3>
435
+ <div class="pa-card__title">
436
+ <h3 class="pa-card__title-text">Widget Pro</h3>
437
+ </div>
400
438
  </div>
401
439
  <div class="pa-card__body">
402
440
  <p>Click anywhere on this card to navigate.</p>
@@ -498,12 +536,23 @@ CLICKABLE CARD:
498
536
  looks identical to a regular card.
499
537
 
500
538
  SUB-ELEMENTS:
501
- - .pa-card__header Flex row: [title] [description] — [actions].
502
- Direct h1..h6 and <p> children auto-truncate.
503
- Native tags inside get margin/padding/border
504
- reset so they inherit the header layout cleanly.
539
+ - .pa-card__header Flex row of three canonical slots:
540
+ [.pa-card__title] [.pa-card__description]
541
+ [.pa-card__actions]. (Bare h1..h6 / <p> children
542
+ still render + auto-truncate as legacy tolerance,
543
+ but the documented shape is the slots above —
544
+ one structure, so a <Card> wrapper emits one tree.)
545
+ - .pa-card__description Muted, flexing, truncating description line —
546
+ the header's middle slot. Bare <p> tolerated.
505
547
  - .pa-card__body flex: 1; standard padding.
506
548
  - .pa-card__footer Border-top + footer background; flex row.
549
+ Canonical content: optional leading text /
550
+ .pa-card__meta on the left + .pa-card__actions
551
+ on the right. The actions slot auto-pins to the
552
+ trailing edge (no empty spacer <div> needed),
553
+ with or without leading content. Always put
554
+ footer buttons inside .pa-card__actions so they
555
+ don't scatter to opposite corners.
507
556
  - .pa-card__actions THE canonical header actions slot. Button/
508
557
  control container (gap + align-center);
509
558
  flex-shrink: 0 inside header. Use this for any
@@ -514,11 +563,14 @@ SUB-ELEMENTS:
514
563
  the actions slot).
515
564
  - .pa-card__meta Muted secondary text (dates, byline, etc.).
516
565
 
517
- TITLE SUB-COMPONENT:
518
- - .pa-card__title Flex row wrapping icon + text; flex: 1 so
519
- the actions sibling stays on the right.
520
- - .pa-card__title-icon Fixed-size icon.
521
- - .pa-card__title-text Truncates with ellipsis on overflow.
566
+ TITLE SUB-COMPONENT (the canonical header title — use it for EVERY card):
567
+ - .pa-card__title Flex row wrapping the optional icon + text;
568
+ flex: 0 1 auto (natural width) so a sibling
569
+ .pa-card__description gets the free space and
570
+ .pa-card__actions stays right. Always present,
571
+ with or without an icon — never a bare <h3>.
572
+ - .pa-card__title-icon Fixed-size leading icon (optional).
573
+ - .pa-card__title-text The heading (h1..h6); truncates with ellipsis.
522
574
 
523
575
  BODY MODIFIER:
524
576
  - .pa-card__body--no-padding Strips body padding (for tables / lists
@@ -556,10 +608,9 @@ SECTION WRAPPERS (lighter than a card, for heading-only groupings):
556
608
  underline, no container behaviour.
557
609
 
558
610
  IMPLICIT BEHAVIOURS:
559
- - The header is flex with auto-truncation: direct h1..h6 and <p>
560
- children get white-space: nowrap + text-overflow: ellipsis. This
561
- is why long card titles look "cut off" in narrow columns — use
562
- --wrap to opt out.
611
+ - The header truncates: .pa-card__title-text and .pa-card__description
612
+ get white-space: nowrap + text-overflow: ellipsis. This is why long
613
+ card titles look "cut off" in narrow columns — use --wrap to opt out.
563
614
  - Buttons inside the header get -0.25rem vertical margin so they
564
615
  don't grow the header height past $card-header-min-height.
565
616
  - Card body :first-child gets margin-top: 0 to avoid double-spacing
@@ -570,8 +621,10 @@ STRUCTURE PATTERNS:
570
621
  Card with three-part header:
571
622
  <div class="pa-card">
572
623
  <div class="pa-card__header">
573
- <h3>Title</h3>
574
- <p>Description (truncates)</p>
624
+ <div class="pa-card__title">
625
+ <h3 class="pa-card__title-text">Title</h3>
626
+ </div>
627
+ <p class="pa-card__description">Description (truncates)</p>
575
628
  <div class="pa-card__actions">
576
629
  <button class="pa-btn pa-btn--sm">Action</button>
577
630
  </div>
@@ -582,7 +635,9 @@ Card with three-part header:
582
635
  Card with underlined header (semantic variant):
583
636
  <div class="pa-card">
584
637
  <div class="pa-card__header pa-card__header--underlined pa-card__header--underline-danger">
585
- <h3>Critical Settings</h3>
638
+ <div class="pa-card__title">
639
+ <h3 class="pa-card__title-text">Critical Settings</h3>
640
+ </div>
586
641
  </div>
587
642
  <div class="pa-card__body">…</div>
588
643
  </div>
@@ -83,8 +83,11 @@
83
83
  white-space: nowrap;
84
84
  }
85
85
 
86
- // Description paragraphs - flexible middle, truncate with ellipsis
87
- > p {
86
+ // Description - flexible middle, truncate with ellipsis.
87
+ // `.pa-card__description` is the canonical element; bare `> p` is kept as
88
+ // legacy tolerance so hand-written markup doesn't break.
89
+ > p,
90
+ > .pa-card__description {
88
91
  flex: 1;
89
92
  min-width: 0;
90
93
  overflow: hidden;
@@ -92,6 +95,12 @@
92
95
  white-space: nowrap;
93
96
  color: var(--pa-text-color-2);
94
97
  font-size: $font-size-sm;
98
+ // Nudge the smaller description onto the title's baseline (see
99
+ // $card-description-offset-y). `position: relative` keeps it in flow —
100
+ // truncation/flex sizing are unaffected — it only shifts the paint.
101
+ // Font-metric dependent, so themes can retune via the CSS variable.
102
+ position: relative;
103
+ top: var(--pa-card-description-offset-y, #{$card-description-offset-y});
95
104
  }
96
105
 
97
106
  // Actions container - fixed, doesn't shrink
@@ -243,13 +252,18 @@
243
252
  &--wrap {
244
253
  flex-wrap: wrap;
245
254
 
246
- > h1, > h2, > h3, > h4, > h5, > h6 {
255
+ // Un-truncate both the bare-heading shorthand AND the canonical
256
+ // `.pa-card__title` > `.pa-card__title-text` so titles wrap (not
257
+ // ellipsis) in wrap mode regardless of which header shape is used.
258
+ > h1, > h2, > h3, > h4, > h5, > h6,
259
+ > .pa-card__title .pa-card__title-text {
247
260
  white-space: normal;
248
261
  overflow: visible;
249
262
  text-overflow: clip;
250
263
  }
251
264
 
252
- > p {
265
+ > p,
266
+ > .pa-card__description {
253
267
  white-space: normal;
254
268
  flex-basis: 100%;
255
269
  order: 1;
@@ -263,7 +277,11 @@
263
277
  align-items: center;
264
278
  gap: $spacing-sm;
265
279
  min-width: 0; // Enable text truncation
266
- flex: 1;
280
+ // Natural width (like a bare heading), shrinkable, no grow — so a sibling
281
+ // `.pa-card__description` gets the free space instead of the title hogging
282
+ // half the row. `justify-content: space-between` on the header still pins
283
+ // `.pa-card__actions` to the right.
284
+ flex: 0 1 auto;
267
285
 
268
286
  &-icon {
269
287
  flex-shrink: 0;
@@ -279,7 +297,9 @@
279
297
  color: var(--pa-text-color-1);
280
298
  font-size: $font-size-base;
281
299
  font-weight: $font-weight-semibold;
282
- line-height: 1;
300
+ // Not `1` — line-height 1 clips glyph descenders (g, y, p) under
301
+ // `overflow: hidden`. Matches the room bare headings already leave.
302
+ line-height: 1.4;
283
303
  }
284
304
  }
285
305
 
@@ -306,6 +326,18 @@
306
326
  display: flex;
307
327
  justify-content: space-between;
308
328
  align-items: center;
329
+ gap: $spacing-sm;
330
+
331
+ // Canonical footer content model (mirrors the header): optional leading
332
+ // content on the left — text or `.pa-card__meta` — plus `.pa-card__actions`
333
+ // on the right. The auto inline-start margin pins the actions slot to the
334
+ // trailing edge whether or not there's leading content, so an actions-only
335
+ // footer no longer needs an empty spacer `<div>` to fight `space-between`.
336
+ // Buttons live inside `.pa-card__actions` (the same slot as the header) so
337
+ // they never scatter to opposite corners the way bare footer buttons do.
338
+ .pa-card__actions {
339
+ margin-inline-start: auto;
340
+ }
309
341
  }
310
342
 
311
343
  &__actions {
@@ -176,6 +176,13 @@ $card-body-padding-h: 1rem !default; // 10px horizontal
176
176
  $card-header-padding-v: 0.5rem !default; // 5px vertical
177
177
  $card-header-padding-h: 1rem !default; // 10px horizontal - matches body
178
178
  $card-header-min-height: 4rem !default; // 40px - fits xs buttons (32px) with padding
179
+ // Vertical nudge that drops the (smaller) header description onto the title's
180
+ // baseline. Under the header's center alignment a 16px title and 14px
181
+ // description don't share a baseline, leaving the description ~1px high. The
182
+ // exact correction is font-metric dependent, so it's exposed as a variable
183
+ // (SCSS default here + `--pa-card-description-offset-y` at runtime) that themes
184
+ // with a different font can retune. Set to 0 to disable the nudge entirely.
185
+ $card-description-offset-y: 1px !default;
179
186
  $card-footer-padding-v: 1.2rem !default; // 12px vertical
180
187
  $card-footer-padding-h: 1rem !default; // 10px horizontal - matches body
181
188
  $card-grid-min-width: 280px !default;