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 +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +10 -0
- data/app/assets/tailwind/studio_engine/engine.css +23 -10
- data/app/helpers/studio_navbar_helper.rb +8 -0
- data/app/javascript/studio/nav_collapse.js +3 -1
- data/app/views/components/_user_nav.html.erb +54 -9
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +17 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8fa12ba607e8798bdcb58e5b2758cc0a2fc57f20f298bf42b30fe8ce550e3723
|
|
4
|
+
data.tar.gz: 7f6c1fd58ad8722230cd3cf6992b9b8e9fc59c340ad7466f58dc77d9399b070c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
244
|
-
|
|
245
|
-
<%
|
|
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
|
-
<%
|
|
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 %>
|
data/lib/studio/version.rb
CHANGED
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
|