@wpmoo/ui 1.0.0-rc.9 → 1.1.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/ASSET_LICENSE.md +1 -1
  2. package/LICENSE +28 -0
  3. package/README.md +38 -3
  4. package/certification.json +3 -1
  5. package/dist/assets/css/moo-ui.css +436 -25
  6. package/dist/assets/css/moo-ui.min.css +10 -31
  7. package/dist/assets/css/moo.css +428 -24
  8. package/dist/assets/css/moo.min.css +10 -32
  9. package/dist/js/chart.js +23 -21
  10. package/dist/js/chart.min.js +5 -5
  11. package/dist/js/combobox.js +18 -65
  12. package/dist/js/context-menu.js +16 -68
  13. package/dist/js/datatable.js +38 -21
  14. package/dist/js/datepicker.js +5 -4
  15. package/dist/js/datepicker.min.js +4 -3
  16. package/dist/js/moo-ui.js +350 -15402
  17. package/dist/js/moo-ui.min.js +3 -23
  18. package/dist/js/sheet.js +46 -0
  19. package/dist/js/sidebar.js +3 -2
  20. package/dist/js/slider.js +12 -47
  21. package/dist/js/state.js +15 -41
  22. package/dist/js/table-of-contents.js +195 -0
  23. package/dist/release-manifest.json +4 -4
  24. package/package.json +5 -1
  25. package/scss/_components.scss +3 -2
  26. package/scss/_config.scss +15 -21
  27. package/scss/_settings.scss +0 -3
  28. package/scss/components/_accordion.scss +1 -12
  29. package/scss/components/_alert.scss +1 -12
  30. package/scss/components/_avatar.scss +1 -16
  31. package/scss/components/_badge.scss +1 -0
  32. package/scss/components/_breadcrumb.scss +1 -12
  33. package/scss/components/_button.scss +1 -29
  34. package/scss/components/_button_group.scss +1 -25
  35. package/scss/components/_card.scss +1 -29
  36. package/scss/components/_checkbox.scss +1 -38
  37. package/scss/components/_close_button.scss +1 -9
  38. package/scss/components/_collapsible.scss +1 -0
  39. package/scss/components/_combobox.scss +1 -6
  40. package/scss/components/_context-menu.scss +1 -11
  41. package/scss/components/_datatable.scss +46 -85
  42. package/scss/components/_datepicker.scss +1 -0
  43. package/scss/components/_dialog.scss +1 -14
  44. package/scss/components/_dropdown.scss +5 -51
  45. package/scss/components/_field.scss +1 -40
  46. package/scss/components/_input.scss +1 -5
  47. package/scss/components/_input_group.scss +1 -51
  48. package/scss/components/_kbd.scss +1 -0
  49. package/scss/components/_menubar.scss +1 -0
  50. package/scss/components/_navigation.scss +76 -6
  51. package/scss/components/_pagination.scss +1 -13
  52. package/scss/components/_popover.scss +1 -22
  53. package/scss/components/_progress.scss +1 -3
  54. package/scss/components/_radio_group.scss +105 -6
  55. package/scss/components/_search_trigger.scss +1 -2
  56. package/scss/components/_select.scss +1 -0
  57. package/scss/components/_separator.scss +1 -6
  58. package/scss/components/_sheet.scss +1 -17
  59. package/scss/components/_sidebar.scss +1 -2
  60. package/scss/components/_skeleton.scss +1 -8
  61. package/scss/components/_slider.scss +1 -2
  62. package/scss/components/_spinner.scss +1 -7
  63. package/scss/components/_switch.scss +1 -4
  64. package/scss/components/_table.scss +1 -2
  65. package/scss/components/_tabs.scss +1 -24
  66. package/scss/components/_textarea.scss +1 -0
  67. package/scss/components/_toast.scss +1 -4
  68. package/scss/components/_toc.scss +48 -0
  69. package/scss/components/_toggle_group.scss +1 -3
  70. package/scss/components/_tooltip.scss +1 -20
  71. package/scss/components/sidebar/_base.scss +0 -7
  72. package/scss/components/sidebar/_collapsed.scss +0 -6
  73. package/scss/components/sidebar/_identity.scss +0 -1
  74. package/scss/components/sidebar/_menus.scss +0 -17
  75. package/scss/foundations/_backdrop.scss +0 -26
  76. package/scss/foundations/_focus.scss +0 -56
  77. package/scss/foundations/_scope.scss +15 -0
  78. package/scss/layouts/_app.scss +0 -6
  79. package/scss/layouts/_page_grid.scss +0 -3
  80. package/scss/layouts/app/sidebar/_base.scss +0 -37
  81. package/scss/layouts/app/sidebar/_default.scss +0 -6
  82. package/scss/layouts/app/sidebar/_floating.scss +0 -11
  83. package/scss/layouts/app/sidebar/_inset.scss +0 -2
  84. package/scss/mixins/_banner.scss +9 -3
  85. package/scss/moo-core.scss +3 -6
  86. package/scss/moo-ui.scss +9 -5
  87. package/scss/settings/_bootstrap_overrides.scss +0 -8
  88. package/scss/settings/_component_variables.scss +4 -266
  89. package/scss/settings/_forms.scss +0 -37
  90. package/scss/settings/_palette.scss +0 -42
  91. package/scss/themes/_root.scss +3 -0
  92. package/scss/themes/_standalone.scss +0 -2
  93. package/scss/themes/_theme.scss +3 -2
  94. package/scss/utilities/_background_color.scss +18 -0
  95. package/scss/utilities/_border.scss +4 -0
  96. package/scss/utilities/_scroll_smooth.scss +9 -0
@@ -1,15 +1,5 @@
1
- // Bootstrap ships .table markup only, with no sort/filter/pagination
2
- // behavior and no faceted-filter or column-visibility concept, so this
3
- // partial covers only the pieces that have no Bootstrap token to consume:
4
- // the facet dropdown's own checkbox indicator (mirroring the shared
5
- // dropdown_toggle_item() indicator in scss/components/_dropdown.scss), the
6
- // sort-direction icon swap, and the hidden-column state the runtime drives.
7
-
8
- // A fixed layout keeps the table's overall width stable as sorting,
9
- // filtering, pagination, and column visibility change which rows and
10
- // columns are on screen; Bootstrap's own .table stays auto-layout (sized
11
- // to the current content), which is what let the table jump width as
12
- // visible content changed.
1
+ /* Data Table */
2
+
13
3
  .datatable {
14
4
  --moo-datatable-min-width: #{$moo-datatable-min-width};
15
5
  --moo-datatable-actions-cell-bg: #{$moo-datatable-actions-cell-bg};
@@ -34,29 +24,42 @@
34
24
  }
35
25
 
36
26
  .datatable--responsive-scroll .datatable-table,
37
- .datatable--responsive-toggle .datatable-table {
27
+ .datatable--responsive-toggle .datatable-table,
28
+ .datatable--responsive-auto .datatable-table {
38
29
  min-width: var(--moo-datatable-min-width);
39
30
  }
40
31
 
41
- // Toggle mode renders both the table and the card list at once (unlike
42
- // cards mode, which swaps them by breakpoint) so a person, not a viewport,
43
- // can choose the layout the way Odoo's List/Kanban switcher works. Visibility
44
- // follows the root's own data-datatable-view state instead of a d-*-none
45
- // utility.
46
- .datatable--responsive-toggle[data-datatable-view="table"] .datatable-card-frame {
32
+ .datatable--responsive-toggle[data-datatable-view="table"] .datatable-card-frame,
33
+ .datatable--responsive-auto[data-datatable-view="table"] .datatable-card-frame {
47
34
  display: none;
48
35
  }
49
36
 
50
- .datatable--responsive-toggle[data-datatable-view="cards"] .datatable-frame {
37
+ .datatable--responsive-toggle[data-datatable-view="cards"] .datatable-frame,
38
+ .datatable--responsive-auto[data-datatable-view="cards"] .datatable-frame {
51
39
  display: none;
52
40
  }
53
41
 
54
- // Bootstrap's .table has no bordered/rounded container variant of its own
55
- // (.table-bordered adds vertical column rules too, which the reference
56
- // doesn't have -- only an outer frame plus its own default row dividers).
57
- // The radius has to live on this wrapper, not the <table> itself: Bootstrap
58
- // resets every table to border-collapse: collapse, and a border-radius on a
59
- // collapsed-border table does not clip its own cell backgrounds.
42
+ @each $name, $value in map-merge($grid-breakpoints, $moo-datatable-responsive-breakpoint) {
43
+ @if $value != 0 {
44
+ .datatable--responsive-auto[data-datatable-responsive-breakpoint="#{$name}"] {
45
+ --moo-datatable-responsive-breakpoint: #{$value};
46
+
47
+ &[data-datatable-view="auto"] .datatable-card-frame {
48
+ display: none;
49
+ }
50
+
51
+ @include media-breakpoint-down($name, map-merge($grid-breakpoints, $moo-datatable-responsive-breakpoint)) {
52
+ &[data-datatable-view="auto"] .datatable-frame {
53
+ display: none;
54
+ }
55
+ &[data-datatable-view="auto"] .datatable-card-frame {
56
+ display: block;
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+
60
63
  .datatable-frame {
61
64
  border: var(--bs-border-width) solid var(--bs-border-color);
62
65
  border-radius: var(--bs-border-radius);
@@ -69,10 +72,6 @@
69
72
  overflow: hidden;
70
73
  }
71
74
 
72
- // Pin the actions column to the inline-end edge so the menu trigger stays
73
- // in view at every scroll position; the opaque background hides the cells
74
- // sliding underneath. Row menus are temporarily moved under body while open
75
- // (see datatable.js), so the wrapper keeps its overflow and scroll offset.
76
75
  .datatable-frame [data-datatable-column="actions"] {
77
76
  position: sticky;
78
77
  inset-inline-end: 0;
@@ -93,8 +92,6 @@
93
92
  );
94
93
  }
95
94
 
96
- // Card menus still live inside the card frame, so lift that frame while a
97
- // card menu is open.
98
95
  .datatable-card-frame:has(.datatable-card .dropdown-menu.show) {
99
96
  position: relative;
100
97
  z-index: $zindex-dropdown;
@@ -127,11 +124,18 @@
127
124
  vertical-align: middle;
128
125
  }
129
126
 
130
- // The toolbar's flex layout belongs to the component itself, not to
131
- // Bootstrap layout utilities on the macro markup: the narrow-screen grid
132
- // override below can replace it without an !important fight against
133
- // .d-flex, and the trailing actions group owns its own
134
- // margin-inline-start: auto alignment instead of .ms-auto.
127
+ .datatable-col > .btn-link,
128
+ .datatable-card-title > .btn-link {
129
+ max-inline-size: 100%;
130
+ white-space: normal;
131
+ overflow-wrap: anywhere;
132
+ text-align: inherit;
133
+ }
134
+
135
+ .datatable-table > :not(caption) > * > .datatable-col:nth-child(1 of :not(.datatable-col-hidden)) {
136
+ padding-inline-start: $spacer;
137
+ }
138
+
135
139
  .datatable-toolbar {
136
140
  display: flex;
137
141
  flex-wrap: wrap;
@@ -147,11 +151,6 @@
147
151
  margin-inline-start: auto;
148
152
  }
149
153
 
150
- // Bootstrap's .form-control defaults to width: 100%, so inside the
151
- // toolbar's flex row the search input claims the entire line on its own
152
- // and pushes the Status/Priority facet triggers onto a wrapped second
153
- // line even when there is room beside it. A max-width (not a fixed width)
154
- // caps it on wide toolbars while still letting it shrink on narrow ones.
155
154
  .datatable-toolbar-query {
156
155
  display: flex;
157
156
  flex: 0 1 22rem;
@@ -177,11 +176,6 @@
177
176
  width: 100%;
178
177
  }
179
178
 
180
- // A filter menu is a direct child so Bootstrap can anchor it to the trigger.
181
- // Clip the joined control while the menu is closed, matching the normal
182
- // input-group surface; release that clipping only while the menu is open so
183
- // the positioned dropdown can escape the control without leaking focus or
184
- // child paint past its rounded border.
185
179
  .datatable-searchbar:not(:has(> .dropdown-menu.show)) {
186
180
  overflow: hidden;
187
181
  }
@@ -198,8 +192,6 @@
198
192
  position: absolute;
199
193
  inset-block-start: 50%;
200
194
  inset-inline-start: 0.625rem;
201
- // Bootstrap raises a focused form control to z-index: 5. Keep the
202
- // decorative icon above that opaque surface so focus does not hide it.
203
195
  z-index: 6;
204
196
  display: inline-flex;
205
197
  align-items: center;
@@ -273,11 +265,6 @@
273
265
  min-width: 0;
274
266
  }
275
267
 
276
- // Only the option rows scroll; the facet/back-button header and the search
277
- // box above them stay in normal flow outside this nested list, so they
278
- // never move. list-style/margin/padding reset because this <ul> nests
279
- // inside a .dropdown-menu <li>, which does not get Bootstrap's own
280
- // dropdown-menu list reset applied to it a second time.
281
268
  .datatable-facet-options,
282
269
  .datatable-filter-options {
283
270
  max-height: $moo-datatable-facet-options-max-height;
@@ -619,10 +606,6 @@
619
606
  display: inline-flex;
620
607
  }
621
608
 
622
- // A card view is a grid, not a stack: on md/lg/tablet-and-wider screens a
623
- // single edge-to-edge card reads as an oversized table row, not a card.
624
- // auto-fill + a minimum card width wraps into as many columns as the
625
- // viewport allows and falls back to one column once it can't.
626
609
  .datatable-cards {
627
610
  --moo-datatable-card-min-width: #{$moo-datatable-card-min-width};
628
611
 
@@ -647,7 +630,11 @@
647
630
  align-items: center;
648
631
  padding: 0.5rem 0.75rem;
649
632
  border-bottom: var(--bs-border-width) solid var(--bs-border-color);
650
- background-color: var(--bs-tertiary-bg);
633
+ background-color: color-mix(in srgb, var(--moo-surface) 50%, var(--moo-muted-surface));
634
+ }
635
+
636
+ .datatable-card-header:not(:has(> .datatable-card-select)) {
637
+ grid-template-columns: minmax(0, 1fr) auto;
651
638
  }
652
639
 
653
640
  .datatable-card-select {
@@ -704,11 +691,6 @@
704
691
  padding: 0.375rem 0.875rem;
705
692
  }
706
693
 
707
- // .datatable-col-hidden is a single class (display: none) declared above
708
- // .datatable-card-row's own display: grid; a tied, later-wins cascade let
709
- // the row's grid display beat the hide, so the View dropdown's column
710
- // toggles never reached the card list. Two classes outrank one regardless
711
- // of source order.
712
694
  .datatable-card-row.datatable-col-hidden {
713
695
  display: none;
714
696
  }
@@ -751,9 +733,6 @@
751
733
  background-color: var(--moo-muted-surface);
752
734
  }
753
735
 
754
- // Sticky action cells paint a gradient instead of using the row's
755
- // background-color, so selected rows need the same surface at the opaque end
756
- // of that fade.
757
736
  .datatable-row-selected > [data-datatable-column="actions"] {
758
737
  --moo-datatable-actions-cell-bg: var(--moo-muted-surface);
759
738
  background: var(--moo-muted-surface);
@@ -821,12 +800,6 @@
821
800
  margin: 0;
822
801
  }
823
802
 
824
- // .datatable-facet-item__indicator is Data Table's own facet-dropdown item
825
- // (datatable_facet_item(), not the shared dropdown_toggle_item() macro), so
826
- // it can't inherit .dropdown-item-check__indicator's shared rule (scss/
827
- // components/_dropdown.scss) directly -- it restates the same
828
- // $form-check-input-* tokens and checked treatment so the facet dropdown and
829
- // the filter-picker (which does use the shared indicator) read identically.
830
803
  .datatable-facet-item__indicator {
831
804
  --moo-datatable-facet-item-border-radius: #{$moo-datatable-facet-item-border-radius};
832
805
 
@@ -852,10 +825,6 @@
852
825
  height: 0.75rem;
853
826
  }
854
827
 
855
- // Bootstrap has no "inactive filter" button variant, so the faceted-filter
856
- // trigger is unstyled `.btn.btn-sm` by default and gets its own dashed,
857
- // muted-token treatment here. Selected filters keep the same calm outline
858
- // while the inline summary carries the active value.
859
828
  .datatable-facet-trigger {
860
829
  --bs-btn-active-bg: var(--bs-body-bg);
861
830
  --bs-btn-active-border-color: var(--bs-border-color);
@@ -931,11 +900,6 @@
931
900
  white-space: nowrap;
932
901
  }
933
902
 
934
- // Bootstrap has no floating/overlay toolbar primitive, so the selection
935
- // bulk-actions bar (shown only once at least one row is selected) is a
936
- // Moo-owned fixed-position surface. It reuses Bootstrap's own shadow,
937
- // border, and radius tokens; only the fixed positioning, translucency, and
938
- // backdrop blur have no existing Bootstrap utility to consume.
939
903
  .datatable-bulk-actions {
940
904
  --moo-datatable-bulk-actions-hover-scale: #{$moo-datatable-bulk-actions-hover-scale};
941
905
  --moo-datatable-bulk-actions-backdrop-blur: #{$moo-datatable-bulk-actions-backdrop-blur};
@@ -970,9 +934,6 @@
970
934
  white-space: nowrap;
971
935
  }
972
936
 
973
- // Catalog preview chrome: caps the full-bleed release-review preview pages.
974
- // A Sass knob keeps the geometry on the ordered settings aggregate instead
975
- // of a component-local literal.
976
937
  .datatable-preview-shell {
977
938
  max-width: $moo-datatable-preview-shell-max-width;
978
939
  }
@@ -1,3 +1,4 @@
1
+ /* Date Picker */
1
2
  .moo-datepicker {
2
3
  --moo-datepicker-width: #{$moo-datepicker-width};
3
4
  --moo-datepicker-popover-offset: #{$moo-datepicker-popover-offset};
@@ -1,8 +1,4 @@
1
- // Dialog is the Moo catalog name for Bootstrap's Modal component, so this
2
- // partial retunes Bootstrap's own --bs-modal-* custom properties toward the
3
- // design reference: a larger radius, a quieter border tone, a borderless
4
- // header, one shared surface, and a lighter-weight tightly-set title, instead
5
- // of Bootstrap's own sharper-cornered, divided, heavier-titled look.
1
+ /* Dialog */
6
2
  .modal {
7
3
  --bs-modal-border-radius: var(--bs-border-radius-xl);
8
4
  --bs-modal-border-color: var(--moo-border);
@@ -13,9 +9,6 @@
13
9
  }
14
10
 
15
11
  .modal[tabindex]:focus-visible {
16
- // Bootstrap focuses the modal root when it opens. The shared focus rule
17
- // intentionally covers generic tabindex controls, so reset that container
18
- // ring here without weakening focus treatment for actual controls.
19
12
  outline: none;
20
13
  }
21
14
 
@@ -24,8 +17,6 @@
24
17
  }
25
18
 
26
19
  .modal-title {
27
- // A literal size, not $font-size-base: this project's own base is 14px,
28
- // but the reference dialog title sits at a plain, un-scaled 16px regardless.
29
20
  font-size: 1rem;
30
21
  font-weight: $font-weight-medium;
31
22
  }
@@ -35,8 +26,6 @@
35
26
  }
36
27
 
37
28
  .modal-header:has(> .btn-close) {
38
- // Keep the dismiss control in a stable top/end corner, like the reference
39
- // dialog surface, while reserving room so long titles cannot run beneath it.
40
29
  position: relative;
41
30
  padding-inline-end: calc(
42
31
  var(--bs-modal-padding) + #{$btn-close-width} + #{$btn-close-padding-x * 2} + 0.5rem
@@ -76,7 +65,5 @@
76
65
  }
77
66
 
78
67
  .modal-footer {
79
- // Bootstrap derives this from --bs-modal-padding minus half the button
80
- // gap (12px here); the reference footer keeps the full 16px card padding.
81
68
  padding: $spacer;
82
69
  }
@@ -1,5 +1,4 @@
1
- // Dropdown uses Bootstrap's own consumption variables. These aliases give
2
- // the native menu the reference's surface without a private Moo token layer.
1
+ /* Dropdown */
3
2
  .dropdown-menu {
4
3
  --bs-dropdown-bg: var(--moo-surface);
5
4
  --bs-dropdown-border-color: var(--moo-border);
@@ -13,8 +12,6 @@
13
12
  --bs-dropdown-link-disabled-color: var(--moo-disabled-foreground);
14
13
  --bs-dropdown-header-color: var(--moo-muted-foreground);
15
14
 
16
- // Bootstrap gates dropdown shadow behind $enable-shadows, so apply the
17
- // shared elevation token directly after bridging it to --bs-dropdown-box-shadow.
18
15
  box-shadow: var(--bs-dropdown-box-shadow);
19
16
  }
20
17
 
@@ -26,29 +23,21 @@
26
23
  gap: $spacer * 0.5;
27
24
  }
28
25
 
29
- // Bootstrap's own .dropdown-item bundles :hover and :focus into the same
30
- // highlight (vendor/bootstrap/scss/_dropdown.scss), so an item that still
31
- // holds DOM focus from a prior mouse click (clicking a button leaves it
32
- // focused) keeps its highlight after the mouse moves to hover a different
33
- // item -- two rows lit up at once. Resetting the stale, non-keyboard focus
34
- // back to resting style (only when the item isn't also genuinely hovered,
35
- // so real hover still wins) leaves exactly one item highlighted at a time;
36
- // real keyboard :focus-visible navigation is untouched, since it's a subset
37
- // of :focus and this selector explicitly excludes it.
38
26
  .dropdown-item:focus:not(:focus-visible):not(:hover) {
39
27
  color: var(--bs-dropdown-link-color);
40
28
  background-color: transparent;
41
29
  }
42
30
 
31
+ .dropdown-menu:has(.dropdown-item:hover) .dropdown-item.active:not(:hover) {
32
+ background-color: transparent;
33
+ }
34
+
43
35
  .dropdown-item > [data-icon] {
44
36
  width: 1rem;
45
37
  height: 1rem;
46
38
  flex: 0 0 auto;
47
39
  }
48
40
 
49
- // Destructive dropdown items use the shared --moo-destructive token so the
50
- // red stays in sync with every other destructive control. Matches the
51
- // reference pattern: red text at rest, destructive-tinted bg on hover.
52
41
  .dropdown-item[data-variant="destructive"] {
53
42
  color: var(--moo-destructive) !important;
54
43
  }
@@ -79,14 +68,6 @@
79
68
  background-color: var(--bs-dropdown-link-hover-bg);
80
69
  }
81
70
 
82
- // A checkbox-style menu item can't nest a real <input type="checkbox"> inside
83
- // its <button> (invalid, inaccessible markup), so this indicator stands in
84
- // for one. Sized off Bootstrap's own $form-check-input-* tokens and always
85
- // showing its box (not opacity-toggled to nothing) so it reads as the same
86
- // checkable control as Checkbox itself, wherever a menu uses it -- Menubar,
87
- // Context Menu, and Data Table's column-visibility, page-size, and faceted
88
- // filter menus all compose this one indicator rather than each inventing
89
- // their own checkbox-adjacent look.
90
71
  .dropdown-item-check__indicator {
91
72
  --moo-dropdown-item-check-border-radius: #{$moo-dropdown-item-check-border-radius};
92
73
 
@@ -101,9 +82,6 @@
101
82
  color: transparent;
102
83
  }
103
84
 
104
- // Matches Checkbox's own checked fill (scss/components/_checkbox.scss:
105
- // $form-check-input-checked-bg-color, resolved to --moo-foreground) so a
106
- // checked menu item and a checked Checkbox read as the same control.
107
85
  .dropdown-item-check.active .dropdown-item-check__indicator {
108
86
  background-color: var(--moo-foreground);
109
87
  border-color: var(--moo-foreground);
@@ -115,12 +93,6 @@
115
93
  height: 0.75rem;
116
94
  }
117
95
 
118
- // Single-choice ("radio") menu items reuse the same indicator markup as the
119
- // checkbox variant above but read as a plain checkmark-on-select instead: no
120
- // boxed square, since a box implies each item has its own independent on/off
121
- // state, which is wrong when only one item in the group can ever be active
122
- // (e.g. Data Table's page-size menu). The icon stays hidden until the item
123
- // is active, matching how the reference and native menus mark single-choice items.
124
96
  .dropdown-item-check--radio .dropdown-item-check__indicator {
125
97
  border-color: transparent;
126
98
  background-color: transparent;
@@ -150,28 +122,13 @@
150
122
  height: 0.875rem;
151
123
  }
152
124
 
153
- // The Sidebar header/footer triggers (team switcher, account menu) span
154
- // the sidebar's full width, and the design reference matches its menu
155
- // width to that trigger so it extends past the sidebar edge into the content
156
- // column. Bootstrap's own dropdown-menu only sizes to its own content, so
157
- // match the trigger via the shared positioned dropdown ancestor's width
158
- // instead. Scoped by data-slot attribute, not a sidebar class selector, so
159
- // this stays inside the dropdown component's own selector ownership.
160
125
  [data-slot="sidebar-header"] .dropdown-menu,
161
126
  [data-slot="sidebar-footer"] .dropdown-menu {
162
127
  --moo-dropdown-sidebar-min-width: #{$moo-dropdown-sidebar-min-width};
163
128
 
164
- // unquote(): libsass resolves max() as its own Sass function and rejects
165
- // var() as a non-numeric argument, so escape it through to plain CSS
166
- // (same technique already used for clamp() in the catalog stylesheet).
167
129
  min-width: #{unquote("max(100%, var(--moo-dropdown-sidebar-min-width))")};
168
130
  }
169
131
 
170
- // Header identity menus reuse the Sidebar account-menu body. Keep the same
171
- // comfortable width when the shared body is hosted by the global navbar
172
- // instead of a Sidebar slot; ordinary Header dropdowns remain content-sized.
173
- // The trigger marker is the integration contract, so Dropdown does not reach
174
- // into Sidebar's private class namespace to identify the menu body.
175
132
  [data-identity-trigger="1"] + .dropdown-menu {
176
133
  --moo-dropdown-sidebar-min-width: #{$moo-dropdown-sidebar-min-width};
177
134
  min-width: var(--moo-dropdown-sidebar-min-width);
@@ -198,9 +155,6 @@
198
155
  }
199
156
  }
200
157
 
201
- // The lg Offcanvas breakpoint still renders the sidebar as a drawer on
202
- // tablets. Keep identity menus inside that drawer until the desktop fixed
203
- // overlay contract becomes active.
204
158
  @include media-breakpoint-between(sm, lg) {
205
159
  [data-slot="sidebar-header"] .dropend > .dropdown-menu,
206
160
  [data-slot="sidebar-footer"] .dropend > .dropdown-menu {
@@ -1,22 +1,14 @@
1
+ /* Field */
1
2
  .field {
2
3
  display: flex;
3
4
  flex-direction: column;
4
5
  gap: $moo-field-gap;
5
6
 
6
- // Bootstrap's own .form-label carries a margin-bottom of its own
7
- // (form-label-margin-bottom), which would stack on top of this flex gap
8
- // and roughly double the real spacing the design reference's flat
9
- // gap-only model produces.
10
7
  > .form-label {
11
8
  margin-bottom: 0;
12
- // $form-label-font-weight is unset (inherits normal body weight); the
13
- // reference's own field label sits at a medium weight instead.
14
9
  font-weight: $font-weight-medium;
15
10
  }
16
11
 
17
- // Bootstrap owns the control and .invalid-feedback state. Field mirrors
18
- // the reference's wrapper-level invalid state with data-invalid, so labels
19
- // inherit the destructive color only when the field itself opts into that state.
20
12
  &[data-invalid="true"] {
21
13
  color: var(--bs-form-invalid-color);
22
14
  }
@@ -48,18 +40,9 @@
48
40
  pointer-events: none;
49
41
  }
50
42
 
51
- // field_description()/field_error() keep Bootstrap's own .form-text /
52
- // .invalid-feedback class alongside these (the latter is load-bearing:
53
- // Bootstrap's own validity sibling-selector CSS targets that exact class
54
- // name to show/hide it), so every default here is a token override on top
55
- // of Bootstrap's own rule rather than a full replacement of it.
56
43
  .field-description,
57
44
  .field-error {
58
45
  margin-top: 0;
59
- // .form-text/.invalid-feedback both default to Bootstrap's smaller
60
- // $font-size-sm scale; the reference's own description/error text sit
61
- // at the same size as the label itself, which this project's own
62
- // $font-size-base already matches exactly (0.875rem).
63
46
  font-size: $font-size-base;
64
47
  }
65
48
 
@@ -68,16 +51,10 @@
68
51
  flex-direction: column;
69
52
  gap: $moo-field-group-gap;
70
53
 
71
- // Inside a gap-managed group a separator adds no spacing of its own;
72
- // Bootstrap's default block margins on <hr> would otherwise stack on
73
- // top of the group gap and double the visual distance.
74
54
  > hr {
75
55
  margin-block: 0;
76
56
  }
77
57
 
78
- // A public Bootstrap .row is an explicit layout request from the consumer.
79
- // Preserve its responsive columns and let Bootstrap's gutters own spacing
80
- // instead of turning the row back into a vertical, gap-managed field stack.
81
58
  &.row {
82
59
  flex-direction: row;
83
60
  gap: 0;
@@ -90,29 +67,16 @@
90
67
  gap: $moo-field-form-gap;
91
68
  }
92
69
 
93
- // Bootstrap's own <legend> reboot rule (float/width/padding/line-height)
94
- // still applies here; this only retunes its type scale. A dedicated class
95
- // rather than reusing .form-label, since Field owns this legend's styling
96
- // independently of Radio Group's own separate <legend class="form-label">
97
- // choice.
98
70
  .field-legend {
99
71
  font-size: $moo-field-legend-font-size;
100
72
  font-weight: $font-weight-medium;
101
73
  margin-bottom: $moo-field-legend-margin-bottom;
102
74
  }
103
75
 
104
- // One standard, token-based gap between a fieldset's direct building
105
- // blocks (the legend/description header, field groups, lone fields).
106
- // Sibling margins rather than flex gap: Bootstrap's reboot floats
107
- // <legend> to give it native block flow, and turning the fieldset into a
108
- // flex container would silently opt out of that contract.
109
76
  .field-fieldset > * + * {
110
77
  margin-top: $moo-field-fieldset-gap;
111
78
  }
112
79
 
113
- // The optional description directly under the legend is part of the
114
- // header unit: it sits on the legend's own tight margin instead of the
115
- // standard section gap.
116
80
  .field-fieldset > .field-legend + .field-description {
117
81
  margin-top: 0;
118
82
  }
@@ -125,9 +89,6 @@
125
89
  margin-top: 0;
126
90
  }
127
91
 
128
- // A separator() placed between two fieldsets (or field-group sections) is
129
- // a much quieter division in the design reference than Bootstrap's own
130
- // default <hr> opacity renders.
131
92
  .field-fieldset + hr {
132
93
  opacity: $moo-field-separator-opacity;
133
94
  }
@@ -1,9 +1,8 @@
1
+ /* Input */
1
2
  .form-control {
2
3
  min-height: $input-height;
3
4
  }
4
5
 
5
- // Text inputs should visually match the outer box of an Input Group, whose
6
- // border is promoted to the parent wrapper.
7
6
  .form-control:not(textarea):not([type="file"]):not(.form-control-sm, .form-control-lg) {
8
7
  min-height: calc(#{$input-height} + #{$input-border-width} * 2);
9
8
  }
@@ -17,9 +16,6 @@
17
16
  position: relative;
18
17
  }
19
18
 
20
- // Chromium's clock icon is the primary pointer trigger for the native time
21
- // picker. Keep the indicator covering the whole control but visually hidden so
22
- // pointer users retain picker access without the icon showing.
23
19
  .form-control[type="time"]::-webkit-calendar-picker-indicator {
24
20
  position: absolute;
25
21
  inset: 0;
@@ -1,16 +1,10 @@
1
- // Bootstrap's native input-group keeps borders/radii on each child and trims
2
- // only the inner-facing seams (see the Bootstrap 5.3.3 input-group Sass
3
- // source). Moo promotes the border to the parent so icon/text addons read as
4
- // regions inside one compound input, but still leaves each edge child's own
5
- // outer corner radius aligned with the parent instead of flattening it.
1
+ /* Input Group */
6
2
  .input-group:not(:has(> .form-control[type="file"])) {
7
3
  border: $input-border-width solid $input-border-color;
8
4
  border-radius: var(--moo-input-group-border-radius);
9
5
  background-color: var(--moo-input-bg, transparent);
10
6
  }
11
7
 
12
- // Keep compound surfaces clipped, but let a directly attached menu escape so
13
- // Bootstrap's positioned dropdown remains visible outside the input row.
14
8
  .input-group:not(:has(> .form-control[type="file"])):not(:has(> .dropdown-menu)) {
15
9
  overflow: hidden;
16
10
  }
@@ -19,25 +13,6 @@
19
13
  --moo-input-group-border-radius: var(--bs-border-radius-pill);
20
14
  }
21
15
 
22
- // references/bootstrap/5.3.3/scss/forms/_input-group.scss:9 sets align-items:
23
- // stretch on .input-group by default (this previously overrode it to center;
24
- // restored here since stretch is what makes children fill the row with no
25
- // centering rounding). Bootstrap's own .input-group carries no border of its
26
- // own — border-height math lives entirely on each child, which is why a
27
- // plain $input-height "just works" there. Moo moves that border to the
28
- // parent instead, so the parent's own border needs its own extra space on
29
- // top of $input-height, or children sized to a full $input-height (as
30
- // .form-control and .btn both are here) overflow the parent's inner content
31
- // box by exactly 2 * $input-border-width — this is what actually poked past
32
- // the rounded corner, not the corner radius itself.
33
- //
34
- // Excludes groups with validation feedback (feedback wraps below the control)
35
- // and the block-align case (a [data-align] addon wrapping to its own row
36
- // below/above the control): the fixed single-row height here has higher
37
- // specificity than the auto-height override further down for that case
38
- // (:not(:has(...)) chains add specificity these two-class selectors don't),
39
- // so without this exclusion the parent stayed pinned to one row's height
40
- // and the wrapped addon rendered outside its own border box entirely.
41
16
  .input-group:not(:has(> :is(.valid-feedback, .invalid-feedback, .valid-tooltip, .invalid-tooltip))):not(:has(textarea)):not(:has(> .form-control[type="file"])):not(:has(> [data-align])) {
42
17
  height: calc(#{$input-height} + #{$input-border-width} * 2);
43
18
  }
@@ -59,11 +34,6 @@
59
34
  border-radius: 0;
60
35
  }
61
36
 
62
- // Restore the outer corner Bootstrap's own edge child would already carry:
63
- // Bootstrap only zeros the inner-facing side of a non-edge child, never the
64
- // outer side of an edge one. The trailing side also matches when a dropdown
65
- // menu is the real last DOM child, since the visible trailing control sits
66
- // right before it.
67
37
  .input-group:not(:has(> .form-control[type="file"])) > :first-child:not(ul) {
68
38
  border-start-start-radius: var(--moo-input-group-border-radius);
69
39
  border-end-start-radius: var(--moo-input-group-border-radius);
@@ -78,10 +48,6 @@
78
48
  border: var(--bs-btn-border-width) var(--bs-border-style) var(--bs-btn-border-color);
79
49
  }
80
50
 
81
- // Ghost buttons inside a compound input should read as inline affordances, not
82
- // standalone buttons. Keep Bootstrap's --bs-btn-* contract, but scope the
83
- // hover/active surface to the small control instead of repainting the compound
84
- // input edge.
85
51
  .input-group > .btn-ghost {
86
52
  --bs-btn-hover-bg: transparent;
87
53
  --bs-btn-active-bg: transparent;
@@ -90,10 +56,6 @@
90
56
  border-radius: var(--moo-input-group-border-radius) !important;
91
57
  }
92
58
 
93
- // Match the reference input-group affordance: the compound input stays clear,
94
- // while the hovered control gets a small inset surface. The surface reuses
95
- // the existing Moo muted token and Bootstrap radius instead of adding a
96
- // component-specific color or geometry value.
97
59
  .input-group > .btn-ghost::before {
98
60
  position: absolute;
99
61
  inset: $spacer * 0.25;
@@ -134,8 +96,6 @@
134
96
 
135
97
  .input-group > .input-group-text {
136
98
  color: var(--bs-secondary-color);
137
- // bg intentionally omitted — Bootstrap's addon token consumes the same
138
- // --moo-input-bg surface as the parent compound input.
139
99
  }
140
100
 
141
101
  .input-group > .input-group-text:first-child {
@@ -184,17 +144,7 @@
184
144
  background-color: transparent;
185
145
  }
186
146
 
187
- // The input-group's own compound focus-visible ring lives in
188
- // foundations/_focus.scss, next to the other compound-control rings, instead
189
- // of here -- see that file's comment for why.
190
147
 
191
- // Scoped to the native form control itself, not "any focused child": the
192
- // input-group already carries its own compound glow from the rule above, so
193
- // only the .form-control/.form-select that would otherwise draw a second,
194
- // competing glow needs its own default suppressed. A bare > :focus also
195
- // matched dropdown-toggle buttons living in the same input-group (e.g. Data
196
- // Table's filter-picker trigger), and its (0,4,0) specificity beat those
197
- // buttons' own :focus-visible keyboard ring outright.
198
148
  .input-group:not(:has(> .form-control[type="file"])) > :is(.form-control, .form-select):focus {
199
149
  outline: 0;
200
150
  box-shadow: none;
@@ -1,3 +1,4 @@
1
+ /* Keyboard keys */
1
2
  kbd {
2
3
  display: inline-flex;
3
4
  height: $moo-chip-height;
@@ -1,3 +1,4 @@
1
+ /* Menubar */
1
2
  .menubar {
2
3
  gap: $moo-menubar-gap;
3
4
  padding: $moo-menubar-padding;