avo 4.0.0.beta.30 → 4.0.0.beta.32

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/assets/builds/avo/application.css +222 -158
  4. data/app/assets/builds/avo/application.js +2 -2
  5. data/app/assets/builds/avo/application.js.map +3 -3
  6. data/app/assets/images/avo/favicon-dark.ico +0 -0
  7. data/app/assets/images/avo/logo-dark.png +0 -0
  8. data/app/assets/images/avo/logomark-dark.png +0 -0
  9. data/app/assets/stylesheets/css/components/breadcrumbs.css +44 -4
  10. data/app/assets/stylesheets/css/components/color_scheme_switcher.css +17 -22
  11. data/app/assets/stylesheets/css/components/ui/card.css +5 -0
  12. data/app/assets/stylesheets/css/components/ui/description_list.css +1 -1
  13. data/app/assets/stylesheets/css/fields/code.css +1 -1
  14. data/app/assets/stylesheets/css/layout.css +187 -38
  15. data/app/assets/stylesheets/css/scrollbar.css +12 -0
  16. data/app/assets/stylesheets/css/sidebar.css +2 -2
  17. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +3 -3
  18. data/app/javascript/js/controllers/actions_overflow_controller.js +22 -3
  19. data/app/javascript/js/controllers/sidebar_controller.js +11 -0
  20. data/app/views/avo/partials/_footer.html.erb +1 -1
  21. data/app/views/avo/partials/_navbar.html.erb +31 -49
  22. data/app/views/layouts/avo/application.html.erb +20 -24
  23. data/lib/avo/concerns/breadcrumbs.rb +1 -0
  24. data/lib/avo/configuration/appearance.rb +27 -37
  25. data/lib/avo/version.rb +1 -1
  26. data/lib/generators/avo/templates/locales/avo.ar.yml +1 -0
  27. data/lib/generators/avo/templates/locales/avo.de.yml +1 -0
  28. data/lib/generators/avo/templates/locales/avo.en.yml +1 -0
  29. data/lib/generators/avo/templates/locales/avo.es.yml +1 -0
  30. data/lib/generators/avo/templates/locales/avo.fr.yml +1 -0
  31. data/lib/generators/avo/templates/locales/avo.it.yml +1 -0
  32. data/lib/generators/avo/templates/locales/avo.ja.yml +1 -0
  33. data/lib/generators/avo/templates/locales/avo.nb.yml +1 -0
  34. data/lib/generators/avo/templates/locales/avo.nl.yml +1 -0
  35. data/lib/generators/avo/templates/locales/avo.nn.yml +1 -0
  36. data/lib/generators/avo/templates/locales/avo.pl.yml +1 -0
  37. data/lib/generators/avo/templates/locales/avo.pt-BR.yml +1 -0
  38. data/lib/generators/avo/templates/locales/avo.pt.yml +1 -0
  39. data/lib/generators/avo/templates/locales/avo.ro.yml +1 -0
  40. data/lib/generators/avo/templates/locales/avo.ru.yml +1 -0
  41. data/lib/generators/avo/templates/locales/avo.tr.yml +1 -0
  42. data/lib/generators/avo/templates/locales/avo.ua.yml +1 -0
  43. data/lib/generators/avo/templates/locales/avo.zh-TW.yml +1 -0
  44. data/lib/generators/avo/templates/locales/avo.zh.yml +1 -0
  45. metadata +1 -1
Binary file
Binary file
@@ -1,5 +1,41 @@
1
1
  .breadcrumbs {
2
- @apply w-full;
2
+ @apply sticky z-40 w-full bg-primary mb-4;
3
+ /* top: calc(var(--top-navbar-height) + var(--spacing) * 4); */
4
+ top: calc(var(--top-navbar-height) + var(--spacing) * 2);
5
+ margin-top: calc(var(--spacing) * -2 + 1px);
6
+
7
+ /* Fake background so the content scrolling underneath the breadcrumbs doesn't look like it's bleeding through */
8
+ &::before {
9
+ content: "";
10
+ position: absolute;
11
+ width: 100%;
12
+ top: 0;
13
+ left: 0;
14
+ right: 0;
15
+ height: calc(0.5rem);
16
+ transform: translateY(-100%);
17
+ background: var(--color-primary);
18
+ pointer-events: none;
19
+ }
20
+ }
21
+
22
+ /* Soft fade strip just below the sticky breadcrumbs — eases the seam between
23
+ the breadcrumb bar and the content scrolling underneath. */
24
+ .breadcrumbs::after {
25
+ content: "";
26
+ position: absolute;
27
+ top: auto;
28
+ bottom: 0;
29
+ left: 0;
30
+ right: 0;
31
+ transform: translateY(100%);
32
+ height: --spacing(4);
33
+ background: linear-gradient(
34
+ to bottom,
35
+ var(--color-primary),
36
+ color-mix(in oklab, var(--color-primary), transparent 100%)
37
+ );
38
+ pointer-events: none;
3
39
  }
4
40
 
5
41
  .breadcrumbs__container {
@@ -11,10 +47,10 @@
11
47
  }
12
48
 
13
49
  .breadcrumb-element {
14
- @apply inline-flex items-center gap-2 font-medium leading-6 text-sm rounded-lg px-2 py-0.5 text-content-secondary;
50
+ @apply inline-flex items-center gap-2 font-medium leading-6 text-sm rounded-lg px-1 py-0.5 text-content-secondary;
15
51
 
16
52
  &:first-child {
17
- @apply ps-0;
53
+ @apply -ms-1;
18
54
  }
19
55
  }
20
56
 
@@ -42,6 +78,10 @@
42
78
  }
43
79
 
44
80
  &:hover {
45
- @apply bg-secondary;
81
+ @apply bg-tertiary;
82
+
83
+ .breadcrumb-element__text {
84
+ @apply text-content;
85
+ }
46
86
  }
47
87
  }
@@ -3,9 +3,15 @@
3
3
  @apply inline-flex items-center;
4
4
  }
5
5
 
6
+ /* The switcher's trigger surfaces (inline pill + compact trigger) sit on the
7
+ dark top-navbar in both schemes, so they use primitive neutral tokens that
8
+ don't flip with light/dark — matching breadcrumbs and the sidebar toggle.
9
+ The popover panels below stay theme-adaptive because they render on the
10
+ page surface, not the navbar. */
11
+
6
12
  /* Inline pill — three sections sit in a single rounded container */
7
13
  .color-scheme-switcher--inline {
8
- @apply gap-1 p-1 rounded-lg bg-secondary border border-tertiary;
14
+ @apply gap-1 p-1 rounded-lg bg-avo-neutral-800 border border-avo-neutral-700;
9
15
  }
10
16
 
11
17
  /* Compact — a single icon trigger that opens a unified panel */
@@ -15,7 +21,7 @@
15
21
 
16
22
  .color-scheme-switcher__compact-trigger {
17
23
  @apply inline-flex items-center gap-1 h-8 ps-2 pe-1.5 rounded-lg transition-all duration-150;
18
- @apply text-content-secondary bg-secondary border border-tertiary hover:bg-tertiary;
24
+ @apply text-avo-neutral-300 bg-avo-neutral-800 border border-avo-neutral-700 hover:bg-avo-neutral-700 hover:text-avo-neutral-50;
19
25
  @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-1;
20
26
  @apply cursor-pointer;
21
27
  }
@@ -114,33 +120,22 @@
114
120
  }
115
121
 
116
122
  .color-scheme-switcher__button {
117
- @apply inline-flex items-center justify-center p-1.5 rounded-md transition-all duration-150 text-content-secondary hover:bg-tertiary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-1 active:scale-95;
123
+ @apply inline-flex items-center justify-center p-1.5 rounded-md transition-all duration-150 text-avo-neutral-300 hover:bg-avo-neutral-700 hover:text-avo-neutral-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-1 active:scale-95;
118
124
  @apply border-0 bg-transparent cursor-pointer;
119
125
  }
120
126
 
121
- /* Scheme buttons - active states using scheme selection classes */
122
- .color-scheme-switcher__button[data-scheme="light"] {
123
- @apply bg-transparent text-content-secondary shadow-none;
124
- }
125
-
126
- .scheme-light .color-scheme-switcher__button[data-scheme="light"] {
127
- @apply bg-primary text-content shadow-sm;
128
- }
129
-
130
- .color-scheme-switcher__button[data-scheme="dark"] {
131
- @apply bg-transparent text-content-secondary shadow-none;
132
- }
133
-
134
- .scheme-dark .color-scheme-switcher__button[data-scheme="dark"] {
135
- @apply bg-primary text-content shadow-sm;
136
- }
137
-
127
+ /* Scheme buttons - idle vs active state. The active "pill" stands out against
128
+ the neutral-800 inline pill background by going darker (neutral-950). */
129
+ .color-scheme-switcher__button[data-scheme="light"],
130
+ .color-scheme-switcher__button[data-scheme="dark"],
138
131
  .color-scheme-switcher__button[data-scheme="auto"] {
139
- @apply bg-transparent text-content-secondary shadow-none;
132
+ @apply bg-transparent text-avo-neutral-300 shadow-none;
140
133
  }
141
134
 
135
+ .scheme-light .color-scheme-switcher__button[data-scheme="light"],
136
+ .scheme-dark .color-scheme-switcher__button[data-scheme="dark"],
142
137
  .scheme-auto .color-scheme-switcher__button[data-scheme="auto"] {
143
- @apply bg-primary text-content shadow-sm;
138
+ @apply bg-avo-neutral-950 text-avo-neutral-50 shadow-sm;
144
139
  }
145
140
 
146
141
  .color-scheme-switcher__icon {
@@ -1,6 +1,11 @@
1
1
  /* Card Component - Based on Figma Design System */
2
2
  .card {
3
3
  @apply flex flex-col items-start self-stretch rounded-xl border border-tertiary bg-secondary;
4
+
5
+ /* Contain horizontal overflow so a wide inner scroller (e.g. .card__wrapper
6
+ wrapping a wide table) can't bleed past the card and push the document
7
+ wider than the viewport on narrow screens. */
8
+ overflow-x: clip;
4
9
  }
5
10
 
6
11
  /* Card variants */
@@ -1,3 +1,3 @@
1
1
  .description-list {
2
- @apply w-full flex flex-wrap overflow-auto flex-1 divide-y divide-tertiary flex-col;
2
+ @apply w-full flex flex-wrap overflow-auto flex-1 divide-y divide-tertiary;
3
3
  }
@@ -37,7 +37,7 @@
37
37
  }
38
38
 
39
39
  /* Lift the main stacking context above the navbar (z-index: 100) when CodeMirror is fullscreen */
40
- .main-content-area:has(.CodeMirror-fullscreen) {
40
+ .main-content:has(.CodeMirror-fullscreen) {
41
41
  z-index: 101;
42
42
  }
43
43
 
@@ -1,5 +1,17 @@
1
- :root {
2
- --top-navbar-height: 4rem;
1
+ @theme {
2
+ --top-navbar-height: 3.5rem;
3
+
4
+ --navbar-bg: var(--color-avo-neutral-900);
5
+ --navbar-notch-radius: 1rem;
6
+ --navbar-notch-color: var(--navbar-bg);
7
+
8
+ --border-color: var(--color-tertiary);
9
+ }
10
+
11
+ @layer theme {
12
+ .dark {
13
+ --border-color: var(--color-background);
14
+ }
3
15
  }
4
16
 
5
17
  .skip-to-content {
@@ -24,47 +36,44 @@
24
36
  height: var(--top-navbar-height);
25
37
  }
26
38
 
27
- /* The sidebar width stays the same regardless of the sidebar open state. */
28
- /* The content width and margin offset changes based on the sidebar open state and the :lg breakpoint. */
29
- .main {
39
+ /* Layout variables live on the outermost sidebar host so they cascade to both
40
+ the top-navbar and .main (which are siblings, not parent/child).
41
+ --content-width is intentionally relative to .main's content box (which is
42
+ already offset by --sidebar-offset-size via padding), so centering via
43
+ mx-auto happens inside the area to the right of the sidebar — not the page. */
44
+ [data-controller~="sidebar"] {
30
45
  --sidebar-width: --spacing(64);
31
46
  --sidebar-offset-size: 0rem;
32
- --content-width: calc(100% - var(--sidebar-offset-size) - var(--spacing) * 2);
47
+ --content-width: calc(100% - var(--spacing));
48
+ }
33
49
 
34
- @media (min-width: theme(--breakpoint-lg)) {
35
- &.sidebar-open {
36
- --sidebar-offset-size: var(--sidebar-width);
37
- }
50
+ @media (min-width: theme(--breakpoint-lg)) {
51
+ [data-controller~="sidebar"][data-sidebar-open-value="true"] {
52
+ --sidebar-offset-size: var(--sidebar-width);
38
53
  }
54
+ }
39
55
 
40
- @apply mt-(--top-navbar-height);
56
+ [data-controller~="sidebar"][data-sidebar-open-value="false"] {
57
+ .main-content {
58
+ @apply ms-0;
59
+ }
60
+ }
41
61
 
42
- .main-content-area {
43
- @apply w-(--content-width) ms-1 flex flex-1 flex-col z-50 fixed rounded-2xl bg-primary shadow-content py-2 lg:py-4 px-2 lg:px-4 h-full;
62
+ .main {
63
+ padding-top: var(--top-navbar-height);
64
+ padding-inline-start: calc(var(--sidebar-offset-size) - var(--spacing));
65
+ transition: padding 0.1s ease-in-out;
66
+ }
44
67
 
45
- transition: margin 0.1s ease-in-out, width 0.1s ease-in-out;
68
+ .main-content {
69
+ @apply w-(--content-width) flex flex-col bg-primary py-2 lg:py-4 px-2 lg:px-4 border-s border-(--border-color) ms-1;
70
+ transition: margin 0.1s ease-in-out, width 0.1s ease-in-out;
71
+ min-height: calc(100dvh - var(--top-navbar-height) - var(--spacing));
72
+ }
46
73
 
47
- height: calc(100dvh - var(--spacing) - var(--top-navbar-height));
48
74
 
49
- .scrollable-wrapper {
50
- @apply overflow-y-auto h-full flex-1 flex flex-col;
51
- @apply px-0.5; /* add some space for the view type toggle buttons to not be cut off by the overflow. this might be needed to be handled differently with a wrapper inside the scrolling area */
52
- @apply pt-1; /* add some space for the button focus ring to not be cut off by the overflow */
53
- }
54
- .avo-container {
55
- @apply flex flex-1 flex-col justify-between;
56
- }
57
- }
58
-
59
- &.sidebar-open {
60
- /* Add padding to the main area to allow for the sidebar to expand. */
61
- .main-content-area {
62
- @media (min-width: theme(--breakpoint-lg)) {
63
- margin-inline-start: calc(var(--sidebar-width) + var(--spacing));
64
- width: var(--content-width);
65
- }
66
- }
67
- }
75
+ .main-content__container {
76
+ @apply flex flex-1 flex-col px-2;
68
77
  }
69
78
 
70
79
  .container-large {
@@ -80,7 +89,130 @@
80
89
  }
81
90
 
82
91
  .top-navbar {
83
- @apply z-100 fixed w-full flex shrink-0 items-center px-4 lg:px-2 space-x-4 rtl:space-x-reverse lg:space-x-0;
92
+ @apply z-100 fixed top-0 inset-x-0 w-full shrink-0 pointer-events-none;
93
+
94
+ /* Three-region grid: start | center | end. start/end share remaining space
95
+ equally (1fr each) so the center column (search) stays visually anchored
96
+ to the navbar's midpoint regardless of breadcrumb length. */
97
+ display: grid;
98
+ grid-template-columns: 1fr auto 1fr;
99
+ align-items: center;
100
+ gap: 1rem;
101
+ padding-inline: 0.75rem;
102
+
103
+ background-color: var(--navbar-bg);
104
+
105
+ > * {
106
+ @apply pointer-events-auto;
107
+ }
108
+
109
+ .breadcrumbs__container {
110
+ @apply flex-nowrap overflow-x-auto;
111
+ }
112
+
113
+ /* Inverted arches at the top corners of the content area — a square just
114
+ below the navbar painted with the notch color, with a quarter-circle cut
115
+ out of one bottom corner. The cut reveals the content beneath, so the
116
+ content looks like it has a rounded top corner without actually applying
117
+ border-radius to it (which would clip its scroll). */
118
+ &::before,
119
+ &::after {
120
+ content: "";
121
+ position: absolute;
122
+ top: 100%;
123
+ width: var(--navbar-notch-radius);
124
+ height: var(--navbar-notch-radius);
125
+ pointer-events: none;
126
+ }
127
+
128
+ &::before {
129
+ left: 0;
130
+ background: radial-gradient(
131
+ circle var(--navbar-notch-radius) at 100% 100%,
132
+ transparent 99%,
133
+ var(--navbar-notch-color) 100%
134
+ );
135
+ }
136
+
137
+ &::after {
138
+ right: 0;
139
+ background: radial-gradient(
140
+ circle var(--navbar-notch-radius) at 0% 100%,
141
+ transparent 99%,
142
+ var(--navbar-notch-color) 100%
143
+ );
144
+ }
145
+ }
146
+
147
+ .top-navbar__start {
148
+ @apply flex items-center gap-3 min-w-0 h-full;
149
+ }
150
+
151
+ .top-navbar__logo {
152
+ @apply shrink-0;
153
+
154
+ height: calc(var(--top-navbar-height) - --spacing(6));
155
+ }
156
+
157
+ .top-navbar__center {
158
+ @apply flex items-center justify-center min-w-0;
159
+
160
+ /* Search input is rendered against the dark navbar in both light/dark
161
+ themes, so it uses primitive neutral tokens that are stable across modes
162
+ — matching the breadcrumbs and sidebar-toggle treatment. */
163
+ .search-input__input {
164
+ background-color: var(--color-avo-neutral-800);
165
+ border-color: var(--color-avo-neutral-700);
166
+ color: var(--color-avo-neutral-50);
167
+
168
+ &::placeholder {
169
+ color: var(--color-avo-neutral-400);
170
+ }
171
+ }
172
+
173
+ .search-input__prefix,
174
+ .search-input__suffix {
175
+ color: var(--color-avo-neutral-400);
176
+ }
177
+
178
+ .search-input__suffix kbd {
179
+ background-color: var(--color-avo-neutral-700);
180
+ border-color: var(--color-avo-neutral-600);
181
+ color: var(--color-avo-neutral-200);
182
+ box-shadow: none;
183
+ }
184
+
185
+ /* On mobile, collapse the navbar search to just the input + search icon —
186
+ hide the keyboard shortcut suffix and drop the reserved end-padding so it
187
+ stays compact next to the logo. */
188
+ @media (width < theme(--breakpoint-sm)) {
189
+ .search-input__suffix {
190
+ display: none;
191
+ }
192
+
193
+ .search-input__input.search-input__input--with-shortcut,
194
+ .search-input__input.search-input__input--with-two-key-shortcut {
195
+ padding-inline-end: calc(
196
+ var(--input-icon-offset) +
197
+ var(--input-icon-gap)
198
+ );
199
+ }
200
+ }
201
+ }
202
+
203
+ .top-navbar__end {
204
+ @apply flex items-center justify-end gap-4 min-w-0 h-full;
205
+ }
206
+
207
+ /* Links inside the always-dark navbar use primitive neutral tokens so they
208
+ stay readable in both light and dark mode — matching the search input and
209
+ sidebar toggle treatment. */
210
+ .top-navbar a {
211
+ color: var(--color-avo-neutral-300);
212
+
213
+ &:hover {
214
+ color: var(--color-avo-neutral-50);
215
+ }
84
216
  }
85
217
 
86
218
  /* Sidebar toggle: choose icon via navbar variables; visibility driven by data attribute */
@@ -100,14 +232,31 @@ button[data-sidebar-state="closed"] .sidebar-toggle-icon--closed {
100
232
  @apply inline-flex;
101
233
  }
102
234
 
103
- /* Default hidden state - LTR: sidebar slides in from left */
235
+ /* The toggle sits on the dark top-navbar, so it uses primitive neutral tokens
236
+ that are stable across light/dark — matching the breadcrumbs treatment. */
237
+ button[data-sidebar-toggle-icon] {
238
+ --btn-text-color: var(--color-avo-neutral-300);
239
+
240
+ &:hover {
241
+ --btn-text-color: var(--color-avo-neutral-50);
242
+ background-color: var(--color-avo-neutral-800);
243
+ }
244
+ }
245
+
246
+ /* Default hidden state - LTR: sidebar slides in from left.
247
+ Sidebar starts below the navbar (matching .main's padding-top), so the
248
+ navbar is uninterrupted across the full viewport width. */
104
249
  .avo-sidebar {
105
- @apply fixed z-60 top-0 flex-1 border-e border-tertiary lg:border-none mt-(--top-navbar-height) h-[calc(100dvh-var(--top-navbar-height))] bg-background lg:bg-transparent w-(--sidebar-width);
250
+ @apply fixed z-50 start-0 flex-1 border-e border-tertiary lg:border-none bg-background lg:bg-transparent w-(--sidebar-width) dark:bg-primary;
106
251
 
252
+
253
+ top: var(--top-navbar-height);
254
+ height: calc(100dvh - var(--top-navbar-height));
107
255
  transform: translateX(-100%);
108
- transition: transform 0.1s ease;
256
+ transition: transform 0.1s ease-in-out;
109
257
  }
110
258
 
259
+
111
260
  /* RTL: sidebar slides in from right when hidden */
112
261
  html[dir="rtl"] .avo-sidebar {
113
262
  transform: translateX(100%);
@@ -1,3 +1,15 @@
1
+ /* Keep auto-scrolled targets (focus, scrollIntoView, anchor jumps, hash links)
2
+ below the sticky navbar. Applied to every likely scroll container so it
3
+ works whether the document, the body, or the main content area is what
4
+ actually scrolls. */
5
+ html,
6
+ body,
7
+ .main-content,
8
+ .scrollable-wrapper {
9
+ scroll-padding-top: calc(var(--top-navbar-height) + var(--spacing) * 2);
10
+ scroll-padding-bottom: calc(var(--spacing) * 2);
11
+ }
12
+
1
13
  /* total width */
2
14
  body.os-pc .mac-styled-scrollbar::-webkit-scrollbar {
3
15
  background-color: none;
@@ -285,11 +285,11 @@
285
285
  /* ================================================ */
286
286
 
287
287
  .sidebar-status {
288
- @apply p-4 border-t border-tertiary;
288
+ @apply p-4 border-t border-(--border-color);
289
289
  }
290
290
 
291
291
  .sidebar-status__link {
292
- @apply px-4 py-2 border border-tertiary rounded-sm flex justify-between items-center w-full text-content text-sm;
292
+ @apply px-4 py-2 border border-(--border-color) rounded-sm flex justify-between items-center w-full text-content text-sm;
293
293
  }
294
294
 
295
295
  .sidebar-status__indicator {
@@ -5,7 +5,7 @@
5
5
  data-association="<%= @field.id %>"
6
6
  data-association-class="<%= @field&.target_resource&.model_class || nil %>"
7
7
  >
8
- <div class="description-list">
8
+ <div class="w-full flex flex-wrap">
9
9
  <%= field_wrapper(**field_wrapper_args, label_for: @field.polymorphic_form_field_label, help: @field.polymorphic_help || '') do %>
10
10
  <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [Avo.resource_manager.get_resource_by_model_class(type.to_s).name, type.to_s] },
11
11
  {
@@ -29,11 +29,11 @@
29
29
  <%= @form.hidden_field @field.type_input_foreign_key %>
30
30
  <% end %>
31
31
  <% end %>
32
- <div data-belongs-to-field-target="container" class="hidden border-0"></div>
32
+ <div data-belongs-to-field-target="container" class="hidden <%= @field.width_class %>"></div>
33
33
  <% @field.types.each do |type| %>
34
34
  <template data-belongs-to-field-target="type" data-type="<%= type %>">
35
35
  <div data-polymorphic-type="<%= type %>">
36
- <%= field_wrapper(**field_wrapper_args.merge!(data: reload_data), label: Avo.resource_manager.get_resource_by_model_class(type.to_s).name) do %>
36
+ <%= field_wrapper(**field_wrapper_args.merge!(data: reload_data), label: Avo.resource_manager.get_resource_by_model_class(type.to_s).name, class: "!w-full") do %>
37
37
  <% if @field.is_searchable? %>
38
38
  <%= render Avo::Pro::SearchableAssociations::AutocompleteComponent.new form: @form,
39
39
  disabled: disabled,
@@ -7,11 +7,30 @@ export default class extends Controller {
7
7
  panelList: Boolean,
8
8
  }
9
9
 
10
- // get the table or grid component, if both null, get the panel body (for show page for example)
10
+ // Table/grid on index pages; main content region on show/edit and other non-list views.
11
11
  get parentTarget() {
12
12
  return document.querySelector('[data-component-name="avo/view_types/table_component"]')
13
13
  || document.querySelector('[data-component-name="avo/view_types/grid_component"]')
14
- || document.querySelector('.main-content-area .scrollable-wrapper') // TODO: to be fixed when we get to the row controls
14
+ || document.querySelector('#main-content')
15
+ }
16
+
17
+ // Parent nodes grow with page content, but overflow should be measured against
18
+ // the visible viewport — the old .scrollable-wrapper had a fixed height for this.
19
+ get visibleParentDimensions() {
20
+ const parent = this.parentTarget
21
+
22
+ if (!parent) {
23
+ return { top: 0, left: 0, right: window.innerWidth, bottom: window.innerHeight }
24
+ }
25
+
26
+ const rect = parent.getBoundingClientRect()
27
+
28
+ return {
29
+ top: rect.top,
30
+ left: rect.left,
31
+ right: rect.right,
32
+ bottom: Math.min(rect.bottom, window.innerHeight),
33
+ }
15
34
  }
16
35
 
17
36
  // Check if the document is in RTL mode
@@ -28,7 +47,7 @@ export default class extends Controller {
28
47
  this.element.style.display = 'block'
29
48
  this.childDimensions = this.contentTarget.getBoundingClientRect()
30
49
  this.element.style.display = ''
31
- this.parentDimensions = this.parentTarget.getBoundingClientRect()
50
+ this.parentDimensions = this.visibleParentDimensions
32
51
 
33
52
  this.adjustOverflow()
34
53
  }
@@ -121,12 +121,23 @@ export default class extends Controller {
121
121
  toggleSidebar() {
122
122
  if (this.sidebarTarget.classList.contains('hidden')) {
123
123
  this.sidebarTarget.classList.remove('hidden')
124
+
125
+ // The sidebar starts the session as display:none when closed. Removing
126
+ // `hidden` and toggling `sidebar-open` in the same tick would let the
127
+ // browser collapse both style changes into one repaint — meaning the
128
+ // transform: translateX transition would never fire on the very first
129
+ // open. Force a reflow so the browser commits the post-hidden state
130
+ // (translateX(-100%) visible) before we flip to translateX(0).
131
+ this.mainAreaTarget.offsetHeight // eslint-disable-line no-unused-expressions
124
132
  }
125
133
  this.mainAreaTarget.classList.toggle('sidebar-open')
126
134
 
127
135
  Cookies.set(this.cookieKey, this.newValue(Cookies.get(this.cookieKey)))
128
136
 
129
137
  const isOpen = this.mainAreaTarget.classList.contains('sidebar-open')
138
+ // Keep the controller's openValue in sync so --sidebar-offset-size flips,
139
+ // which drives the navbar spacer width and .main padding transitions.
140
+ this.openValue = isOpen
130
141
  this.setToggleButtonsState(isOpen ? 'open' : 'closed')
131
142
  }
132
143
 
@@ -1,3 +1,3 @@
1
- <div class="text-center text-sm text-gray-700 <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>">
1
+ <div class="text-center text-sm text-content-secondary <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>">
2
2
  <a href="https://avohq.io/" target="_blank">Avo</a> · &copy; <%= Date.today.year %> AvoHQ · v<%= Avo::VERSION %>
3
3
  </div>
@@ -1,60 +1,42 @@
1
1
  <%
2
- # Icons shown when sidebar is open (collapse) vs closed (expand). Change these to customize.
3
2
  sidebar_open_icon = 'layout-sidebar-left-collapse'
4
3
  sidebar_closed_icon = 'layout-sidebar-left-expand'
5
4
  sidebar_state = @sidebar_open ? 'open' : 'closed'
6
5
  %>
7
6
  <%= content_tag :nav, class: class_names("top-navbar", {"print:hidden": Avo.configuration.hide_layout_when_printing}) do %>
8
- <div class="flex items-center space-x-2 rtl:space-x-reverse w-auto lg:w-64 shrink-0 h-full py-2">
9
- <div class="flex items-center gap-0">
10
- <%= a_button class: 'lg:hidden',
11
- size: :sm,
12
- style: :text,
13
- data: {
14
- action: 'click->sidebar#toggleSidebarOnMobile',
15
- sidebar_toggle_icon: true,
16
- sidebar_state: sidebar_state
17
- },
18
- aria: { label: "Toggle sidebar" } do %>
19
- <span class="sidebar-toggle-icon sidebar-toggle-icon--open">
20
- <%= svg sidebar_open_icon, class: 'h-4 button__icon' %>
21
- </span>
22
- <span class="sidebar-toggle-icon sidebar-toggle-icon--closed">
23
- <%= svg sidebar_closed_icon, class: 'h-4 button__icon' %>
24
- </span>
25
- <% end %>
7
+ <div class="top-navbar__start">
8
+ <%# Mobile always needs a toggle to reach the overlay sidebar. Desktop toggle
9
+ is optional and controlled by sidebar_toggle_visible. %>
10
+ <%= a_button class: class_names('shrink-0', 'lg:hidden': !Avo.configuration.sidebar_toggle_visible),
11
+ size: :sm,
12
+ style: :text,
13
+ data: {
14
+ action: 'click->sidebar#toggleSidebarForViewport',
15
+ sidebar_toggle_icon: true,
16
+ sidebar_state: sidebar_state
17
+ },
18
+ aria: { label: "Toggle sidebar" } do %>
19
+ <span class="sidebar-toggle-icon sidebar-toggle-icon--open">
20
+ <%= svg sidebar_open_icon, class: 'h-4 button__icon' %>
21
+ </span>
22
+ <span class="sidebar-toggle-icon sidebar-toggle-icon--closed">
23
+ <%= svg sidebar_closed_icon, class: 'h-4 button__icon' %>
24
+ </span>
25
+ <% end %>
26
26
 
27
- <% if Avo.configuration.sidebar_toggle_visible %>
28
- <%= a_button class: '!hidden lg:!inline-flex',
29
- size: :sm,
30
- style: :text,
31
- data: {
32
- action: 'click->sidebar#toggleSidebar',
33
- sidebar_toggle_icon: true,
34
- sidebar_state: sidebar_state
35
- },
36
- aria: { label: "Toggle sidebar" } do %>
37
- <span class="sidebar-toggle-icon sidebar-toggle-icon--open">
38
- <%= svg sidebar_open_icon, class: 'h-4 button__icon' %>
39
- </span>
40
- <span class="sidebar-toggle-icon sidebar-toggle-icon--closed">
41
- <%= svg sidebar_closed_icon, class: 'h-4 button__icon' %>
42
- </span>
43
- <% end %>
44
- <% end %>
27
+ <div class="top-navbar__logo">
28
+ <%= render partial: "avo/partials/logo" %>
45
29
  </div>
46
- <%= render partial: "avo/partials/logo" %>
47
30
  </div>
48
- <div class="flex-1 flex items-center justify-between lg:justify-start space-x-2 rtl:space-x-reverse sm:space-x-8 lg:px-1">
49
- <%= render Avo::Pro::GlobalSearch::InputComponent.new(resource:) if defined?(Avo::Pro::GlobalSearch::InputComponent) %>
50
- <div class="flex justify-between w-full h-full items-center m-0">
51
- <%= render partial: "avo/partials/header" %>
52
- <%= render partial: "avo/partials/color_scheme_switcher" %>
53
- </div>
31
+
32
+ <div class="top-navbar__center">
33
+ <% if defined?(Avo::Pro::GlobalSearch::InputComponent) %>
34
+ <%= render Avo::Pro::GlobalSearch::InputComponent.new(resource: @resource) %>
35
+ <% end %>
36
+ </div>
37
+
38
+ <div class="top-navbar__end">
39
+ <%= render partial: "avo/partials/header" %>
40
+ <%= render partial: "avo/partials/color_scheme_switcher" %>
54
41
  </div>
55
42
  <% end %>
56
- <!-- This is a hack to fix the issue with the navbar not being accesible because of the .main-content-wrapper padding-->
57
- <!-- TODO: when implementing the final layout (search covered issue) see if we still need this hack-->
58
- <%#
59
- <div class="relative opacity-0 h-16 pointer-events-none"></div>
60
- %>