active_admin_theme 3.1.1 → 3.2.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.
@@ -55,6 +55,9 @@ $skinTitleBarBorderColor: $skinMainSecondColor!default;
55
55
  $skinTitleBarBorderWidth: 0!default;
56
56
  $skinTitleBarButtonPaddingY: 6px!default; // action button vertical padding
57
57
  $skinTitleBarButtonPaddingX: 10px!default; // action button horizontal padding
58
+ // Minimum bar height — raise it alongside the button paddings above if your
59
+ // buttons outgrow the default band (the bar still grows to fit its content).
60
+ $skinTitleBarHeight: 37px!default;
58
61
 
59
62
  // Panel/section headers + tabs accent (the blue bar on panel headers and the
60
63
  // inactive tabs). $skinPanelHeaderColor is the base/light value;
@@ -214,6 +217,20 @@ $skinStatusTagErrorColor: #ce483b!default; // error / errored / red
214
217
  --aa-active-tab-text: #{$skinActiveTabTextColor};
215
218
  --aa-inactive-tab-text: #{$skinInactiveTabTextColor};
216
219
  --aa-delete-link: #{$skinDeleteLinkColor};
220
+
221
+ // Bridge for the companion active_admin_datetimepicker plugin: feed its
222
+ // --aa-datetimepicker-* inputs from this theme's palette, so the xdsoft picker
223
+ // is themed (light + dark) automatically with no per-app override. These point
224
+ // at the --aa-* vars, which are themselves redefined per mode, so a single set
225
+ // here covers both. Harmless no-op when the plugin isn't installed (nothing
226
+ // reads these). A host can still override any of them downstream.
227
+ --aa-datetimepicker-accent: var(--aa-accent);
228
+ --aa-datetimepicker-surface: var(--aa-surface);
229
+ --aa-datetimepicker-surface-2: var(--aa-surface-2);
230
+ --aa-datetimepicker-text: var(--aa-text);
231
+ --aa-datetimepicker-text-muted: var(--aa-text-muted);
232
+ --aa-datetimepicker-border: var(--aa-border);
233
+ --aa-datetimepicker-hover: var(--aa-surface-hover);
217
234
  }
218
235
  // OS preference, unless the user explicitly chose light
219
236
  @media (prefers-color-scheme: dark) {
@@ -249,6 +266,11 @@ $theme-icon-auto: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/s
249
266
  $theme-icon-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linecap='round'%3E%3Ccircle cx='8' cy='8' r='3.1'/%3E%3Cpath d='M8 .9v1.8M8 13.3v1.8M.9 8h1.8M13.3 8h1.8M2.9 2.9l1.3 1.3M11.8 11.8l1.3 1.3M13.1 2.9l-1.3 1.3M4.2 11.8l-1.3 1.3'/%3E%3C/svg%3E")!default;
250
267
  $theme-icon-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M13.4 9.6A5.9 5.9 0 0 1 6.4 2.6a5.9 5.9 0 1 0 7 7z'/%3E%3C/svg%3E")!default;
251
268
 
269
+ // Logout drawn as a sign-out glyph instead of the word "Logout", so it matches
270
+ // the icon toggle beside it. Masked like the theme icons above, so it takes
271
+ // currentColor and follows $skinMenuTextColor; a consumer can override the var.
272
+ $logout-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.5 2.5H3.2v11h3.3'/%3E%3Cpath d='M12.8 8H6'/%3E%3Cpath d='M9.5 5 12.8 8l-3.3 3'/%3E%3C/svg%3E")!default;
273
+
252
274
  // A wrong-typed override is legal SassScript, so it compiles without a word and
253
275
  // emits CSS the browser drops — `border-bottom: none solid #5ea3d3`,
254
276
  // `padding: 10 12px`. The rule silently vanishes and ActiveAdmin's own value
@@ -361,10 +383,35 @@ $theme-icon-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/s
361
383
  html {
362
384
  background: var(--aa-page-bg);
363
385
  }
386
+
387
+ // Pin a bar (#header, #title_bar) to the viewport's left edge at the visible
388
+ // viewport width while a wide index table scrolls the whole page sideways
389
+ // (#wrapper is display:table, min-width:100%). width = 100vw minus the vertical
390
+ // scrollbar — the JS-set --scrollbar-width var (0 when wigu/scrollbar_width is
391
+ // absent, and then max-width:100% keeps 100vw's scrollbar allowance from adding a
392
+ // stray horizontal scrollbar). It's a plain max-width, not min(…, 100%):
393
+ // sassc/libsass evaluates the Sass min() function and rejects calc() as a
394
+ // non-number. No `top`, so the bar still scrolls vertically.
395
+ @mixin viewport-pinned-bar {
396
+ position: sticky;
397
+ left: 0;
398
+ width: calc(100vw - var(--scrollbar-width, 0px));
399
+ max-width: 100%;
400
+ box-sizing: border-box;
401
+ }
402
+
364
403
  body.active_admin {
365
404
  background: var(--aa-page-bg);
366
405
  }
367
406
  #wrapper {
407
+ // ActiveAdmin lays #wrapper out as `display: table`, which sizes to its
408
+ // content: a wide index table therefore scrolls the WHOLE page horizontally
409
+ // (the master / pre-theme behaviour admins are used to). Only guarantee the
410
+ // layout fills the viewport when the content is narrow. The utility nav
411
+ // (clock / theme toggle / logout) is pinned `sticky` (see #utility_nav below)
412
+ // so it stays reachable while the page scrolls sideways.
413
+ min-width: 100%;
414
+
368
415
  div.tabs {
369
416
  > ul > li > a {
370
417
  background-image: none;
@@ -427,6 +474,12 @@ body.active_admin {
427
474
  border: none;
428
475
  text-shadow: none;
429
476
  background-image: none;
477
+
478
+ // A wide index table scrolls the WHOLE page sideways. Pin the header on
479
+ // screen at the viewport width so it doesn't stretch to the table's width
480
+ // (which spread the menu out under the utility nav and overlapped it); it
481
+ // still grows to fit a wrapped menu, ActiveAdmin's natural layout.
482
+ @include viewport-pinned-bar;
430
483
  h1.site_title {
431
484
  color: $skinMainSecondColor;
432
485
  font-size: 16px;
@@ -457,10 +510,34 @@ body.active_admin {
457
510
  // tabs: drop the pill padding, and on hover highlight by text colour only — no
458
511
  // background block. The clock has no click action, so it gets no hover at all.
459
512
  #utility_nav {
513
+ // Sits at the top-right of the header; the header itself is pinned on screen
514
+ // (sticky, see #header above) so the clock / theme toggle / logout stay
515
+ // reachable while a wide table scrolls the page sideways.
516
+ z-index: 2;
517
+ white-space: nowrap;
518
+
460
519
  > li > a { padding: 0 5px; }
461
520
  > li:hover > a, > li.current > a { background-color: transparent; }
462
521
  > li#servertime:hover > a { color: #ffffff; }
463
522
 
523
+ // Server clock (server_clock.js), rendered as two compact lines like
524
+ // yeti-web: time + timezone (larger) over year/month/day (smaller). A
525
+ // right-aligned flex column sized to the nav band (~24px) so both lines
526
+ // fit the header height unchanged. Monospace + tabular figures keep the
527
+ // ticking time a constant width, so the adjacent logout doesn't jitter.
528
+ > li#servertime > span {
529
+ display: inline-flex;
530
+ flex-direction: column;
531
+ justify-content: center;
532
+ align-items: flex-end;
533
+ vertical-align: middle;
534
+ height: 24px;
535
+ font-family: 'SFMono-Regular', Menlo, Consolas, 'Liberation Mono', monospace;
536
+ font-variant-numeric: tabular-nums;
537
+ }
538
+ > li#servertime .clock-time { font-size: 12px; line-height: 1.1; }
539
+ > li#servertime .clock-date { font-size: 9px; line-height: 1.1; }
540
+
464
541
  // The theme switch (app/assets/javascripts/wigu/theme_toggle.js). The
465
542
  // script only writes data-mode; the glyph is drawn here, so a project can
466
543
  // swap it for its own icon font without forking the script. Masked rather
@@ -497,6 +574,35 @@ body.active_admin {
497
574
  -webkit-mask-image: $theme-icon-dark;
498
575
  mask-image: $theme-icon-dark;
499
576
  }
577
+
578
+ // Logout as an icon ($logout-icon), to match the toggle beside it.
579
+ // font-size:0 hides the "Logout" text visually; the accessible name is
580
+ // the aria-label the gem's Ruby puts on the anchor, because zero-sized
581
+ // text is pruned from the accessibility tree by some screen readers
582
+ // (the toggle gets its aria-label from theme_toggle.js the same way).
583
+ // The masked glyph follows the bar's text colour. The real logout
584
+ // link/method from add_logout_button_to_menu is untouched.
585
+ // Scoped to a.wigu-logout — the class the gem's Ruby puts on the item
586
+ // it adds — so a consumer-rendered logout keeps its own label instead
587
+ // of collapsing to an unlabeled glyph.
588
+ > li#logout:has(> a.wigu-logout) { display: inline-flex; align-items: center; vertical-align: middle; }
589
+ > li#logout > a.wigu-logout {
590
+ display: flex;
591
+ align-items: center;
592
+ font-size: 0;
593
+ opacity: 0.75;
594
+
595
+ &:before {
596
+ content: "";
597
+ display: block;
598
+ width: 16px;
599
+ height: 16px;
600
+ background-color: currentColor;
601
+ -webkit-mask: #{$logout-icon} center / contain no-repeat;
602
+ mask: #{$logout-icon} center / contain no-repeat;
603
+ }
604
+ &:hover, &:focus { opacity: 1; }
605
+ }
500
606
  }
501
607
  ul.tabs {
502
608
  li.has_nested {
@@ -566,26 +672,87 @@ body.active_admin {
566
672
  background-color: $skinTitleBarColor;
567
673
  border: none;
568
674
  border-bottom: $skinTitleBarBorderWidth solid $skinTitleBarBorderColor;
675
+ // Pin the whole bar to the viewport like #header, so the title AND the action
676
+ // buttons stay visible while a wide index table scrolls the page sideways.
677
+ @include viewport-pinned-bar;
678
+ // Lay the title (left) and action items (right) on one compact row. Without
679
+ // this, resetting #titlebar_right to static (below) stacks it under the
680
+ // title and the bar grows tall. min-height, NOT height: with a fixed height
681
+ // the bar cannot contain its content — action items that wrap on a narrow
682
+ // window would overflow it and paint over the page — while min-height still
683
+ // makes breadcrumb (detail) pages match plain index pages. Drop the base
684
+ // clearfix — in a flex container it becomes a stray third item that
685
+ // space-between strands right.
686
+ display: flex;
687
+ align-items: center;
688
+ justify-content: space-between;
689
+ min-height: $skinTitleBarHeight;
690
+ &::after { display: none; }
569
691
  #titlebar_left {
692
+ display: flex;
693
+ align-items: center;
694
+ gap: 6px;
695
+ height: auto;
696
+ // Let the title shrink below its min-content width: flex items refuse to
697
+ // otherwise, and a long nowrap record title would push the action items
698
+ // out of the pinned bar — unreachable past the viewport on wide pages.
699
+ min-width: 0;
570
700
  span.breadcrumb {
571
701
  font-size: 12px;
572
702
  text-shadow: none;
573
- margin-bottom: 8px;
703
+ margin-bottom: 0;
574
704
  a, span {
575
- color: lighten( $skinMainFirstColor, 30% )!important; ;
705
+ color: lighten( $skinMainFirstColor, 30% )!important;
576
706
  }
577
707
  }
708
+ // Own a sensible title size so consumer apps don't each shrink AA's large
709
+ // default (which towered over the breadcrumb). margin:0 + the flex parent
710
+ // centre it on the breadcrumb's line; nowrap + ellipsis keep a long
711
+ // record title (show pages) on one line without pushing the action
712
+ // items out of the bar — min-width: 0 lets it actually shrink.
578
713
  h2 {
579
714
  color: #ffffff;
580
715
  text-shadow: none;
716
+ font-size: 16px;
717
+ margin: 0;
718
+ min-width: 0;
719
+ // Screen only: AA registers the stylesheet with media "all", and paper
720
+ // has no horizontal scroll — a nowrap/ellipsis title would print
721
+ // truncated at the page edge where it used to wrap.
722
+ @media screen {
723
+ white-space: nowrap;
724
+ overflow: hidden;
725
+ text-overflow: ellipsis;
726
+ }
581
727
  }
582
728
  }
583
729
  #titlebar_right {
730
+ // Horizontal pinning is handled by #title_bar (sticky + viewport width),
731
+ // so the action items just sit at the right of the bar. Reset any
732
+ // consumer `position: absolute/sticky` (each app used to pin the buttons
733
+ // itself via local stylesheet overrides).
734
+ position: static;
735
+ // Shrink to the action-items height (some apps/base leave it ~50px, taller
736
+ // than the 37px bar, so centering pushed it over the top edge).
737
+ height: auto;
738
+ // Space multiple action items apart (e.g. a Menu dropdown next to a New
739
+ // button); the base leaves them flush on this side.
740
+ .action_items > .action_item:not(:last-child):not(:empty) { margin-right: 10px; }
741
+ // The form selectors cover button_to action items: Rails renders them as
742
+ // form > input[type=submit] (or button), which the anchor selectors
743
+ // miss — consumer apps were re-hardcoding the 6px/10px padding to
744
+ // compensate, desyncing from the $skin* knobs on every change.
584
745
  // `> a` and `> .dropdown_menu > a`: an action item may hold a dropdown, and
585
746
  // a descendant selector here paints its menu entries with the button fill
586
747
  // too — with !important, so the entry rules below cannot undo it.
748
+ // A button_to action item renders as a block <form>; inline-block
749
+ // keeps it on the bar's single row (consumers used to float it, which
750
+ // collapsed the flex bar's height instead).
751
+ .action_item > form { display: inline-block; }
587
752
  .action_item > a,
588
- .action_item > .dropdown_menu > a {
753
+ .action_item > .dropdown_menu > a,
754
+ .action_item > form > input[type="submit"],
755
+ .action_item > form > button {
589
756
  background-image: none;
590
757
  border: none;
591
758
  box-shadow: none;
@@ -601,7 +768,28 @@ body.active_admin {
601
768
  }
602
769
  }
603
770
  div.dropdown_menu {
771
+ // Containing block for the panel below, so `right: 0` means "the
772
+ // trigger's right edge". AA leaves this element unpositioned, which
773
+ // makes the panel resolve against #title_bar (sticky) and park at the
774
+ // bar's far right no matter which action item was clicked. inline-block
775
+ // over AA's `display: inline`: an inline box's containing block spans
776
+ // its text fragments, so the markup's whitespace around the button
777
+ // pushed the panel a few px past the button's edge.
778
+ position: relative;
779
+ display: inline-block;
604
780
  div.dropdown_menu_list_wrapper {
781
+ // Always right-align title-bar dropdowns under their own button,
782
+ // overriding AA's inline positioning. AA 3.x's _position() sets
783
+ // inline `left` OR `right` per open depending on a window-geometry
784
+ // branch and never clears the other, and the wrapper persists across
785
+ // opens — after a resize between opens both offsets accumulate and
786
+ // the width:auto wrapper stretches from the stale left coordinate to
787
+ // the right edge. Its `right` branch is wrong here anyway: it offsets
788
+ // from the window, not from the bar. Consumer apps used to carry
789
+ // exactly this pair as a local hotfix; the theme owns the bar now, so
790
+ // it owns the guard.
791
+ left: auto !important;
792
+ right: 0 !important;
605
793
  background-color: var(--aa-elevated);
606
794
  border: 1px solid var(--aa-border);
607
795
  background-image: none;
@@ -612,6 +800,12 @@ body.active_admin {
612
800
  // that is invisible whenever the panel fill is close to the page —
613
801
  // which is exactly what a light surface is. Outline it like the panel.
614
802
  div.dropdown_menu_nipple {
803
+ // The panel is pinned right (above) while AA still offsets the
804
+ // arrow from the panel's LEFT edge by the button's centre, which
805
+ // lands it on empty bar. Pin it right too, centred on the button's
806
+ // own caret (AA draws that 6px wide at right: 12px).
807
+ left: auto !important;
808
+ right: 9px !important;
615
809
  border-color: var(--aa-border) transparent;
616
810
  &:before, &:after { border-color: var(--aa-elevated) transparent; }
617
811
  }
@@ -639,6 +833,12 @@ body.active_admin {
639
833
  }
640
834
  }
641
835
  .flashes {
836
+ // Pin like #header/#title_bar: this is the one full-width bar between the
837
+ // two pinned ones, and on a wide page scrolled right its left-aligned text
838
+ // would leave the viewport, leaving an unreadable colored stripe. AA puts
839
+ // all padding/background on the inner .flash, so the always-rendered empty
840
+ // wrapper stays zero-height and inert on flash-less pages.
841
+ @include viewport-pinned-bar;
642
842
  .flash {
643
843
  color: #ffffff;
644
844
  background-image: none;
@@ -1216,6 +1416,38 @@ table.index_table thead th a {
1216
1416
  color: var(--aa-table-header-text) !important;
1217
1417
  }
1218
1418
 
1419
+ // Sticky table headers: keep the column labels visible while a long index table
1420
+ // scrolls vertically. The header/title bar pin only horizontally (no `top`), so
1421
+ // they scroll away on vertical scroll and this thead pins to the viewport top —
1422
+ // no overlap. Scoped to .paginated_collection so it only hits real AA index
1423
+ // listings, not custom report/dashboard tables.
1424
+ //
1425
+ // Opting out: AA 3.x hardcodes the index table's class attribute and ignores
1426
+ // `index class:` options, so .skip-sticky-headers is reachable only on tables
1427
+ // whose markup you render yourself. For real AA listings, opt out app-wide
1428
+ // with one CSS line in your own stylesheet:
1429
+ // .paginated_collection table.index_table thead tr th { position: static; }
1430
+ // Opt a hand-built table IN with .with-sticky-headers.
1431
+ // tr:first-child only: with a multi-row thead (group header over column
1432
+ // header) every row would stick at the same offset and paint over each other —
1433
+ // per-row offsets need measurements CSS alone can't express. The first row
1434
+ // pins; any further header rows scroll away normally.
1435
+ .paginated_collection table.index_table:not(.skip-sticky-headers) thead tr:first-child th,
1436
+ table.with-sticky-headers thead tr:first-child th {
1437
+ position: sticky;
1438
+ top: -1px;
1439
+ z-index: 98;
1440
+ }
1441
+ // Square the corner cells of sticky theads: their inner-radius top corners are
1442
+ // transparent, so once the header detaches from the table's rounded border the
1443
+ // rows scrolling beneath flicker through the ~3px notches. At rest the square
1444
+ // background overpaints the border's rounding by the same ~3px — the lesser,
1445
+ // static artifact (CSS cannot tell "stuck" from "at rest").
1446
+ .paginated_collection table.index_table:not(.skip-sticky-headers) thead tr:first-child th:first-child,
1447
+ table.with-sticky-headers thead tr:first-child th:first-child { border-top-left-radius: 0; }
1448
+ .paginated_collection table.index_table:not(.skip-sticky-headers) thead tr:first-child th:last-child,
1449
+ table.with-sticky-headers thead tr:first-child th:last-child { border-top-right-radius: 0; }
1450
+
1219
1451
  // Form section header: a <legend> interrupts the fieldset's top border, so the
1220
1452
  // header bar has no border around it (unlike a panel header). Float the legend
1221
1453
  // so it flows as a normal block and the fieldset border wraps around it.
@@ -1360,7 +1592,47 @@ form fieldset > ol > li fieldset ol li {
1360
1592
  border-color: var(--aa-border);
1361
1593
  }
1362
1594
  .dropdown_menu .dropdown_menu_list_wrapper .dropdown_menu_list li {
1363
- border-bottom-color: var(--aa-border);
1595
+ // Was repainted to --aa-border, but a hairline under every row makes the
1596
+ // flyout read as a table of cells rather than a menu. Drop it — the hover
1597
+ // highlight is enough separation. (Same depth as the base rule, later wins.)
1598
+ border-bottom: none;
1599
+ }
1600
+
1601
+ // Dropdown-button caret. ActiveAdmin draws it with two stacked triangles on the
1602
+ // button: :before is a #FFF "shadow", :after the #777 arrow — both hard-coded.
1603
+ // On the themed blue title-bar action button and on the dark elevated
1604
+ // table-tools buttons the #777 arrow is ~invisible. Recolor it to the button's
1605
+ // own text colour and hide the light shadow into the fill.
1606
+ #titlebar_right .dropdown_menu .dropdown_menu_button {
1607
+ &:before { border-top-color: var(--aa-button); }
1608
+ &:after { border-top-color: var(--aa-button-text); }
1609
+ }
1610
+ .table_tools .dropdown_menu .dropdown_menu_button,
1611
+ .batch_actions_selector .dropdown_menu_button {
1612
+ &:before { border-top-color: var(--aa-elevated); }
1613
+ &:after { border-top-color: var(--aa-text); }
1614
+ }
1615
+
1616
+ // Disabled form controls. The theme recolors inputs/selects via --aa-*, but a
1617
+ // disabled one looked identical to an enabled one (especially in dark). Mute
1618
+ // the fill, text and cursor so "disabled" reads in both modes. select2 sets its
1619
+ // control bg with !important, so the disabled override needs it too.
1620
+ #active_admin_content {
1621
+ input:disabled, textarea:disabled, select:disabled {
1622
+ background-color: var(--aa-surface-2) !important;
1623
+ color: var(--aa-text-muted) !important;
1624
+ opacity: 0.6;
1625
+ cursor: not-allowed !important;
1626
+ }
1627
+ }
1628
+ // Both container classes (--default --disabled) so this out-specifies the
1629
+ // enabled control rule's `--default` fill (which is !important) regardless of
1630
+ // source order.
1631
+ .select2-container--default.select2-container--disabled .select2-selection {
1632
+ background-color: var(--aa-surface-2) !important;
1633
+ opacity: 0.6;
1634
+ cursor: not-allowed !important;
1635
+ .select2-selection__rendered { color: var(--aa-text-muted) !important; }
1364
1636
  }
1365
1637
 
1366
1638
  .ui-dialog {
@@ -1370,6 +1642,11 @@ form fieldset > ol > li fieldset ol li {
1370
1642
  background-color: var(--aa-surface);
1371
1643
  color: var(--aa-text);
1372
1644
  border-bottom: 1px solid var(--aa-border);
1645
+ // jQuery-UI's default `minHeight: 150` option is applied inline to the content
1646
+ // box (min-height:~54px), so a one-line confirm leaves a big empty area
1647
+ // between the message and the buttons. Let the content size to its text
1648
+ // (the inline min-height needs !important to beat).
1649
+ .ui-dialog-content { min-height: 0 !important; }
1373
1650
  .ui-dialog-titlebar {
1374
1651
  background-image: none;
1375
1652
  box-shadow: none;
@@ -1465,6 +1742,28 @@ form.filter_form .filter_form_field.filter_date_range .seperator {
1465
1742
  width: 10px;
1466
1743
  }
1467
1744
 
1745
+ // Date-only range filters (jQuery-UI datepicker, input.datepicker) get no width
1746
+ // from the base, so the two inputs fall back to their size="12" intrinsic width
1747
+ // and line up side by side only by luck; the label's float:right "Set range"
1748
+ // link then leaks into the input band on a longer label and bumps the "To"
1749
+ // input onto a second line. Lay the field out with flexbox: the label takes a
1750
+ // full row, the two inputs share the next, immune to the float.
1751
+ form.filter_form .filter_form_field.filter_date_range {
1752
+ display: flex;
1753
+ flex-wrap: wrap;
1754
+
1755
+ > label { flex: 0 0 100%; }
1756
+
1757
+ input.datepicker {
1758
+ box-sizing: border-box;
1759
+ flex: 1 1 0;
1760
+ min-width: 0;
1761
+ margin: 0;
1762
+
1763
+ & + input.datepicker { margin-left: 6px; }
1764
+ }
1765
+ }
1766
+
1468
1767
  //filter select box border radius
1469
1768
  .filter_form_field select {
1470
1769
  border-radius: 3px;
@@ -1549,8 +1848,10 @@ input[type='radio'] {
1549
1848
  // the seven :not()s are worth seven class-level points, and that beat every
1550
1849
  // component rule declared later — pagination links and the Clear Filters
1551
1850
  // button came out --aa-link instead of --aa-text.
1552
- a:where(:not(.button):not(.action_item):not(.table_tools_button):not(.dropdown_menu_button):not(.ui-tabs-anchor):not(.delete_link):not([data-method="delete"])) { color: var(--aa-link); }
1553
- a[data-method="delete"], a.delete_link { color: var(--aa-delete-link); }
1851
+ a:where(:not(.button):not(.action_item):not(.table_tools_button):not(.dropdown_menu_button):not(.ui-tabs-anchor):not(.delete_link):not(.delete-link):not([data-method="delete"])) { color: var(--aa-link); }
1852
+ // `.delete-link` (hyphen) as well as AA's own `.delete_link`: some apps name
1853
+ // their soft-delete links with the hyphen.
1854
+ a[data-method="delete"], a.delete_link, a.delete-link { color: var(--aa-delete-link); }
1554
1855
 
1555
1856
  // Pagination — non-current page chips use a light-grey bg in the theme
1556
1857
  .pagination span > a { background-color: var(--aa-surface-2); color: var(--aa-text); }
@@ -1560,7 +1861,10 @@ input[type='radio'] {
1560
1861
  // cannot be out-specified by anything sane here, which is why this needs
1561
1862
  // !important, like the accent fill one line over in the base pagination
1562
1863
  // block. Without it the digit renders at 1.00:1 on its own background.
1563
- .pagination span > a:hover { color: $skinMenuPillTextColor !important; }
1864
+ // The hover fill is the accent ($skinMainSecondColor); $skinMenuPillTextColor
1865
+ // is a light blue that sits on it at ~1.16:1 (invisible). White reads on the
1866
+ // accent (matches the base pagination block's own hover colour one file over).
1867
+ .pagination span > a:hover { color: #fff !important; }
1564
1868
 
1565
1869
  // Batch-actions / table-tools dropdown menu items are white by default
1566
1870
  .table_tools .dropdown_menu_list li a {
@@ -1632,12 +1936,25 @@ input[type='radio'] {
1632
1936
  table.index_table tbody tr:nth-child(even) > td,
1633
1937
  table.index_table tbody tr.even > td { background: var(--aa-surface-2); }
1634
1938
  table.index_table tbody tr:hover > td { background: var(--aa-surface-hover); }
1635
- // Checked rows: the zebra and hover rules above outrank ActiveAdmin's own
1636
- // `tr.selected td`, so without this a ticked row looks exactly like an
1637
- // unticked one. The :hover variant keeps the selection visible while the
1638
- // pointer is over the row.
1939
+ // Selected cells: the zebra and hover rules above outrank ActiveAdmin's own
1940
+ // `tr.selected td` / `td.selected`, so without this a ticked row (batch
1941
+ // actions) or a click-highlighted row + column (crisscross) looks unselected.
1942
+ // The `tr >` in the td.selected selector is load-bearing: the even-stripe
1943
+ // rule is `...tbody tr:nth-child(even) > td` (specificity 0,0,2,4); a plain
1944
+ // `tbody td.selected` is only (0,0,2,3) and loses on even rows, breaking the
1945
+ // cross every other row. Matching the `tr >` ties the specificity, so source
1946
+ // order wins. The :hover variants keep the selection visible under the pointer.
1639
1947
  table.index_table tbody tr.selected > td,
1640
- table.index_table tbody tr.selected:hover > td { background: var(--aa-selected); }
1948
+ table.index_table tbody tr.selected:hover > td,
1949
+ table.index_table tbody tr > td.selected,
1950
+ table.index_table tbody tr:hover > td.selected { background: var(--aa-selected); }
1951
+ // The clicked cell sits at the centre of the cross; an accent outline marks
1952
+ // it apart from the row/column tint without a competing fill (outset inward
1953
+ // so it adds no layout shift).
1954
+ table.index_table tbody tr > td.selected-cell {
1955
+ outline: 2px solid var(--aa-accent);
1956
+ outline-offset: -2px;
1957
+ }
1641
1958
  // Plain content tables (e.g. the CDR show "Attempts" table) are striped by the
1642
1959
  // base CSS with light fills; map the odd/even rows onto the dark surfaces too.
1643
1960
  table tbody tr.odd > td, table tbody tr.odd > th { background-color: var(--aa-surface); }
@@ -1654,11 +1971,23 @@ input[type='radio'] {
1654
1971
  color: var(--aa-text);
1655
1972
  }
1656
1973
 
1974
+ // Give the content column its own block-formatting context so #index_footer's
1975
+ // `clear: both` (below) only clears the pagination floats INSIDE the table
1976
+ // area — not the floated #sidebar. Without this, a tall filter sidebar pushes
1977
+ // the pagination far below a short table (it clears the sidebar's float too).
1978
+ #main_content { display: flow-root; }
1979
+
1657
1980
  // Index footer (per-page selector, pagination, "displaying…" info, download
1658
1981
  // links): lay them out in one left-packed, vertically-centred row. The gem
1659
1982
  // floats them, so they sat top-aligned and, having different heights, looked
1660
1983
  // misaligned. A slightly larger font than the gem default (0.85em) too.
1661
1984
  #index_footer {
1985
+ // ActiveAdmin floats .pagination_top and .paginated_collection_contents
1986
+ // left; the footer has no clear, so it rode up beside them and rendered
1987
+ // ABOVE the table (per-page + page numbers + "Displaying…" duplicated on
1988
+ // top, nothing below). Clear the floats so it sits under the table — one
1989
+ // pagination on top, one on the bottom, like master.
1990
+ clear: both;
1662
1991
  text-align: left;
1663
1992
  font-size: 0.95em;
1664
1993
  .pagination_per_page,
@@ -1711,6 +2040,83 @@ input[type='radio'] {
1711
2040
  table.ui-datepicker-calendar tbody td span { color: var(--aa-text-muted); }
1712
2041
  .ui-state-hover, .ui-state-active { background: var(--aa-surface-hover); }
1713
2042
  }
2043
+
2044
+ // select2 (used by most ActiveAdmin apps). It ships its own light chrome and
2045
+ // appends the dropdown to <body>, so it is styled here at the mixin level
2046
+ // like the datepicker. Drive the control, the dropdown panel, the results
2047
+ // list and the search field from the --aa-* tokens so it follows both modes;
2048
+ // without this the dropdown stays a white panel with white/near-invisible
2049
+ // text in dark mode. !important is required because select2's own stylesheet
2050
+ // sets these to white with equal specificity and loads after this in the
2051
+ // bundle — same reason the datetime picker overrides use it.
2052
+ .select2-container--default {
2053
+ .select2-selection--single,
2054
+ .select2-selection--multiple {
2055
+ background-color: var(--aa-input-bg) !important;
2056
+ border-color: var(--aa-input-border) !important;
2057
+ color: var(--aa-text);
2058
+ }
2059
+ .select2-selection__rendered { color: var(--aa-text) !important; }
2060
+ .select2-selection__placeholder { color: var(--aa-text-muted) !important; }
2061
+ // Clear "×": keep it a small, muted inline glyph that brightens on hover.
2062
+ // select2 4.1 renders it as a <button> that takes a large square hover fill
2063
+ // (the "big blue square"); neutralize that and the 4.0 span the same way so
2064
+ // the affordance is just the colour change, correctly aligned.
2065
+ .select2-selection__clear {
2066
+ color: var(--aa-text-muted) !important;
2067
+ background: transparent !important;
2068
+ border: none !important;
2069
+ box-shadow: none !important;
2070
+ padding: 0 4px !important;
2071
+ &:hover, &:focus { color: var(--aa-text) !important; background: transparent !important; }
2072
+ }
2073
+ .select2-selection--multiple .select2-selection__choice {
2074
+ background-color: var(--aa-surface-2) !important;
2075
+ border-color: var(--aa-border) !important;
2076
+ color: var(--aa-text) !important;
2077
+ }
2078
+ .select2-search__field { color: var(--aa-text) !important; }
2079
+ }
2080
+ .select2-dropdown {
2081
+ background-color: var(--aa-surface) !important;
2082
+ border-color: var(--aa-border) !important;
2083
+ color: var(--aa-text);
2084
+ }
2085
+ .select2-results__option { color: var(--aa-text) !important; }
2086
+ .select2-results__option--highlighted[aria-selected] {
2087
+ background-color: var(--aa-surface-hover) !important;
2088
+ color: var(--aa-text) !important;
2089
+ }
2090
+ .select2-results__option[aria-selected="true"] {
2091
+ background-color: var(--aa-selected) !important;
2092
+ color: var(--aa-text) !important;
2093
+ }
2094
+
2095
+ // tom-select (used by e.g. yeti). Same situation as select2: its own light
2096
+ // chrome, dropdown rendered in place; recolor from the --aa-* tokens.
2097
+ .ts-wrapper {
2098
+ .ts-control {
2099
+ background: var(--aa-input-bg) !important;
2100
+ border-color: var(--aa-input-border) !important;
2101
+ color: var(--aa-text);
2102
+ input, input::placeholder { color: var(--aa-text) !important; }
2103
+ .item {
2104
+ background: var(--aa-surface-2) !important;
2105
+ border-color: var(--aa-border) !important;
2106
+ color: var(--aa-text) !important;
2107
+ }
2108
+ }
2109
+ }
2110
+ .ts-dropdown {
2111
+ background: var(--aa-surface) !important;
2112
+ border-color: var(--aa-border) !important;
2113
+ color: var(--aa-text);
2114
+ .optgroup-header { background: var(--aa-surface-2) !important; color: var(--aa-text-muted) !important; }
2115
+ .option { color: var(--aa-text) !important; }
2116
+ .option.active,
2117
+ .active { background: var(--aa-surface-hover) !important; color: var(--aa-text) !important; }
2118
+ .option[aria-selected="true"] { background: var(--aa-selected) !important; color: var(--aa-text) !important; }
2119
+ }
1714
2120
  }
1715
2121
 
1716
2122
  // DARK-ONLY content overrides — genuinely dark-specific values with no light
@@ -1729,3 +2135,58 @@ input[type='radio'] {
1729
2135
  html:not([data-theme="light"]) { @include aa-dark-content; }
1730
2136
  }
1731
2137
  html[data-theme="dark"] { @include aa-dark-content; }
2138
+
2139
+ // Trix / ActionText editor. Trix ships with ActionText (a Rails built-in), so
2140
+ // its chrome is themed here for every consumer; the app still imports Trix's
2141
+ // own base CSS (trix/dist/trix). All rules key off the --aa-* tokens, so they
2142
+ // follow the active palette in both light and dark, and are inert when no
2143
+ // editor is on the page.
2144
+ trix-toolbar {
2145
+ .trix-button-group { border-color: var(--aa-border); }
2146
+ .trix-button {
2147
+ background: var(--aa-surface);
2148
+ color: var(--aa-text);
2149
+ border-bottom-color: var(--aa-border);
2150
+ &:not(:first-child) { border-left-color: var(--aa-border); }
2151
+ &:not(:disabled):hover { background: var(--aa-surface-hover); }
2152
+ &.trix-active { background: var(--aa-selected); }
2153
+ }
2154
+ // Trix dims idle icons to opacity 0.6 and disabled ones to 0.125 — both read
2155
+ // as faint in either palette, the 0.125 so faint the block-format buttons
2156
+ // (disabled until the editor is focused) look broken. Lift idle to 0.8 and
2157
+ // disabled to 0.4: still clearly dimmer than idle, but legible. Trix keeps the
2158
+ // active icon at 1 via a higher-specificity rule.
2159
+ .trix-button--icon::before { opacity: 0.8; }
2160
+ .trix-button--icon:disabled::before { opacity: 0.4; }
2161
+ }
2162
+
2163
+ trix-editor {
2164
+ background: var(--aa-input-bg);
2165
+ border-color: var(--aa-input-border);
2166
+ color: var(--aa-text);
2167
+ &:empty:not(:focus)::before { color: var(--aa-text-muted); } // placeholder
2168
+ }
2169
+
2170
+ .trix-content {
2171
+ pre { background: var(--aa-surface-2); }
2172
+ blockquote { border-left: 0.3em solid var(--aa-border); }
2173
+ }
2174
+
2175
+ .trix-dialog {
2176
+ background: var(--aa-surface);
2177
+ color: var(--aa-text);
2178
+ box-shadow: 0 2px 8px var(--aa-shadow);
2179
+ input[type="text"], input[type="url"] {
2180
+ background: var(--aa-input-bg);
2181
+ border-color: var(--aa-input-border);
2182
+ color: var(--aa-text);
2183
+ }
2184
+ .trix-button { background: var(--aa-accent); color: #fff; }
2185
+ }
2186
+
2187
+ // Trix bakes its toolbar icons into the base CSS as black SVG background images
2188
+ // on ::before; invert them in dark mode so they read on the dark toolbar (the
2189
+ // idle/disabled opacity lift above applies in both modes).
2190
+ @include aa-dark {
2191
+ trix-toolbar .trix-button--icon::before { filter: invert(1); }
2192
+ }
@@ -1,3 +1,3 @@
1
1
  module ActiveAdminTheme
2
- VERSION = "3.1.1"
2
+ VERSION = "3.2.0"
3
3
  end