avo 4.0.0.beta.38 → 4.0.0.beta.39

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '035975ce0d0f50467d9b474fd7ced61e2300c8bfea942fff46e1babf5f7cb860'
4
- data.tar.gz: 0b23292242df415414e95f22b91fa19688e298cc0fb2be6afc0d3037a7e5a870
3
+ metadata.gz: bb52cb35a8c7fa2313a889555006158bcef98a0a6c485193e00a870a122a7128
4
+ data.tar.gz: 311597bb570d5bd21b769f85414ac510e6e0d60a66a995c03fd38732b2a9bf61
5
5
  SHA512:
6
- metadata.gz: c96c448c9a9e12bb3390c15c92402bb9041bb1fce51c3ad5ee2c778c3757f838ed821beff913646bc3cf99e2973a8666babbbed7e6ccbc6ab1eb04369f416397
7
- data.tar.gz: 9014338df7368dbef72768edd92dddf87159385adfbc76fc423f3b3da68f800a9ba894bdb1a2a7af1615ccd49ab79b97012187fcd1918ea3bf41cbf91222be63
6
+ metadata.gz: c1db80f20234f0df3044451fdcf31834b378f2e09780acf8e1afc2c409f7bc1c8f50c8a04aeddf89e81876d0bb743bab91c1cf90b9bdc5cc8f1dd465c1a1c6da
7
+ data.tar.gz: cf71d7af8d386873db2e123d1fe6f051e4b10150e8ee10e944d46cea6fac62ca053c2783154928a8ae31aff5dc7ef43216e0ae26df26e772841d46ad93ca7051
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (4.0.0.beta.38)
4
+ avo (4.0.0.beta.39)
5
5
  actionview (>= 6.1)
6
6
  active_link_to
7
7
  activerecord (>= 6.1)
@@ -6023,7 +6023,7 @@
6023
6023
  justify-content: flex-end;
6024
6024
  gap: calc(var(--spacing) * 3);
6025
6025
  }
6026
- .top-navbar a {
6026
+ .top-navbar .header-menu a {
6027
6027
  color: var(--color-avo-neutral-300);
6028
6028
  &:hover {
6029
6029
  color: var(--color-avo-neutral-50);
@@ -8217,6 +8217,34 @@
8217
8217
  line-height: var(--tw-leading, var(--text-sm--line-height));
8218
8218
  color: var(--color-content-secondary);
8219
8219
  }
8220
+ .count {
8221
+ display: inline-flex;
8222
+ min-width: calc(var(--spacing) * 4);
8223
+ align-items: center;
8224
+ justify-content: center;
8225
+ border-radius: var(--radius-sm);
8226
+ background-color: color-mix(in srgb, oklch(39.04% 0 89.88) 15%, transparent);
8227
+ @supports (color: color-mix(in lab, red, red)) {
8228
+ background-color: color-mix(in oklab, var(--color-accent) 15%, transparent);
8229
+ }
8230
+ padding-inline: calc(var(--spacing) * 1);
8231
+ padding-block: 1px;
8232
+ font-size: var(--text-xs);
8233
+ line-height: var(--tw-leading, var(--text-xs--line-height));
8234
+ --tw-leading: 1;
8235
+ line-height: 1;
8236
+ --tw-font-weight: var(--font-weight-medium);
8237
+ font-weight: var(--font-weight-medium);
8238
+ color: var(--color-content);
8239
+ --tw-numeric-spacing: tabular-nums;
8240
+ font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
8241
+ &:where(.dark, .dark *) {
8242
+ background-color: color-mix(in srgb, oklch(39.04% 0 89.88) 30%, transparent);
8243
+ @supports (color: color-mix(in lab, red, red)) {
8244
+ background-color: color-mix(in oklab, var(--color-accent) 30%, transparent);
8245
+ }
8246
+ }
8247
+ }
8220
8248
  :root {
8221
8249
  --input-py: calc(var(--spacing) * 1);
8222
8250
  --input-font-size: calc(var(--spacing) * 3.5);
@@ -8472,6 +8500,9 @@
8472
8500
  line-height: calc(var(--spacing) * 4);
8473
8501
  color: var(--color-content-secondary);
8474
8502
  }
8503
+ [data-field-type="boolean"] .field-wrapper__help {
8504
+ margin-top: calc(var(--spacing) * 0);
8505
+ }
8475
8506
  .field-wrapper__label-help {
8476
8507
  font-size: var(--text-xs);
8477
8508
  line-height: var(--tw-leading, var(--text-xs--line-height));
@@ -10664,19 +10695,23 @@
10664
10695
  }
10665
10696
  .modal__card {
10666
10697
  position: relative;
10698
+ display: flex;
10667
10699
  width: 100%;
10668
- height: 100%;
10669
10700
  flex-shrink: 0;
10701
+ flex-direction: column;
10702
+ max-height: calc(100dvh - 2rem);
10670
10703
  box-shadow: var(--shadow-modal);
10671
10704
  }
10672
10705
  .modal__card > .card {
10706
+ min-height: calc(var(--spacing) * 0);
10673
10707
  width: 100%;
10674
- height: 100%;
10708
+ flex: 1;
10675
10709
  }
10676
10710
  .modal__card > .card > .card__wrapper {
10677
10711
  display: flex;
10712
+ min-height: calc(var(--spacing) * 0);
10678
10713
  width: 100%;
10679
- height: 100%;
10714
+ flex: 1;
10680
10715
  flex-direction: column;
10681
10716
  }
10682
10717
  .modal__card .card__header {
@@ -10740,6 +10775,9 @@
10740
10775
  width: 100%;
10741
10776
  height: 100%;
10742
10777
  }
10778
+ .card__body.modal-form-body {
10779
+ padding: calc(var(--spacing) * 4) !important;
10780
+ }
10743
10781
  .modal__controls {
10744
10782
  display: flex;
10745
10783
  width: 100%;
@@ -10879,35 +10917,6 @@
10879
10917
  width: 100%;
10880
10918
  justify-content: space-between;
10881
10919
  }
10882
- .filters__count {
10883
- margin-inline-start: calc(var(--spacing) * 1);
10884
- display: inline-flex;
10885
- min-width: calc(var(--spacing) * 4);
10886
- align-items: center;
10887
- justify-content: center;
10888
- border-radius: var(--radius-sm);
10889
- background-color: color-mix(in srgb, oklch(39.04% 0 89.88) 15%, transparent);
10890
- @supports (color: color-mix(in lab, red, red)) {
10891
- background-color: color-mix(in oklab, var(--color-accent) 15%, transparent);
10892
- }
10893
- padding-inline: calc(var(--spacing) * 1);
10894
- padding-block: 1px;
10895
- font-size: var(--text-xs);
10896
- line-height: var(--tw-leading, var(--text-xs--line-height));
10897
- --tw-leading: 1;
10898
- line-height: 1;
10899
- --tw-font-weight: var(--font-weight-medium);
10900
- font-weight: var(--font-weight-medium);
10901
- color: var(--color-content);
10902
- --tw-numeric-spacing: tabular-nums;
10903
- font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
10904
- &:where(.dark, .dark *) {
10905
- background-color: color-mix(in srgb, oklch(39.04% 0 89.88) 30%, transparent);
10906
- @supports (color: color-mix(in lab, red, red)) {
10907
- background-color: color-mix(in oklab, var(--color-accent) 30%, transparent);
10908
- }
10909
- }
10910
- }
10911
10920
  .filters__panel {
10912
10921
  position: absolute;
10913
10922
  inset: auto;
@@ -144,6 +144,7 @@
144
144
  @import "./css/components/ui/popover_menu.css";
145
145
  @import "./css/components/ui/radio.css";
146
146
  @import "./css/components/ui/checkbox_list.css";
147
+ @import "./css/components/ui/count.css";
147
148
  @import "./css/components/input.css";
148
149
 
149
150
  @import "./css/components/field-wrapper.css";
@@ -10,6 +10,12 @@
10
10
  @apply text-content-secondary mt-2 text-xs leading-4;
11
11
  }
12
12
 
13
+ /* Boolean fields render a compact checkbox/toggle, so the help text sits
14
+ directly beneath it without the extra top margin other inputs need. */
15
+ [data-field-type="boolean"] .field-wrapper__help {
16
+ @apply mt-0;
17
+ }
18
+
13
19
  .field-wrapper__label-help {
14
20
  @apply text-content-secondary text-xs leading-none font-normal;
15
21
  }
@@ -2,15 +2,6 @@
2
2
  @apply relative flex w-full justify-between;
3
3
  }
4
4
 
5
- .filters__count {
6
- /* Compact pill: accent-tinted background keeps the "this filter is active"
7
- hint, neutral content text guarantees contrast in both schemes.
8
- (Using `text-accent` looked fine in light, but in dark mode `--color-accent`
9
- resolves to a *light* value — same hue and same value as the translucent
10
- bg, killing contrast. Neutral text sidesteps that.) */
11
- @apply inline-flex items-center justify-center ms-1 rounded-sm bg-accent/15 dark:bg-accent/30 text-content font-medium text-xs leading-none min-w-4 py-px px-1 tabular-nums;
12
- }
13
-
14
5
  .filters__panel {
15
6
  @apply absolute top-full z-40 mt-2 w-72 inset-auto sm:end-0 flex flex-col;
16
7
 
@@ -30,18 +30,25 @@
30
30
  }
31
31
 
32
32
  .modal__card {
33
- @apply relative size-full shrink-0;
33
+ @apply relative w-full shrink-0 flex flex-col;
34
+
35
+ /* Never exceed the viewport — keeps a tall (e.g. :auto height) modal fully
36
+ on screen so its body can scroll instead of being clipped by the centered
37
+ overlay. */
38
+ max-height: calc(100dvh - 2rem);
34
39
 
35
40
  box-shadow: var(--shadow-modal);
36
41
  }
37
42
 
38
- /* Modal-specific card overrides */
43
+ /* Modal-specific card overrides. The flex-1 + min-h-0 chain lets the card and
44
+ its wrapper fill the (capped) modal height and shrink, so the body's
45
+ overflow-auto kicks in while the header and footer stay pinned. */
39
46
  .modal__card > .card {
40
- @apply size-full;
47
+ @apply w-full flex-1 min-h-0;
41
48
  }
42
49
 
43
50
  .modal__card > .card > .card__wrapper {
44
- @apply size-full flex flex-col;
51
+ @apply w-full flex flex-col flex-1 min-h-0;
45
52
  }
46
53
 
47
54
  .modal__card .card__header {
@@ -72,6 +79,13 @@
72
79
  @apply size-full;
73
80
  }
74
81
 
82
+ /* Belongs-to "Create new" embeds an edit form in the modal. Drop the nested
83
+ panel chrome (background + border) and pad the body so the form breathes.
84
+ Scoped to match the `.card__body` selector above so the padding wins. */
85
+ .card__body.modal-form-body {
86
+ @apply !p-4;
87
+ }
88
+
75
89
  .modal__controls {
76
90
  @apply flex items-center justify-between py-2 px-3 w-full;
77
91
  }
@@ -0,0 +1,10 @@
1
+ .count {
2
+ /* Compact pill: accent-tinted background keeps the "this is active" hint,
3
+ neutral content text guarantees contrast in both schemes.
4
+ (Using `text-accent` looked fine in light, but in dark mode `--color-accent`
5
+ resolves to a *light* value — same hue and same value as the translucent
6
+ bg, killing contrast. Neutral text sidesteps that.)
7
+ Margin is intentionally omitted — callers add spacing (e.g. `ms-1`) for
8
+ their own layout. */
9
+ @apply inline-flex items-center justify-center rounded-sm bg-accent/15 dark:bg-accent/30 text-content font-medium text-xs leading-none min-w-4 py-px px-1 tabular-nums;
10
+ }
@@ -209,7 +209,7 @@
209
209
  /* Links inside the always-dark navbar use primitive neutral tokens so they
210
210
  stay readable in both light and dark mode — matching the search input and
211
211
  sidebar toggle treatment. */
212
- .top-navbar a {
212
+ .top-navbar .header-menu a {
213
213
  color: var(--color-avo-neutral-300);
214
214
 
215
215
  &:hover {
@@ -71,11 +71,11 @@ class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
71
71
  end
72
72
 
73
73
  def modal_args
74
+ # `modal_layout: true` is what routes the `new` action through the `avo/modal`
75
+ # layout. The modal's title, size and footer controls are rendered by
76
+ # `Avo::Views::ResourceEditComponent` when it detects this embedded context.
74
77
  {
75
- modal_layout: true,
76
- modal_width: :full,
77
- modal_height: :full,
78
- wrapper_class: "container mx-auto py-4"
78
+ modal_layout: true
79
79
  }
80
80
  end
81
81
 
@@ -9,7 +9,7 @@
9
9
  'data-tippy': 'tooltip' do %>
10
10
  <%= t 'avo.filters' %>
11
11
  <% if applied_filters_count.positive? %>
12
- <span class="filters__count"><%= applied_filters_count %></span>
12
+ <%= render Avo::UI::CountComponent.new(count: applied_filters_count, classes: "ms-1") %>
13
13
  <% end %>
14
14
  <% end %>
15
15
  <%= tag.div class: 'filters__panel css-animate-slide-down',
@@ -0,0 +1 @@
1
+ <span class="<%= class_names("count", @classes) %>"><%= @count %></span>
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Compact numeric pill used next to a label or title — e.g. the active-filter
4
+ # count on the filters bar or an unread-notification count. Extracted from the
5
+ # filters bar so the same accent-tinted style can be reused anywhere a small
6
+ # count needs to sit alongside text.
7
+ class Avo::UI::CountComponent < Avo::BaseComponent
8
+ prop :count
9
+ prop :classes, default: nil
10
+ end
@@ -13,29 +13,29 @@
13
13
  <%= build_form do |form| %>
14
14
  <%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
15
15
 
16
- <%= content_tag :div, class: "panel-spacer" do %>
17
- <% @resource.get_items.each_with_index do |item, index| %>
18
- <%= render Avo::Items::SwitcherComponent.new(
19
- resource: @resource,
20
- reflection: @reflection,
21
- item: item,
22
- index: index + 1,
23
- view: @view,
24
- parent_resource: @parent_resource,
25
- parent_record: @parent_record,
26
- form: form,
27
- parent_component: self,
28
- actions: @actions
29
- ) %>
16
+ <% if embedded_in_modal? %>
17
+ <%# The form wraps the modal so the footer submit button posts natively. %>
18
+ <%= render Avo::ModalComponent.new(title: title, width: :"4xl", body_class: "modal-form-body") do |c| %>
19
+ <% c.with_controls do %>
20
+ <% @resource.render_edit_controls.each do |control| %>
21
+ <%= render_control control %>
22
+ <% end %>
23
+ <% end %>
24
+
25
+ <%= content_tag :div, render_form_items(form), class: "panel-spacer" %>
30
26
  <% end %>
27
+ <% else %>
28
+ <%= content_tag :div, class: "panel-spacer" do %>
29
+ <%= render_form_items(form) %>
31
30
 
32
- <% if Avo.configuration.buttons_on_form_footers %>
33
- <%= render ui.panel do |panel| %>
34
- <% panel.with_footer do %>
35
- <%= render ui.panel_header do |header| %>
36
- <% header.with_controls do %>
37
- <% @resource.render_edit_controls.each do |control| %>
38
- <%= render_control control %>
31
+ <% if Avo.configuration.buttons_on_form_footers %>
32
+ <%= render ui.panel do |panel| %>
33
+ <% panel.with_footer do %>
34
+ <%= render ui.panel_header do |header| %>
35
+ <% header.with_controls do %>
36
+ <% @resource.render_edit_controls.each do |control| %>
37
+ <%= render_control control %>
38
+ <% end %>
39
39
  <% end %>
40
40
  <% end %>
41
41
  <% end %>
@@ -51,6 +51,35 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
51
51
  @resource.render_edit_controls
52
52
  end
53
53
 
54
+ # True when the form is shown inside a modal opened from a belongs_to
55
+ # "Create new" link. In that case the title moves to the modal header and the
56
+ # controls move to the modal footer, so the in-form panel header is skipped.
57
+ def embedded_in_modal?
58
+ params[:via_belongs_to_resource_class].present?
59
+ end
60
+
61
+ # Renders the form panels. When embedded in a modal the panel header is
62
+ # dropped — its title and controls live in the modal chrome instead.
63
+ def render_form_items(form)
64
+ items = @resource.get_items
65
+ items = items.reject(&:is_header?) if embedded_in_modal?
66
+
67
+ safe_join(items.each_with_index.map do |item, index|
68
+ render Avo::Items::SwitcherComponent.new(
69
+ resource: @resource,
70
+ reflection: @reflection,
71
+ item: item,
72
+ index: index + 1,
73
+ view: @view,
74
+ parent_resource: @parent_resource,
75
+ parent_record: @parent_record,
76
+ form: form,
77
+ parent_component: self,
78
+ actions: @actions
79
+ )
80
+ end)
81
+ end
82
+
54
83
  private
55
84
 
56
85
  def via_index?
@@ -37,6 +37,7 @@
37
37
 
38
38
  <div class="top-navbar__end">
39
39
  <%= render partial: "avo/partials/header" %>
40
+ <%= render Avo::Notifications::BellComponent.new if Avo.plugin_manager.installed?("avo-notifications") %>
40
41
  <%= render partial: "avo/partials/color_scheme_switcher" %>
41
42
  </div>
42
43
  <% end %>
@@ -64,7 +64,13 @@
64
64
  <%= content_tag :div,
65
65
  class: "relative flex items-center justify-between w-full" do %>
66
66
  <% if field.sortable && resource.sorting_supported? %>
67
- <%= link_to params.permit!.merge(sort_by: sort_by, sort_direction: sort_direction),
67
+ <%
68
+ # url_for(params) picks the alphabetically-first per-resource route,
69
+ # which on association tables routes sort links to the wrong resource.
70
+ sort_query_params = request.query_parameters.merge(sort_by: sort_by, sort_direction: sort_direction).compact
71
+ sort_url = "#{request.path}?#{sort_query_params.to_query}"
72
+ %>
73
+ <%= link_to sort_url,
68
74
  class: class_names("flex-1 flex justify-between", text_classes),
69
75
  'data-turbo-frame': params[:turbo_frame] do %>
70
76
  <%= field.table_header_label %>
@@ -1,7 +1,13 @@
1
1
  <%= turbo_frame_tag Avo::MODAL_FRAME_ID do %>
2
- <%= render(Avo::ModalComponent.new(width: @modal_width, height: @modal_height, body_class: "bg-backgrund")) do |c| %>
3
- <%= tag.div class: @wrapper_class do %>
4
- <%= yield %>
2
+ <% if params[:via_belongs_to_resource_class].present? %>
3
+ <%# Belongs-to "Create new" flow: the edit component renders its own modal
4
+ (title + footer controls) so the form can wrap the modal natively. %>
5
+ <%= yield %>
6
+ <% else %>
7
+ <%= render(Avo::ModalComponent.new(width: @modal_width, height: @modal_height, body_class: "bg-background")) do |c| %>
8
+ <%= tag.div class: @wrapper_class do %>
9
+ <%= yield %>
10
+ <% end %>
5
11
  <% end %>
6
12
  <% end %>
7
13
  <% end %>
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "4.0.0.beta.38" unless const_defined?(:VERSION)
2
+ VERSION = "4.0.0.beta.39" unless const_defined?(:VERSION)
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avo
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.beta.38
4
+ version: 4.0.0.beta.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Marin
@@ -273,6 +273,7 @@ files:
273
273
  - app/assets/stylesheets/css/components/ui/card.css
274
274
  - app/assets/stylesheets/css/components/ui/checkbox.css
275
275
  - app/assets/stylesheets/css/components/ui/checkbox_list.css
276
+ - app/assets/stylesheets/css/components/ui/count.css
276
277
  - app/assets/stylesheets/css/components/ui/description_list.css
277
278
  - app/assets/stylesheets/css/components/ui/dropdown.css
278
279
  - app/assets/stylesheets/css/components/ui/dropdown_card.css
@@ -681,6 +682,8 @@ files:
681
682
  - app/components/avo/u_i/badge_component.rb
682
683
  - app/components/avo/u_i/card_component.html.erb
683
684
  - app/components/avo/u_i/card_component.rb
685
+ - app/components/avo/u_i/count_component.html.erb
686
+ - app/components/avo/u_i/count_component.rb
684
687
  - app/components/avo/u_i/dropdown_card_component.html.erb
685
688
  - app/components/avo/u_i/dropdown_card_component.rb
686
689
  - app/components/avo/u_i/dropdown_component.html.erb