@posiwise/core-styles 1.0.15 → 1.0.17

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/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
- "name": "@posiwise/core-styles",
3
- "version": "1.0.15",
4
- "main": "index.js",
5
- "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
7
- },
8
- "author": "",
9
- "license": "ISC",
10
- "description": ""
2
+ "name": "@posiwise/core-styles",
3
+ "version": "1.0.17",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "author": "",
9
+ "license": "ISC",
10
+ "description": ""
11
11
  }
@@ -46,6 +46,25 @@
46
46
  --p-autocomplete-focus-border-color: var(--first);
47
47
  --p-autocomplete-hover-border-color: var(--first);
48
48
 
49
+ // PrimeNG placeholder color tokens — calibrated 2026-05-21 to design-system
50
+ // N400 (#a8afbc) for cross-widget placeholder uniformity. PrimeNG's styled-
51
+ // mode injects a later `:root, :host` block that re-sets
52
+ // `--p-form-field-placeholder-color: var(--p-surface-500)` (and chains
53
+ // autocomplete/select/multiselect tokens off it) at equal specificity,
54
+ // so source order alone loses — `!important` on the custom-property
55
+ // declaration is the stable lever (CSS vars with !important win across
56
+ // later equal-specificity redefinitions). Owning the token avoids chasing
57
+ // per-widget selector specificity (e.g.
58
+ // `.p-autocomplete-input-chip input::placeholder`). Mirrors the Bootstrap
59
+ // `.form-control::placeholder` color so all five rendering paths
60
+ // (Bootstrap input, p-autocomplete chip input, p-multiselect label,
61
+ // p-select label, p-inputtext) match.
62
+ --p-form-field-placeholder-color: #a8afbc !important;
63
+ --p-inputtext-placeholder-color: #a8afbc !important;
64
+ --p-autocomplete-placeholder-color: #a8afbc !important;
65
+ --p-select-placeholder-color: #a8afbc !important;
66
+ --p-multiselect-placeholder-color: #a8afbc !important;
67
+
49
68
  // PrimeNG Checkbox checked CSS variables override
50
69
  --p-checkbox-checked-background: var(--first);
51
70
  --p-checkbox-checked-border-color: var(--first);
@@ -261,6 +280,478 @@
261
280
  .primeng-datatable-container table thead tr th {
262
281
  background-color: var(--tabs_bg) !important;
263
282
  color: var(--tabs_text) !important;
283
+ font-size: 11px;
284
+ font-weight: 600;
285
+ letter-spacing: 0.04em;
286
+ text-transform: uppercase;
287
+ // Convention (2026-05-21): header label + sort icon ALWAYS on one
288
+ // line, vertically centred in the header cell.
289
+ white-space: nowrap;
290
+ vertical-align: middle !important;
291
+ }
292
+
293
+ // Sort icon vertical centring with the header text.
294
+ .primeng-datatable-container table thead tr th p-sorticon,
295
+ .primeng-datatable-container table thead tr th .p-sortable-column-icon {
296
+ vertical-align: middle;
297
+ }
298
+
299
+ // Global focus halo (brand accent) for every PrimeNG input, multiselect,
300
+ // and select on the platform — was previously scoped to .search-filter
301
+ // which left ad-hoc filter rows (e.g. newsletter/items) without any
302
+ // focus affordance. !important defeats the legacy `body .p-inputtext`
303
+ // focus rule. Codified 2026-05-21.
304
+ .p-inputtext:enabled:focus,
305
+ .p-multiselect:focus-within,
306
+ .p-select:focus-within,
307
+ .form-control:focus:not([readonly]):not(:disabled) {
308
+ border-color: var(--first) !important;
309
+ outline: 0;
310
+ box-shadow: 0 0 0 3px rgba(24, 104, 225, 0.18) !important;
311
+ }
312
+
313
+ // Some views (newsletter/items, etc.) place their filter row OUTSIDE
314
+ // the p-table's caption — as a sibling .row above the table — so the
315
+ // 10px filter→thead gap owned by .p-datatable-header doesn't apply.
316
+ // Bridge that with a matching margin below any pre-table .row inside
317
+ // the datatable container.
318
+ .primeng-datatable-container > .row {
319
+ margin-bottom: 10px;
320
+ }
321
+
322
+ // PrimeNG p-autocomplete — align with the 36px / N200 widget convention.
323
+ // Was rendering at 32.64px with a `rgb(128,128,128)` border (its own
324
+ // PrimeNG default), distinct from p-select / p-multiselect.
325
+ .p-autocomplete .p-inputtext,
326
+ p-autocomplete .p-inputtext,
327
+ .p-autocomplete-input {
328
+ min-height: 36px;
329
+ border-color: #e2e5ea !important;
330
+ }
331
+
332
+ // PrimeNG p-datepicker — align input height (was 32.64px) and border
333
+ // colour (was #C4C4C4) with the platform's 36px / N200 widget convention.
334
+ .p-datepicker .p-inputtext,
335
+ p-datepicker .p-inputtext {
336
+ min-height: 36px;
337
+ border-color: #e2e5ea !important;
338
+ }
339
+
340
+ // Calendar trigger button next to the p-datepicker input — same 36px
341
+ // height to keep the input-group flush.
342
+ .p-datepicker .p-button,
343
+ p-datepicker .p-button {
344
+ min-height: 36px;
345
+ }
346
+
347
+ // ngx-daterangepicker buttons (trigger calendar icon next to the input,
348
+ // + APPLY button inside the dropdown) — make them match the platform's
349
+ // 36px-tall primary-button convention so they align with adjacent inputs
350
+ // and read as regular Submit buttons.
351
+ .ngx-daterangepicker-action {
352
+ min-height: 36px !important;
353
+ padding: 0 12px !important;
354
+ display: inline-flex !important;
355
+ align-items: center;
356
+ justify-content: center;
357
+ border-radius: 6px !important;
358
+ font-size: 13px;
359
+ font-weight: 500;
360
+ }
361
+
362
+ // Quill rich-text editor (ngx-quill / Snow theme): unify the toolbar +
363
+ // container bg/border with the platform input convention (white bg,
364
+ // N200 border, 6px radius split between top/bottom halves).
365
+ .ql-toolbar.ql-snow,
366
+ .ql-container.ql-snow {
367
+ border-color: #e2e5ea !important;
368
+ background-color: #fff !important;
369
+ }
370
+ .ql-toolbar.ql-snow {
371
+ border-radius: 6px 6px 0 0 !important;
372
+ }
373
+ .ql-container.ql-snow {
374
+ border-radius: 0 0 6px 6px !important;
375
+ }
376
+ .ql-editor {
377
+ background-color: #fff !important;
378
+ }
379
+
380
+ // Global `.form-control` defaults — same as the `form > .form-control`
381
+ // rule in `custom-bootstrap/_forms.scss`, but unscoped so inputs OUTSIDE
382
+ // a <form> (date pickers, list filters, ad-hoc widgets) inherit it too.
383
+ // Codified 2026-05-21 after the newsletter-insight date-range picker was
384
+ // found rendering with transparent bg + 4.875px radius because it lives
385
+ // outside a <form>.
386
+ .form-control {
387
+ border: 1px solid #e2e5ea !important;
388
+ border-radius: 6px !important;
389
+ background-color: #fff !important;
390
+ min-height: 36px;
391
+ }
392
+
393
+ // Disabled / readonly form-control: distinguishable bg, no focus halo.
394
+ .form-control:disabled,
395
+ .form-control[readonly] {
396
+ background-color: #eceff1 !important;
397
+ }
398
+ .form-control[readonly]:focus,
399
+ .form-control:disabled:focus {
400
+ border-color: #e2e5ea !important;
401
+ box-shadow: none !important;
402
+ outline: 0;
403
+ }
404
+
405
+ // Navbar global Search: match the 6px rounded corners + N200 border the
406
+ // rest of the platform uses (input-group default zeroes radii between
407
+ // children — re-apply on the outer edges).
408
+ .navbar-search-input {
409
+ .form-control {
410
+ border-top-left-radius: 6px !important;
411
+ border-bottom-left-radius: 6px !important;
412
+ border-top-right-radius: 0 !important;
413
+ border-bottom-right-radius: 0 !important;
414
+ }
415
+
416
+ .input-group-text {
417
+ border-top-right-radius: 6px !important;
418
+ border-bottom-right-radius: 6px !important;
419
+ border-top-left-radius: 0 !important;
420
+ border-bottom-left-radius: 0 !important;
421
+ }
422
+ }
423
+
424
+ // Collapsed Action column (Linear/Notion pattern, codified 2026-05-21).
425
+ // The Action column reserves no horizontal space at rest — header text
426
+ // is hidden, th + td collapse to 0 width, and the <ul> floats over the
427
+ // row's right edge on row hover. Soft 80ms exit delay so quick row-to-row
428
+ // mouse movements don't flicker the strip in/out.
429
+ //
430
+ // Covers all `actions-list-{two,three,four}` variants. Per-view local
431
+ // SCSS should NOT redeclare this — pattern lives in `admin-list-view.md`.
432
+ .primeng-datatable-container {
433
+ th[class*='actions-list-'],
434
+ td[data-head='Action'] {
435
+ width: 0 !important;
436
+ min-width: 0 !important;
437
+ padding: 0 !important;
438
+ max-width: 0 !important;
439
+ }
440
+
441
+ th[class*='actions-list-'] {
442
+ font-size: 0 !important;
443
+ color: transparent !important;
444
+ // Visually erase the column header text — but keep the bg blue
445
+ // so the residual ~20px doesn't read as a gap in the blue
446
+ // header bar. (Updated 2026-05-21 — prior transparent bg
447
+ // produced a visible gap after DELETED in admin lists.)
448
+ border-color: transparent !important;
449
+ }
450
+
451
+ td[data-head='Action'] {
452
+ border-left-color: transparent !important; // hide the visual seam between Action td and prev cell
453
+ border-right-color: transparent !important; // hide the body right edge of the residual action column
454
+ }
455
+
456
+ // Hide the right border of whatever data column sits immediately
457
+ // before the action column, so the empty 22px action cell merges
458
+ // seamlessly into the row instead of reading as a stray column.
459
+ // (Added 2026-05-21.)
460
+ td:has(+ td[data-head='Action']) {
461
+ border-right-color: transparent !important;
462
+ }
463
+
464
+ // Force the action column body cell to actually collapse — a
465
+ // per-view component-scoped rule (.table-responsive[ng-attr] td)
466
+ // sets `padding: 6px 10px !important` with equal specificity to
467
+ // the global rule, so source-order lets it win and the action
468
+ // td stays ~22px wide. Doubling the attribute selector wins
469
+ // specificity (1 extra attribute) and forces padding 0 so the
470
+ // visible "extra column" disappears. (Added 2026-05-21.)
471
+ td[data-head='Action'][data-head],
472
+ th[class*='actions-list-'][class*='actions-list-'] {
473
+ padding: 0 !important;
474
+ border-width: 0 !important; // collapse the residual 2px from transparent borders
475
+ }
476
+
477
+ td[data-head='Action'] {
478
+ position: relative;
479
+ overflow: visible !important;
480
+ }
481
+
482
+ td[data-head='Action'] ul {
483
+ position: absolute;
484
+ right: 8px;
485
+ // Anchor to row's top/bottom (with small inset) so the strip
486
+ // height tracks the row. Avoids overlapping the pagination
487
+ // footer when there's only one short row in the table.
488
+ top: 2px;
489
+ bottom: 2px;
490
+ transform: translateX(4px);
491
+ display: flex;
492
+ align-items: center;
493
+ opacity: 0;
494
+ background: rgba(255, 255, 255, 0.96);
495
+ padding: 0 6px !important; // no vertical padding — height comes from top/bottom anchors
496
+ margin: 0 !important;
497
+ border-radius: 8px;
498
+ box-shadow: -8px 0 16px -6px rgba(0, 0, 0, 0.08);
499
+ z-index: 10; // float above the pagination footer
500
+ transition:
501
+ opacity 180ms ease 80ms,
502
+ transform 180ms ease 80ms;
503
+
504
+ li {
505
+ display: inline-flex !important;
506
+ align-items: center;
507
+ justify-content: center;
508
+ width: 34px;
509
+ height: 34px;
510
+ border-radius: 6px;
511
+ margin: 0 2px !important;
512
+ cursor: pointer;
513
+ transition:
514
+ background-color 120ms ease,
515
+ color 120ms ease;
516
+ }
517
+
518
+ li:hover {
519
+ background-color: #eef0f3;
520
+ }
521
+
522
+ i.fa {
523
+ font-size: 16px !important;
524
+ padding: 0 !important;
525
+ line-height: 1;
526
+ }
527
+
528
+ .edit-icon,
529
+ .cta1-icon {
530
+ color: #5a6473 !important;
531
+ }
532
+ .delete-icon {
533
+ color: #c0392b !important;
534
+ }
535
+ li:hover .edit-icon,
536
+ li:hover .cta1-icon {
537
+ color: #1a1d21 !important;
538
+ }
539
+ }
540
+
541
+ tr:hover td[data-head='Action'] ul,
542
+ tr:focus-within td[data-head='Action'] ul {
543
+ opacity: 1;
544
+ transform: translateX(0);
545
+ transition:
546
+ opacity 120ms ease,
547
+ transform 120ms ease;
548
+ }
549
+ }
550
+
551
+ // Active-sort column background: PrimeNG's preset uses its own
552
+ // hardcoded primary; bind it to --first so the sorted column tracks
553
+ // the tenant accent (consistent with .p-datatable-thead default bg).
554
+ .primeng-datatable-container table thead tr th.p-datatable-column-sorted,
555
+ .primeng-datatable-container table thead tr th[aria-sort='ascending'],
556
+ .primeng-datatable-container table thead tr th[aria-sort='descending'] {
557
+ background-color: var(--first) !important;
558
+ }
559
+
560
+ // Canonical body cell font-size — 13px (comfortable read default).
561
+ // Admin views that need analyst-grade density (products, subscriptions)
562
+ // override locally to 11px in their component SCSS via the
563
+ // `.table-responsive { @media (min-width: 767px) { td, th { 11px !important } } }`
564
+ // block. Codified 2026-05-21.
565
+ .primeng-datatable-container table tbody tr td {
566
+ font-size: 13px;
567
+ }
568
+
569
+ // Global row-hover affordance — every admin data table gets the same
570
+ // "cursor here" highlight. Tracks the mouse as the user scrolls.
571
+ .primeng-datatable-container table tbody tr {
572
+ transition: background-color 120ms ease;
573
+ }
574
+
575
+ .primeng-datatable-container table tbody tr:hover {
576
+ background-color: #eef0f3 !important;
577
+ }
578
+
579
+ // Soft-tint admin-table badges (lifted 2026-05-21 from
580
+ // subscriptions-list.component.scss per admin-list-view pattern
581
+ // §B-1/B-3: badges MUST be soft-tinted (light bg + dark ink), never
582
+ // saturated colour-only fills with white text). Scoped under the
583
+ // datatable container so non-table badge usages (modals, dashboards,
584
+ // navbars) keep the saturated Bootstrap globals untouched.
585
+ // `class*='bg-*'` catches indexed variants emitted by appDynamicBadge
586
+ // (bg-blue-grey-1, bg-warning-2, …). `!important` matches Bootstrap's
587
+ // `.bg-*` utility-class specificity.
588
+ .primeng-datatable-container .badge {
589
+ font-size: 11px;
590
+ font-weight: 600;
591
+ padding: 2px 8px;
592
+ border-radius: 4px;
593
+ letter-spacing: 0.01em;
594
+ }
595
+
596
+ .primeng-datatable-container .badge[class*='bg-success'] {
597
+ background-color: #e6f4ec !important;
598
+ color: #0f6b3a !important;
599
+ }
600
+
601
+ .primeng-datatable-container .badge[class*='bg-danger'] {
602
+ background-color: #fbeae8 !important;
603
+ color: #c0392b !important;
604
+ }
605
+
606
+ .primeng-datatable-container .badge[class*='bg-warning'] {
607
+ background-color: #fbf1e3 !important;
608
+ color: #8a5a00 !important;
609
+ }
610
+
611
+ .primeng-datatable-container .badge[class*='bg-blue-grey'] {
612
+ background-color: #eef0f3 !important;
613
+ color: #5a6473 !important;
614
+ }
615
+
616
+ // Indexed soft-tint variants for appDynamicBadge categorical
617
+ // differentiation (e.g. shard name, frontend repo). The bare
618
+ // `.bg-blue-grey` above stays neutral; the indexed `-1 … -24`
619
+ // variants emitted by appDynamicBadge each get a distinct hue so
620
+ // multi-value columns (shard tags, category chips) stay scannable.
621
+ // HSL ramp: 15° rotation per index, low saturation + high lightness
622
+ // keeps every variant in the calm soft-tint register per the design
623
+ // system's "calm under data load" Admin profile. Specificity ties
624
+ // with the substring rule above; comes after in source order so the
625
+ // indexed rule wins for `bg-blue-grey-N`.
626
+ @for $i from 1 through 24 {
627
+ .primeng-datatable-container .badge.bg-blue-grey-#{$i} {
628
+ background-color: hsl($i * 15, 18%, 92%) !important;
629
+ color: hsl($i * 15, 32%, 35%) !important;
630
+ }
631
+ }
632
+
633
+ // Shape dot per B-2 — accessibility beyond colour alone.
634
+ .primeng-datatable-container .badge[class*='bg-success']::before,
635
+ .primeng-datatable-container .badge[class*='bg-danger']::before,
636
+ .primeng-datatable-container .badge[class*='bg-warning']::before {
637
+ content: '';
638
+ display: inline-block;
639
+ width: 6px;
640
+ height: 6px;
641
+ margin-right: 6px;
642
+ border-radius: 50%;
643
+ background-color: currentColor;
644
+ vertical-align: middle;
645
+ }
646
+
647
+ // Form-widget unification (2026-05-21): every PrimeNG p-select +
648
+ // p-multiselect on this platform shares the same border colour and
649
+ // min-height as text inputs (.form-control = 36px, N200 border, 6px
650
+ // radius — see custom-bootstrap/_forms.scss). Without this, the dropdowns
651
+ // are 1.5px taller and use a slightly different gray border, breaking
652
+ // form alignment when widgets sit side-by-side.
653
+ .p-select,
654
+ .p-multiselect {
655
+ border-color: #e2e5ea !important;
656
+ min-height: 36px;
657
+ }
658
+
659
+ // PrimeNG p-iconfield wraps the input + leading icon. Platform convention
660
+ // (2026-05-21): search boxes are tidy, right-aligned, capped at 240px so
661
+ // they never dominate the row. The input fills the iconfield so the icon
662
+ // overlays its left padding cleanly.
663
+ //
664
+ // EXCLUDE PrimeNG's internal iconfields (multiselect/select panel filter
665
+ // inputs use this same component — they live inside `.p-*-filter-container`
666
+ // and must keep their own panel-fitting width). Without this exclusion the
667
+ // 240px cap + margin-left:auto pushes the internal filter to the right
668
+ // edge of the panel, leaving it visually broken.
669
+ p-iconfield:not([class*='filter-container']) {
670
+ display: block;
671
+ width: 100%;
672
+ max-width: 360px;
673
+ margin-left: auto;
674
+
675
+ input,
676
+ .p-inputtext {
677
+ width: 100%;
678
+ }
679
+ }
680
+
681
+ // ── Filter row unification ── platform-wide: every admin filter row
682
+ // (subscriptions / users / products / faqs / resources …) uses the same
683
+ // equal-width flex layout + 38px control height so the multi-select /
684
+ // select / search input no longer have different default heights.
685
+ .search-filter {
686
+ display: flex;
687
+ flex-wrap: wrap;
688
+ align-items: stretch;
689
+ gap: 12px;
690
+ // Spacing to the table head is now owned by .p-datatable-header
691
+ // (single source of truth for filter→thead gap). No margin here.
692
+
693
+ > div {
694
+ flex: 1 1 220px;
695
+ min-width: 0;
696
+ // Cap each filter column at 360px (was 240px — bumped 2026-05-21
697
+ // for more comfortable widget width). Tidy analyst-chip row;
698
+ // prevents a 2-col view from giving each widget 50% of the row.
699
+ max-width: 360px;
700
+ }
701
+
702
+ .p-inputtext {
703
+ width: 100%;
704
+ height: 38px;
705
+ max-width: none;
706
+ // N200 line token — unified filter-widget border
707
+ border: 1px solid #e2e5ea;
708
+ border-radius: 6px;
709
+ }
710
+
711
+ .p-multiselect,
712
+ .p-select {
713
+ width: 100%;
714
+ height: 38px;
715
+ border-radius: 6px;
716
+ display: flex;
717
+ align-items: center;
718
+ // N200 line token — unified filter-widget border
719
+ border: 1px solid #e2e5ea;
720
+ }
721
+
722
+ // Focus state — applied uniformly to multiselect, select, and the
723
+ // search input. Uses the brand accent (--first) as a spotlight per
724
+ // doctrine § 2 (accent is for stateful focus, not structural chrome).
725
+ // :focus-within on the wrapper catches PrimeNG's internal-trigger
726
+ // focus too. !important defeats the legacy
727
+ // `body .p-inputtext:enabled:focus:not(.ui-state-error):not(.p-invalid)`
728
+ // rule whose specificity outranks ours. Codified 2026-05-21.
729
+ .p-inputtext:enabled:focus,
730
+ .p-multiselect:focus-within,
731
+ .p-select:focus-within {
732
+ border-color: var(--first) !important;
733
+ outline: 0;
734
+ box-shadow: 0 0 0 3px rgba(24, 104, 225, 0.18) !important;
735
+ }
736
+
737
+ .p-multiselect-label,
738
+ .p-select-label {
739
+ display: flex;
740
+ align-items: center;
741
+ padding-block: 0;
742
+ line-height: 1;
743
+ }
744
+ }
745
+
746
+ @media (max-width: 720px) {
747
+ .search-filter {
748
+ flex-direction: column;
749
+ gap: 8px;
750
+
751
+ > div {
752
+ flex: 1 1 auto;
753
+ }
754
+ }
264
755
  }
265
756
 
266
757
  .analytics {
@@ -417,6 +908,21 @@
417
908
  }
418
909
 
419
910
  .p-autocomplete-input-multiple {
911
+ // Chip container <ul> was rendering taller than sibling p-multiselect /
912
+ // p-select widgets: its own vertical padding stacked on top of the inner
913
+ // input's 36px min-height. Pin the ul to 36px and zero its vertical
914
+ // padding so the inner input fills it instead of adding to it.
915
+ min-height: 36px;
916
+ padding-top: 0 !important;
917
+ padding-bottom: 0 !important;
918
+ align-items: center;
919
+
920
+ .p-autocomplete-input,
921
+ .p-autocomplete-input-chip input,
922
+ .p-autocomplete-input-chip .p-inputtext {
923
+ min-height: 0 !important;
924
+ }
925
+
420
926
  &:hover {
421
927
  border-color: var(--first) !important;
422
928
  }
@@ -1051,6 +1557,64 @@ p-datepicker .p-datepicker-dropdown,
1051
1557
  }
1052
1558
  }
1053
1559
 
1560
+ // Dropdown overlay panel — option truncation
1561
+ // Pattern: factory/patterns/dropdown-option-truncation.md
1562
+ // Caps overlay width; truncates each option with ellipsis. Components SHOULD
1563
+ // provide an item template that sets [title]="<label>" so the browser's
1564
+ // native tooltip reveals the full value on hover (D-3 / D-4).
1565
+ .p-select-overlay,
1566
+ .p-autocomplete-overlay,
1567
+ .p-dropdown-panel {
1568
+ max-width: min(420px, calc(100vw - 32px));
1569
+
1570
+ .p-select-option,
1571
+ .p-autocomplete-option,
1572
+ .p-dropdown-item {
1573
+ display: block;
1574
+ max-width: 100%;
1575
+ overflow: hidden;
1576
+ text-overflow: ellipsis;
1577
+ white-space: nowrap;
1578
+
1579
+ // Covers templated item content wrapped in <span> / <div>
1580
+ > * {
1581
+ max-width: 100%;
1582
+ overflow: hidden;
1583
+ text-overflow: ellipsis;
1584
+ white-space: nowrap;
1585
+ display: inline-block;
1586
+ vertical-align: middle;
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ // Multiselect overlay — same width cap, but options keep flex layout so the
1592
+ // checkbox and label have a natural gap. Using `display: block` + inline-block
1593
+ // children (as we do for select/autocomplete) collapses the checkbox-to-label
1594
+ // space. Codified 2026-05-21 after `/admin/tracking/events` Filter-by-users
1595
+ // regression.
1596
+ .p-multiselect-overlay {
1597
+ max-width: min(420px, calc(100vw - 32px));
1598
+
1599
+ .p-multiselect-option {
1600
+ display: flex;
1601
+ align-items: center;
1602
+ gap: 0.5rem;
1603
+ max-width: 100%;
1604
+
1605
+ // The label child truncates; the checkbox keeps its natural size.
1606
+ // `:not(.p-checkbox)` matches both the default text span and any
1607
+ // templated `<span>` wrapper.
1608
+ > *:not(.p-checkbox) {
1609
+ min-width: 0;
1610
+ flex: 1 1 auto;
1611
+ overflow: hidden;
1612
+ text-overflow: ellipsis;
1613
+ white-space: nowrap;
1614
+ }
1615
+ }
1616
+ }
1617
+
1054
1618
  // PrimeNG Autocomplete option selected (for p-autocomplete components)
1055
1619
  .p-autocomplete-option-selected {
1056
1620
  background-color: var(--first) !important;
@@ -1443,6 +2007,17 @@ p {
1443
2007
 
1444
2008
  .md-drppicker .btn {
1445
2009
  background-color: var(--tabs_bg) !important;
2010
+ // Platform Submit-button convention (2026-05-21): near-square corners,
2011
+ // Bootstrap .btn natural size (~31px tall). Override the library's
2012
+ // line-height: 36px which inflates the button to 45px.
2013
+ border-radius: 2px !important;
2014
+ min-height: 0 !important;
2015
+ height: auto !important;
2016
+ line-height: 1.5 !important;
2017
+ padding: 0.375rem 0.75rem !important;
2018
+ font-size: 13px;
2019
+ font-weight: 500;
2020
+ text-transform: none !important;
1446
2021
  }
1447
2022
 
1448
2023
  .md-drppicker .ranges ul li button.active {
@@ -17,7 +17,11 @@ button:hover {
17
17
  }
18
18
 
19
19
  .btn {
20
- border-radius: 2px;
20
+ border-radius: 4px;
21
+ }
22
+
23
+ .p-button {
24
+ border-radius: 4px;
21
25
  }
22
26
 
23
27
  .btn-circle {
@@ -248,7 +248,7 @@
248
248
 
249
249
  .card {
250
250
  background-color: rgb(255 255 255);
251
- border: 1px solid rgb(218 220 224) !important;
251
+ border: 1px solid rgb(226 229 234) !important; // N200 — design-system line token
252
252
  border-radius: 8px;
253
253
  box-sizing: border-box;
254
254
  height: 100%;
@@ -21,8 +21,10 @@ form {
21
21
  }
22
22
 
23
23
  .form-control {
24
- border: 1px solid rgb(166 169 174);
25
- border-radius: 2px;
24
+ border: 1px solid #e2e5ea;
25
+ border-radius: 6px;
26
+ background-color: #fff; // unified with dropdowns
27
+ min-height: 36px; // align with p-select / p-multiselect heights
26
28
  color: rgb(128 128 128);
27
29
 
28
30
  /* &::input-placeholder {
@@ -31,21 +33,29 @@ form {
31
33
 
32
34
  &:placeholder {
33
35
  /* Firefox 18- */
34
- color: color.adjust($body-bg, $lightness: -20%);
36
+ color: #a8afbc; // N400 — unified with PrimeNG $placeHolder (lighter than text to read as hint, not value)
35
37
  }
36
38
 
37
39
  &::placeholder {
38
40
  /* Firefox 19+ */
39
- color: color.adjust($body-bg, $lightness: -20%);
41
+ color: #a8afbc; // N400 — unified with PrimeNG $placeHolder (lighter than text to read as hint, not value)
42
+ font-weight: 400;
43
+ font-style: normal;
40
44
  }
41
45
 
42
46
  /* &:input-placeholder {
43
47
  color: color.adjust($body-bg, $lightness: -20%);
44
48
  } */
45
49
 
46
- &:focus {
50
+ &:focus:not([readonly]):not(:disabled) {
47
51
  border-color: rgb(0 0 0);
48
52
  }
53
+ &[readonly]:focus,
54
+ &:disabled:focus {
55
+ border-color: #e2e5ea !important;
56
+ box-shadow: none !important;
57
+ outline: 0;
58
+ }
49
59
  }
50
60
 
51
61
  .form-control-position {
@@ -1,5 +1,5 @@
1
1
  .app-sidebar {
2
- z-index: 1038; // above navbar (1037) so sidebar header renders on top; below modal-backdrop (1040)
2
+ z-index: 10000; // above the dashboard header (.navbar-blue is z-index: 9999) so the sidebar header renders on top
3
3
  }
4
4
 
5
5
  .navbar {
@@ -153,7 +153,7 @@
153
153
  position: fixed;
154
154
  top: 0;
155
155
  width: 220px;
156
- z-index: 1038;
156
+ z-index: 10000; // above the dashboard header (.navbar-blue is z-index: 9999); keep both .app-sidebar rules in sync
157
157
 
158
158
  &.hide-sidebar {
159
159
  transform: translate3d(-100%, 0, 0);
@@ -2,7 +2,7 @@
2
2
  // Tabs/Navs/Pills
3
3
 
4
4
  .tab-content {
5
- padding: 30px 0 20px;
5
+ padding: 12px 0 20px;
6
6
  }
7
7
 
8
8
  .p-hidden {
@@ -14,6 +14,7 @@
14
14
 
15
15
  .nav-link {
16
16
  height: 100%;
17
+ border-radius: 4px 4px 0 0;
17
18
 
18
19
  &.active {
19
20
  background-color: transparent;
@@ -81,7 +82,7 @@
81
82
  border: 0;
82
83
  padding: 6px !important;
83
84
  background: transparent;
84
- border-radius: 0;
85
+ border-radius: 4px 4px 0 0;
85
86
  color: map.get($primary, base);
86
87
  font-size: 14px;
87
88
  font-weight: 400;
@@ -1488,6 +1488,31 @@ body {
1488
1488
  color: $placeHolder;
1489
1489
  } */
1490
1490
 
1491
+ // PrimeNG renders p-select/p-multiSelect placeholders as text nodes inside
1492
+ // .p-placeholder labels (not via the native ::placeholder pseudo) —
1493
+ // unify them explicitly to match $placeHolder.
1494
+ .p-select-label.p-placeholder,
1495
+ .p-multiselect-label.p-placeholder {
1496
+ color: $placeHolder;
1497
+ font-weight: 400;
1498
+ font-style: normal;
1499
+ }
1500
+
1501
+ // p-autoComplete renders a real <input> with a native placeholder attr.
1502
+ // The legacy `body { &::placeholder }` rule above compiles to
1503
+ // body::placeholder (a no-op — body has no placeholder pseudo), so
1504
+ // autocomplete inputs were falling back to browser default until now.
1505
+ // Target every PrimeNG/Bootstrap input placeholder explicitly here.
1506
+ input::placeholder,
1507
+ textarea::placeholder,
1508
+ .p-inputtext::placeholder,
1509
+ .p-autocomplete-input::placeholder {
1510
+ color: $placeHolder;
1511
+ font-weight: 400;
1512
+ font-style: normal;
1513
+ opacity: 1; // override Firefox's default 0.54 so color is honored as-is
1514
+ }
1515
+
1491
1516
  /* PrimeNG 19: Support both old and new class names */
1492
1517
  .ui-inputtext.ng-dirty.ng-invalid,
1493
1518
  .p-inputtext.ng-dirty.ng-invalid {
@@ -2245,49 +2270,67 @@ body {
2245
2270
  }
2246
2271
  }
2247
2272
 
2248
- /* PrimeNG 19 Accordion - same visual as legacy (gray default, primary active, white content) */
2273
+ /* PrimeNG 19/21 Accordion - Refined Utilitarian (Linear/Attio register):
2274
+ single rounded shell, hairline borders, internal dividers between panels,
2275
+ white surface, calm hover, accent indicator on the active panel.
2276
+ !important on bg/border/padding is required since PrimeNG 21's theme stylesheet
2277
+ loads after our SCSS and matches at the same specificity. */
2249
2278
  .p-accordion {
2279
+ border: 1px solid #e2e5ea !important; /* N200 line */
2280
+ border-radius: 12px !important; /* lg radius (containers) */
2281
+ background-color: #ffffff !important; /* N0 surface */
2282
+ overflow: hidden; /* clip panel hover/active bg to the rounded shell */
2283
+
2250
2284
  .p-accordionpanel {
2251
- margin-bottom: 2px;
2285
+ margin-bottom: 0;
2286
+ border-bottom: 1px solid #e2e5ea; /* internal hairline divider */
2287
+
2288
+ &:last-of-type {
2289
+ border-bottom: 0;
2290
+ }
2252
2291
 
2253
2292
  > .p-accordionheader {
2254
- background-color: rgb(239 239 239);
2255
- border: 1px solid $ng-gray;
2256
- color: $font-color-main;
2257
- padding: 0.857em 1em;
2293
+ background-color: #ffffff !important;
2294
+ border: 0 !important;
2295
+ padding: 0.75em 1em !important;
2296
+ transition:
2297
+ background-color 0.15s ease-out,
2298
+ box-shadow 0.15s ease-out;
2258
2299
 
2259
2300
  .p-accordionheader-toggle-icon {
2260
- color: $titleBarIcon;
2301
+ color: #7c8696; /* N500 ink-3 */
2302
+ transition:
2303
+ transform 0.2s ease-out,
2304
+ color 0.15s ease-out;
2261
2305
  }
2262
2306
  }
2263
2307
 
2264
2308
  &:not(.p-accordionpanel-active):not(.p-disabled) {
2265
2309
  > .p-accordionheader:hover {
2266
- border: 1px solid $ng-gray;
2267
- color: $font-color-main;
2310
+ background-color: #f7f8fa !important; /* N50 bg */
2311
+ border: 0 !important;
2268
2312
 
2269
2313
  .p-accordionheader-toggle-icon {
2270
- color: $font-color-main;
2314
+ color: #3d4654; /* N700 */
2271
2315
  }
2272
2316
  }
2273
2317
  }
2274
2318
 
2275
2319
  &:not(.p-disabled).p-accordionpanel-active {
2276
2320
  > .p-accordionheader {
2277
- background-color: rgb(239 239 239);
2278
- color: rgb(255 255 255);
2321
+ background-color: #f7f8fa !important; /* N50 to mark open */
2322
+ box-shadow: inset 2px 0 0 0 var(--first); /* sacred accent edge */
2279
2323
 
2280
2324
  .p-accordionheader-toggle-icon {
2281
- color: rgb(255 255 255);
2325
+ color: var(--first);
2282
2326
  }
2283
2327
  }
2284
2328
 
2285
2329
  > .p-accordionheader:hover {
2286
- background-color: rgb(239 239 239);
2287
- color: rgb(255 255 255);
2330
+ background-color: #f7f8fa !important;
2288
2331
 
2289
2332
  .p-accordionheader-toggle-icon {
2290
- color: rgb(255 255 255);
2333
+ color: var(--first);
2291
2334
  }
2292
2335
  }
2293
2336
 
@@ -2300,14 +2343,15 @@ body {
2300
2343
  .p-accordioncontent {
2301
2344
  max-height: 0 !important;
2302
2345
  overflow: hidden !important;
2303
- transition: max-height 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
2346
+ transition: max-height 0.2s ease-out !important;
2304
2347
  }
2305
2348
 
2306
2349
  .p-accordioncontent-content {
2307
- background-color: rgb(255 255 255);
2308
- border: 1px solid $ng-gray;
2350
+ background-color: #ffffff !important;
2351
+ border: 0 !important;
2352
+ border-top: 1px solid #eef0f3 !important; /* N100 — softer than panel divider */
2309
2353
  color: $font-color-main;
2310
- padding: 1.171em 1em;
2354
+ padding: 1rem 1.125rem !important;
2311
2355
  box-sizing: border-box;
2312
2356
  }
2313
2357
  }
@@ -2953,7 +2997,7 @@ body {
2953
2997
  .p-datatable-header {
2954
2998
  background: transparent !important;
2955
2999
  border: 0;
2956
- padding: 0.25em 0.5em;
3000
+ padding: 0.25em 0.5em 10px; // canonical filter→thead gap (2026-05-21)
2957
3001
  }
2958
3002
 
2959
3003
  .p-datatable-thead {
@@ -5093,8 +5137,12 @@ body {
5093
5137
  }
5094
5138
 
5095
5139
  .total-records-count {
5096
- bottom: -8px;
5140
+ color: #7c8696; /* N500 ink-3 — quiet meta */
5097
5141
  float: left;
5142
+ font-size: 0.9375rem; /* 15px — quiet but readable */
5143
+ font-weight: 400;
5144
+ letter-spacing: 0;
5145
+ padding: 8px 10px;
5098
5146
  position: relative;
5099
5147
  white-space: nowrap;
5100
5148
  }
@@ -5143,6 +5191,23 @@ body {
5143
5191
  color: rgb(255 255 255) !important;
5144
5192
  }
5145
5193
 
5194
+ .primeng-datatable-container table thead tr:first-child th:first-child {
5195
+ border-top-left-radius: 4px;
5196
+ }
5197
+
5198
+ // Apply the right-corner radius to whichever cell visually sits at the
5199
+ // right edge: if the last <th> is an actions-list-* (collapsed to 0px),
5200
+ // the visible right edge is the previous header cell. Targeting both
5201
+ // covers tables with and without the actions column.
5202
+ .primeng-datatable-container table thead tr:first-child th:last-child,
5203
+ .primeng-datatable-container
5204
+ table
5205
+ thead
5206
+ tr:first-child
5207
+ th:nth-last-child(2):has(+ th[class*='actions-list-']) {
5208
+ border-top-right-radius: 4px;
5209
+ }
5210
+
5146
5211
  .analytics {
5147
5212
  padding-bottom: 30px;
5148
5213
 
@@ -69,8 +69,8 @@ $paginationHover: rgb(234 234 234);
69
69
  /* Prime add on button background color */
70
70
  $input-ground-addon: rgb(244 244 244);
71
71
 
72
- /* Placeholder Color */
73
- $placeHolder: rgb(102 102 102);
72
+ /* Placeholder Color — design-system N400 (calibrated 2026-05-21 for cross-widget uniformity; N400 keeps hints distinct from real values) */
73
+ $placeHolder: #a8afbc;
74
74
 
75
75
  /* Hover and not selected button */
76
76
  $selectButton: rgb(200 200 200);