studio-engine 0.116.1 → 0.116.2.rc1

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: 0a2c1402ba7875aa502acdcdf8a273f216495fa2c3a37e00c9000622de11f52a
4
- data.tar.gz: 49ff67175f004ed66f671245e673a5b50090585a41448b28ce369c0ee148f1da
3
+ metadata.gz: 8fa12ba607e8798bdcb58e5b2758cc0a2fc57f20f298bf42b30fe8ce550e3723
4
+ data.tar.gz: 7f6c1fd58ad8722230cd3cf6992b9b8e9fc59c340ad7466f58dc77d9399b070c
5
5
  SHA512:
6
- metadata.gz: fb499cd28e29a024ac4d7c31eeaee28043a4e1d1ea841e801d11070b81bb518a861f06d7b52fb0f4ecc94c18c46de1529701a711b4d2cdc69e5f79ac06197c8c
7
- data.tar.gz: 93b0522427c0481f8777dc1612dc4da0ece932d2a51c0dc635c7fadff5c5d92abba37a9b8d2b285fdb2153fae047a81b52d94299df88261d8efe3d7c31d190cd
6
+ metadata.gz: 3887513540ba4056ec5d3d2c7d9a1b1b83cafc6e41005732ac582b091bb347db7a4400c6feab5b1c0f1cc87a7e7d08c4bb9bee8031f5ad7caa6a62d7bf752df3
7
+ data.tar.gz: 1da12039f78c9f35c8d28d03ff776d5b41184444b0cd133264578aaff3c441665065448a8d872cbc39d9ee4098fd6ea05bdbd0e114a1dacfd8f781b885201659
data/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.116.2 — 2026-10-28
8
+
9
+ ### Added
10
+
11
+ - **`Studio.navbar_name_in_rows`: the name stacks over the second row.** `true`
12
+ moves the signed-in name out of the account control to the end of the user
13
+ nav's row 1 (`[data-nav-tools]`, after the icons), so it sits directly over the
14
+ row-2 slot (a level or seeds bar), and the avatar stands alone in
15
+ `[data-nav-account]`, labelled with the name. The name is its own control of
16
+ the avatar's kind (`[data-nav-name-trigger]`: the host sidebar button, the link
17
+ sidebar button, the account link or plain text) and opens what the avatar
18
+ opens; the account group in `engine.css` lights gear, name and avatar from it
19
+ too. Below `md` the name control is `hidden` (no invisible tab stop) unless
20
+ `Studio.navbar_phone_name` shows the name there. `false`, the default, renders byte-identical to before. The scroll
21
+ timeline's name scale and `studio/nav_collapse`'s name width now skip the
22
+ avatar's `aria-hidden` box, so neither mistakes it for the name. The partial
23
+ reads it through `studio_navbar_name_in_rows?`, memoized per render like the
24
+ other navbar helpers, so one request may set `@studio_navbar_name_in_rows`.
25
+ Guarded by `test/integration/navbar_name_in_rows_render_test.rb` and
26
+ `e2e/navbar_name_in_rows.spec.js`.
27
+
28
+ ### Changed
29
+
30
+ - **`.gitignore` ignores `/.npmrc`.** The hub's `bin/agent-worktree` writes a
31
+ desk-local `.npmrc` (the npm script guard) before it links a desk's
32
+ `node_modules` to the primary's; unignored, that file would dirty the desk, so
33
+ desks fell back to `npm ci`. Development only; the packaged gem is unchanged.
34
+
7
35
  ## 0.116.1 — 2026-10-28
8
36
 
9
37
  ### Fixed
data/README.md CHANGED
@@ -1057,6 +1057,16 @@ name never fades. A browser without scroll timelines (Firefox today) ends a long
1057
1057
  name in an ellipsis instead. `false`, the default, keeps the name
1058
1058
  screen-reader text below `md`.
1059
1059
 
1060
+ ### Name in rows — `Studio.navbar_name_in_rows`
1061
+
1062
+ `true` ends the user nav's row 1 with the signed-in name, after the icons, so
1063
+ it stacks over the row-2 slot (`div2_slot`, or the engine's wallet-and-level
1064
+ row), and leaves the avatar alone in `[data-nav-account]` with the name as its
1065
+ `aria-label`. The name is its own control (`[data-nav-name-trigger]`) of the
1066
+ avatar's kind and opens what the avatar opens. It may shrink and truncate; the
1067
+ icons may not. `Studio.navbar_phone_name` still decides whether it shows below
1068
+ `md`. `false`, the default, keeps the name inside the account control.
1069
+
1060
1070
  ### Brand, root hook and layer tier — `Studio.navbar_brand`, `Studio.navbar_root_hook`, `Studio.navbar_z_tier`
1061
1071
 
1062
1072
  ```ruby
@@ -738,36 +738,38 @@ body.modal-open .studio-app-banner {
738
738
  (Studio.navbar_menu_trigger) and an account that opens the host's
739
739
  sidebar (Studio.navbar_account_sidebar). A host that sets both is
740
740
  saying they are its one menu, as Turf Monster's gear sidebar is.
741
+ With Studio.navbar_name_in_rows the name is its own control in row 1
742
+ ([data-nav-name-trigger]), and pointing at it lights the group too.
741
743
  An account link is never grouped. */
742
744
  :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]) [data-link-sidebar-trigger] svg,
743
745
  :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]) [data-nav-account] > [aria-hidden] > * {
744
746
  transition: transform 200ms ease, box-shadow 200ms ease;
745
747
  }
746
748
  :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]) [data-link-sidebar-trigger]:hover { background-color: transparent; }
747
- :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]):has([data-link-sidebar-trigger]:hover, [data-nav-account]:hover, [data-link-sidebar-trigger]:focus-visible, [data-nav-account]:focus-visible) [data-link-sidebar-trigger],
748
- :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]):has([data-link-sidebar-trigger]:hover, [data-nav-account]:hover, [data-link-sidebar-trigger]:focus-visible, [data-nav-account]:focus-visible) [data-nav-name] { color: var(--color-primary); }
749
- :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]):has([data-link-sidebar-trigger]:hover, [data-nav-account]:hover, [data-link-sidebar-trigger]:focus-visible, [data-nav-account]:focus-visible) [data-nav-account] > [aria-hidden] > * {
749
+ :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]):has([data-link-sidebar-trigger]:hover, [data-nav-account]:hover, [data-nav-name-trigger]:hover, [data-link-sidebar-trigger]:focus-visible, [data-nav-account]:focus-visible, [data-nav-name-trigger]:focus-visible) [data-link-sidebar-trigger],
750
+ :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]):has([data-link-sidebar-trigger]:hover, [data-nav-account]:hover, [data-nav-name-trigger]:hover, [data-link-sidebar-trigger]:focus-visible, [data-nav-account]:focus-visible, [data-nav-name-trigger]:focus-visible) [data-nav-name] { color: var(--color-primary); }
751
+ :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]):has([data-link-sidebar-trigger]:hover, [data-nav-account]:hover, [data-nav-name-trigger]:hover, [data-link-sidebar-trigger]:focus-visible, [data-nav-account]:focus-visible, [data-nav-name-trigger]:focus-visible) [data-nav-account] > [aria-hidden] > * {
750
752
  box-shadow:
751
753
  0 0 0 2px rgb(var(--color-primary-rgb) / 0.85),
752
754
  0 0 14px 2px rgb(var(--color-primary-rgb) / 0.45);
753
755
  }
754
756
  @media (prefers-reduced-motion: no-preference) {
755
- :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]):has([data-link-sidebar-trigger]:hover, [data-nav-account]:hover, [data-link-sidebar-trigger]:focus-visible, [data-nav-account]:focus-visible) [data-link-sidebar-trigger] svg { transform: rotate(45deg); }
757
+ :has(> [data-nav-tools]):has(> [data-nav-account][aria-controls~="studio-link-sidebar"]):has([data-link-sidebar-trigger]:hover, [data-nav-account]:hover, [data-nav-name-trigger]:hover, [data-link-sidebar-trigger]:focus-visible, [data-nav-account]:focus-visible, [data-nav-name-trigger]:focus-visible) [data-link-sidebar-trigger] svg { transform: rotate(45deg); }
756
758
  }
757
759
  :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])) [data-nav-menu-trigger] [aria-controls] svg,
758
760
  :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])) [data-nav-account] > [aria-hidden] > * {
759
761
  transition: transform 200ms ease, box-shadow 200ms ease;
760
762
  }
761
763
  :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])) [data-nav-menu-trigger] [aria-controls]:hover { background-color: transparent; }
762
- :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])):has([data-nav-menu-trigger] [aria-controls]:hover, [data-nav-account]:hover, [data-nav-menu-trigger] [aria-controls]:focus-visible, [data-nav-account]:focus-visible) [data-nav-menu-trigger] [aria-controls],
763
- :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])):has([data-nav-menu-trigger] [aria-controls]:hover, [data-nav-account]:hover, [data-nav-menu-trigger] [aria-controls]:focus-visible, [data-nav-account]:focus-visible) [data-nav-name] { color: var(--color-primary); }
764
- :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])):has([data-nav-menu-trigger] [aria-controls]:hover, [data-nav-account]:hover, [data-nav-menu-trigger] [aria-controls]:focus-visible, [data-nav-account]:focus-visible) [data-nav-account] > [aria-hidden] > * {
764
+ :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])):has([data-nav-menu-trigger] [aria-controls]:hover, [data-nav-account]:hover, [data-nav-name-trigger]:hover, [data-nav-menu-trigger] [aria-controls]:focus-visible, [data-nav-account]:focus-visible, [data-nav-name-trigger]:focus-visible) [data-nav-menu-trigger] [aria-controls],
765
+ :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])):has([data-nav-menu-trigger] [aria-controls]:hover, [data-nav-account]:hover, [data-nav-name-trigger]:hover, [data-nav-menu-trigger] [aria-controls]:focus-visible, [data-nav-account]:focus-visible, [data-nav-name-trigger]:focus-visible) [data-nav-name] { color: var(--color-primary); }
766
+ :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])):has([data-nav-menu-trigger] [aria-controls]:hover, [data-nav-account]:hover, [data-nav-name-trigger]:hover, [data-nav-menu-trigger] [aria-controls]:focus-visible, [data-nav-account]:focus-visible, [data-nav-name-trigger]:focus-visible) [data-nav-account] > [aria-hidden] > * {
765
767
  box-shadow:
766
768
  0 0 0 2px rgb(var(--color-primary-rgb) / 0.85),
767
769
  0 0 14px 2px rgb(var(--color-primary-rgb) / 0.45);
768
770
  }
769
771
  @media (prefers-reduced-motion: no-preference) {
770
- :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])):has([data-nav-menu-trigger] [aria-controls]:hover, [data-nav-account]:hover, [data-nav-menu-trigger] [aria-controls]:focus-visible, [data-nav-account]:focus-visible) [data-nav-menu-trigger] [aria-controls] svg { transform: rotate(45deg); }
772
+ :has(> [data-nav-tools] [data-nav-menu-trigger]):has(> [data-nav-account][data-profile-image-toggle]:not([aria-controls~="studio-link-sidebar"])):has([data-nav-menu-trigger] [aria-controls]:hover, [data-nav-account]:hover, [data-nav-name-trigger]:hover, [data-nav-menu-trigger] [aria-controls]:focus-visible, [data-nav-account]:focus-visible, [data-nav-name-trigger]:focus-visible) [data-nav-menu-trigger] [aria-controls] svg { transform: rotate(45deg); }
771
773
  }
772
774
  /* THE ACCOUNT ROW (components/_user_nav), from 768px. Every app whose
773
775
  navbar renders the engine's user nav gets this, Turf Monster included:
@@ -813,6 +815,11 @@ body.modal-open .studio-app-banner {
813
815
  .nav-shell [data-nav-tools] :has(> [data-nav-theme-toggle="desktop"]) { gap: calc(var(--nav-gap) - 8.6px); }
814
816
  .nav-shell :has(> [data-nav-tools]) { gap: calc(var(--nav-gap) - 6.3px); }
815
817
  .nav-shell [data-nav-account] { gap: var(--nav-gap); }
818
+ /* Studio.navbar_name_in_rows: the name ends row 1, so the row's gap is
819
+ now name to avatar, a whole --nav-gap, and gear to name is row 1's,
820
+ the same gap less the name's 6.3px side bearing (its gap-2 given back). */
821
+ .nav-shell :has(> [data-nav-tools] [data-nav-name-trigger]) { gap: var(--nav-gap); }
822
+ .nav-shell [data-nav-tools] [data-nav-name-trigger] { margin-inline-start: calc(var(--nav-gap) - 6.3px - 0.5rem); }
816
823
  .nav-shell [data-nav-tools][data-nav-rows="1"] > div:first-child { padding-inline: 0 !important; }
817
824
  .nav-shell [data-nav-rows="1"] + [data-nav-account] > [aria-hidden] > * {
818
825
  width: var(--nav-avatar);
@@ -906,7 +913,8 @@ body.modal-open .studio-app-banner {
906
913
  .nav-shell[data-nav-timeline] .nav-logo-link,
907
914
  .nav-shell[data-nav-timeline] [data-nav-account] > [aria-hidden],
908
915
  .nav-shell[data-nav-timeline] [data-nav-avatar-badge],
909
- .nav-shell[data-nav-timeline] [data-nav-account] > span:first-child,
916
+ .nav-shell[data-nav-timeline] [data-nav-account] > span:first-child:not([aria-hidden]),
917
+ .nav-shell[data-nav-timeline] [data-nav-name-trigger],
910
918
  .nav-shell[data-nav-timeline] [data-nav-tools],
911
919
  .nav-shell[data-nav-timeline] [data-nav-links] {
912
920
  animation-timing-function: ease-in-out;
@@ -917,7 +925,12 @@ body.modal-open .studio-app-banner {
917
925
  .nav-shell[data-nav-timeline] .nav-logo-link { transform-origin: left center; animation-name: nav-tl-brand; }
918
926
  .nav-shell[data-nav-timeline] [data-nav-account] > [aria-hidden],
919
927
  .nav-shell[data-nav-timeline] [data-nav-avatar-badge] { transform-origin: right center; animation-name: nav-tl-avatar; }
920
- .nav-shell[data-nav-timeline] [data-nav-account] > span:first-child { transform-origin: right center; animation-name: nav-tl-name; }
928
+ /* The name in row 1 (Studio.navbar_name_in_rows) rides the icon column's
929
+ slide and scales by the avatar's ratio from its right edge, so it
930
+ keeps its gap to the avatar; the gear-to-name gap grows by what the
931
+ name gives up. */
932
+ .nav-shell[data-nav-timeline] [data-nav-name-trigger] { transform-origin: right center; animation-name: nav-tl-avatar; }
933
+ .nav-shell[data-nav-timeline] [data-nav-account] > span:first-child:not([aria-hidden]) { transform-origin: right center; animation-name: nav-tl-name; }
921
934
  .nav-shell[data-nav-timeline] [data-nav-tools] { animation-name: nav-tl-tools; }
922
935
  .nav-shell[data-nav-timeline] [data-nav-links] { animation-name: nav-tl-links; }
923
936
  }
@@ -50,6 +50,14 @@ module StudioNavbarHelper
50
50
  @studio_navbar_avatar_badge = Studio.navbar_avatar_badge_for(self)
51
51
  end
52
52
 
53
+ # Studio.navbar_name_in_rows, read once per render pass. The ivar lets one
54
+ # request set it without writing the process-wide setting (the e2e lab).
55
+ def studio_navbar_name_in_rows?
56
+ return @studio_navbar_name_in_rows if defined?(@studio_navbar_name_in_rows)
57
+
58
+ @studio_navbar_name_in_rows = Studio.navbar_name_in_rows
59
+ end
60
+
53
61
  # Studio.navbar_header_bars as partial specs, [] for none.
54
62
  def studio_navbar_header_bars
55
63
  @studio_navbar_header_bars ||= Studio.navbar_header_bars_for(self)
@@ -451,7 +451,9 @@ export class NavCollapse {
451
451
  var el = this.el;
452
452
  var brand = el.querySelector('.nav-logo-link');
453
453
  var avatar = el.querySelector('[data-nav-account] > [aria-hidden]');
454
- var name = el.querySelector('[data-nav-account] > span:first-child');
454
+ // The name's box, never the avatar's: with Studio.navbar_name_in_rows the
455
+ // name is in row 1 and the avatar is the account's first child.
456
+ var name = el.querySelector('[data-nav-account] > span:first-child:not([aria-hidden])');
455
457
  var read = function () {
456
458
  return {
457
459
  h: el.offsetHeight,
@@ -93,6 +93,27 @@
93
93
  <% server_level = current_user.respond_to?(:level) && current_user.level.present? ? current_user.level : nil %>
94
94
  <% wallet_connected = current_user.respond_to?(:truncated_solana) && current_user.try(:solana_connected?) %>
95
95
  <% nav_rows = div2_content.present? || server_level || wallet_connected ? 2 : 1 %>
96
+ <%# The account's kind (a host sidebar, the link sidebar, a link or plain
97
+ text) is read here, before the rows: Studio.navbar_name_in_rows draws
98
+ the name as its own control at the end of row 1, and that control must
99
+ open what the avatar opens. Div 3's comments below say what each kind is. %>
100
+ <% account_sidebar = respond_to?(:studio_navbar_account_sidebar) ? studio_navbar_account_sidebar : nil %>
101
+ <% account_opens_sidebar = !account_sidebar && respond_to?(:studio_account_opens_sidebar?) && studio_account_opens_sidebar? %>
102
+ <%# Studio.navbar_phone_name: the name shows below md too, capped, and
103
+ fades at its edge when it overflows (engine.css, THE PHONE NAME). %>
104
+ <% phone_name = Studio.navbar_phone_name %>
105
+ <% name_in_rows = respond_to?(:studio_navbar_name_in_rows?) ? studio_navbar_name_in_rows? : Studio.navbar_name_in_rows %>
106
+ <% nav_user_name = Studio.navbar_user_name_for(current_user, self) %>
107
+ <%# The name: its box (screen-reader text below md, or the phone name's
108
+ capped box) around the truncating data-nav-name. Built here as a
109
+ string, not a capture, so the default render keeps its bytes. %>
110
+ <% name_hover = account_sidebar || account_opens_sidebar || studio_nav_destination %>
111
+ <% name_html = safe_join([
112
+ (phone_name ? '<span class="min-w-0 max-w-[4.5rem] min-[400px]:max-w-[6rem] md:max-w-40" data-nav-name-phone>' : '<span class="sr-only md:not-sr-only md:max-w-40">').html_safe,
113
+ %(<span class="block truncate text-right text-heading font-semibold text-base leading-none transition#{" group-hover:text-primary" if name_hover}" data-nav-name>).html_safe,
114
+ nav_user_name,
115
+ "</span></span>".html_safe
116
+ ]) %>
96
117
  <div class="flex-1 min-w-0 flex flex-col justify-center" data-nav-tools data-nav-rows="<%= nav_rows %>">
97
118
  <%# Div 1 (Bigger): balance + icons. %>
98
119
  <div class="flex items-center gap-2 leading-none" :style="'padding: 0 6px;' + ($store.devMode ? 'background: cornflowerblue;' : '')">
@@ -114,6 +135,32 @@
114
135
  <%= render "components/admin_dropdown", display_class: "hidden md:block" unless respond_to?(:studio_sidebar_replaces_admin_menu?) && studio_sidebar_replaces_admin_menu? %>
115
136
  <% end %>
116
137
  </div>
138
+ <% if name_in_rows %>
139
+ <%# Studio.navbar_name_in_rows: the name ends row 1, over the row-2
140
+ slot, as its own control (data-nav-name-trigger) of the avatar's
141
+ kind. It is the one that gives way: it may shrink and truncate,
142
+ the icons may not. %>
143
+ <%# Below md, unless Studio.navbar_phone_name, the name is not shown,
144
+ so its control is not rendered there at all (hidden, never a
145
+ 0x0 tab stop announcing the name a second time): the avatar,
146
+ labelled with the name, is the account below md. %>
147
+ <% name_classes = "group min-w-0 flex-shrink rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40#{" hidden md:block" unless phone_name}" %>
148
+ <% if account_sidebar %>
149
+ <% name_js = studio_navbar_account_sidebar_expressions(account_sidebar) %>
150
+ <%= tag.button type: "button", class: "#{name_classes} cursor-pointer",
151
+ data: { nav_name_trigger: "", profile_image_toggle: "" },
152
+ aria: { controls: account_sidebar[:controls], haspopup: "dialog" },
153
+ ":aria-expanded": name_js[:expanded], "@click.stop": name_js[:toggle] do %><%= name_html %><% end %>
154
+ <% elsif account_opens_sidebar %>
155
+ <button type="button" class="<%= name_classes %> cursor-pointer" data-nav-name-trigger data-profile-image-toggle
156
+ aria-controls="studio-link-sidebar studio-link-sidebar-mobile" aria-haspopup="dialog"
157
+ :aria-expanded="$store.sidebars && $store.sidebars.linkTreeOpen ? 'true' : 'false'"><%= name_html %></button>
158
+ <% elsif studio_nav_destination %>
159
+ <%= link_to name_html, studio_nav_destination, class: name_classes, "data-nav-name-trigger": "" %>
160
+ <% else %>
161
+ <span class="<%= name_classes %>" data-nav-name-trigger><%= name_html %></span>
162
+ <% end %>
163
+ <% end %>
117
164
  </div>
118
165
  <% if div2_content.present? %>
119
166
  <%# Div 2 replaced by the consumer's slot (div2_slot or legacy div2_html). %>
@@ -237,14 +284,12 @@
237
284
  this one; the guarded @click is the path when nothing claims it. Its
238
285
  controls may not name the engine's own link sidebar ids, refused at
239
286
  assignment (lib/studio/navbar_account_sidebar.rb). %>
240
- <% account_sidebar = respond_to?(:studio_navbar_account_sidebar) ? studio_navbar_account_sidebar : nil %>
241
- <% account_opens_sidebar = !account_sidebar && respond_to?(:studio_account_opens_sidebar?) && studio_account_opens_sidebar? %>
242
287
  <% account_classes = "group flex items-center gap-2 flex-shrink-0" %>
243
- <%# Studio.navbar_phone_name: the name shows below md too, capped, and
244
- fades at its edge when it overflows (engine.css, THE PHONE NAME). %>
245
- <% phone_name = Studio.navbar_phone_name %>
288
+ <%# With Studio.navbar_name_in_rows the name is in row 1, so the avatar
289
+ stands alone here and the control takes the name as its label. %>
290
+ <% account_label = name_in_rows ? nav_user_name : nil %>
246
291
  <% account_inner = capture do %>
247
- <% if phone_name %><span class="min-w-0 max-w-[4.5rem] min-[400px]:max-w-[6rem] md:max-w-40" data-nav-name-phone><% else %><span class="sr-only md:not-sr-only md:max-w-40"><% end %><span class="block truncate text-right text-heading font-semibold text-base leading-none transition<%= " group-hover:text-primary" if account_sidebar || account_opens_sidebar || studio_nav_destination %>" data-nav-name><%= Studio.navbar_user_name_for(current_user, self) %></span></span>
292
+ <% unless name_in_rows %><%= name_html %><% end %>
248
293
  <span class="flex-shrink-0 flex items-center" aria-hidden="true">
249
294
  <%= render "components/avatar", user: current_user, size: "nav" %>
250
295
  </span>
@@ -253,16 +298,16 @@
253
298
  <% account_js = studio_navbar_account_sidebar_expressions(account_sidebar) %>
254
299
  <%= tag.button type: "button", class: "#{account_classes} cursor-pointer rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
255
300
  data: { nav_account: "", profile_image_toggle: "" },
256
- aria: { controls: account_sidebar[:controls], haspopup: "dialog" },
301
+ aria: { controls: account_sidebar[:controls], haspopup: "dialog", label: account_label },
257
302
  ":aria-expanded": account_js[:expanded], "@click.stop": account_js[:toggle],
258
303
  "x-bind:style": "$store.devMode && 'background: lightgreen'" do %><%= account_inner %><% end %>
259
304
  <% elsif account_opens_sidebar %>
260
- <button type="button" class="<%= account_classes %> cursor-pointer rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40" data-nav-account data-profile-image-toggle
305
+ <button type="button" class="<%= account_classes %> cursor-pointer rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40" data-nav-account data-profile-image-toggle<%= %( aria-label="#{h(account_label)}").html_safe if account_label %>
261
306
  aria-controls="studio-link-sidebar studio-link-sidebar-mobile" aria-haspopup="dialog"
262
307
  :aria-expanded="$store.sidebars && $store.sidebars.linkTreeOpen ? 'true' : 'false'"
263
308
  x-bind:style="$store.devMode && 'background: lightgreen'"><%= account_inner %></button>
264
309
  <% elsif studio_nav_destination %>
265
- <%= link_to account_inner, studio_nav_destination, class: account_classes, "data-nav-account": "", "x-bind:style": "$store.devMode && 'background: lightgreen'" %>
310
+ <%= link_to account_inner, studio_nav_destination, class: account_classes, "data-nav-account": "", "aria-label": account_label, "x-bind:style": "$store.devMode && 'background: lightgreen'" %>
266
311
  <% else %>
267
312
  <span class="<%= account_classes %>" data-nav-account x-bind:style="$store.devMode && 'background: lightgreen'"><%= account_inner %></span>
268
313
  <% end %>
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.116.1"
2
+ VERSION = "0.116.2.rc1"
3
3
  end
data/lib/studio.rb CHANGED
@@ -407,6 +407,23 @@ module Studio
407
407
  @@navbar_phone_name = navbar_boolean!(:navbar_phone_name, value)
408
408
  end
409
409
 
410
+ # ---- Navbar name in rows ----
411
+ # true moves the signed-in name out of the account control and into the user
412
+ # nav's rows: it ends row 1 ([data-nav-tools], after the icons), so it stacks
413
+ # directly over the row-2 slot (a level or seeds bar), and the avatar stands
414
+ # alone in [data-nav-account]. The name stays a control of its own that opens
415
+ # what the avatar opens, and the account group (engine.css, THE ACCOUNT
416
+ # GROUP) lights the three together from either. Turf Monster's stacked name
417
+ # and level, as before it adopted the engine's user nav. false, the default,
418
+ # renders byte-identical to before.
419
+ #
420
+ # config.navbar_name_in_rows = true
421
+ mattr_reader :navbar_name_in_rows, default: false
422
+
423
+ def self.navbar_name_in_rows=(value)
424
+ @@navbar_name_in_rows = navbar_boolean!(:navbar_name_in_rows, value)
425
+ end
426
+
410
427
  # ---- Navbar brand ----
411
428
  # The navbar's logo image and title words, for an app whose brand is not the
412
429
  # "Navbar Logo" image plus app_name with its last word in text-primary: a
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.116.1
4
+ version: 0.116.2.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie