fuji_admin 1.0.0 → 1.2.0
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/README.md +23 -2
- data/app/assets/javascripts/fuji_admin/filters.js +19 -2
- data/app/assets/stylesheets/fuji_admin/pages/_login.scss +485 -42
- data/lib/fuji_admin/meta_tags.rb +43 -0
- data/lib/fuji_admin/version.rb +1 -1
- data/lib/fuji_admin.rb +5 -4
- metadata +2 -2
- data/lib/fuji_admin/active_admin_patch.rb +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 75684a6385b8b7b262e2be3df545d053634ae598bd0ad0b3f2a970fa16e2df7d
|
|
4
|
+
data.tar.gz: 27c25b625df9bacc85cbfe126f3459cb596b5bf3c632ab3209b2ef4f9cd9d2bf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db8499a2646ba57bcae37bdac0dbec443ed59ade1a75bd5effaffa4adbccb390b968ebd9f0d7b48f52394101d84eccc40ac41952b68d249152bafd18790eb87d
|
|
7
|
+
data.tar.gz: 63059e3daa84eb7beed8ee5c95de248f854bb3aaa7484c5c39fd0496eda1f9c7427bbfb416887be508bea3fc4bea6edd29fadf52f8bc4235b9530dfdc54fa3ff
|
data/README.md
CHANGED
|
@@ -4,6 +4,26 @@ A responsive, modern theme for [ActiveAdmin](https://github.com/activeadmin/acti
|
|
|
4
4
|
Clean card-based layout, slide-in filter drawer, float-label inputs,
|
|
5
5
|
row-action dropdowns, and a live palette switcher with 30 built-in palettes.
|
|
6
6
|
|
|
7
|
+
## Screenshots
|
|
8
|
+
|
|
9
|
+
| Index page (Editorial Navy) | Filter drawer |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
|  |  |
|
|
12
|
+
|
|
13
|
+
| New record form with float labels & date picker | Show page |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
|  |  |
|
|
16
|
+
|
|
17
|
+
| Live palette switcher | Login (editorial split) |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
|  |  |
|
|
20
|
+
|
|
21
|
+
### Mobile
|
|
22
|
+
|
|
23
|
+
| Index (hamburger nav, horizontal scroll) | Row-actions dropdown | Show page (flattened panel) | Login (stacked) |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
|  |  |  |  |
|
|
26
|
+
|
|
7
27
|
## Why fuji_admin
|
|
8
28
|
|
|
9
29
|
Fuji Admin drops onto an existing ActiveAdmin app without touching a single
|
|
@@ -48,14 +68,15 @@ the whole app.
|
|
|
48
68
|
## Requirements
|
|
49
69
|
|
|
50
70
|
- Ruby `>= 3.1.0`
|
|
51
|
-
- ActiveAdmin `>= 3.0`, `< 4.0`
|
|
71
|
+
- ActiveAdmin `>= 3.0`, `< 4.0` (AA 4 ships its own Tailwind-based theme
|
|
72
|
+
with a different asset pipeline — fuji_admin doesn't apply there yet)
|
|
52
73
|
|
|
53
74
|
## Install in an existing ActiveAdmin project
|
|
54
75
|
|
|
55
76
|
1. Add to your `Gemfile`:
|
|
56
77
|
|
|
57
78
|
```ruby
|
|
58
|
-
gem "fuji_admin", "~> 0
|
|
79
|
+
gem "fuji_admin", "~> 1.0"
|
|
59
80
|
```
|
|
60
81
|
|
|
61
82
|
```bash
|
|
@@ -83,12 +83,27 @@
|
|
|
83
83
|
else openDrawer();
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Track where the mousedown happened so we can distinguish a real
|
|
87
|
+
// outside-click from a text selection whose drag ends on the backdrop.
|
|
88
|
+
// Without this, selecting text inside an input and releasing the mouse
|
|
89
|
+
// over the greyed area fires a click on the backdrop and closes the
|
|
90
|
+
// drawer mid-interaction.
|
|
91
|
+
var mouseDownTarget = null;
|
|
92
|
+
|
|
93
|
+
function onDocumentMouseDown(e) {
|
|
94
|
+
mouseDownTarget = e.target;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isInside(container, el) {
|
|
98
|
+
return container && el && container.contains(el);
|
|
99
|
+
}
|
|
100
|
+
|
|
86
101
|
function onDocumentClick(e) {
|
|
87
102
|
if (!document.body.classList.contains(BODY_OPEN_CLASS)) return;
|
|
88
103
|
var sb = sidebar();
|
|
89
|
-
if (sb
|
|
104
|
+
if (isInside(sb, e.target) || isInside(sb, mouseDownTarget)) return;
|
|
90
105
|
var t = toggleBtn();
|
|
91
|
-
if (t
|
|
106
|
+
if (isInside(t, e.target) || isInside(t, mouseDownTarget)) return;
|
|
92
107
|
closeDrawer();
|
|
93
108
|
}
|
|
94
109
|
|
|
@@ -266,6 +281,8 @@
|
|
|
266
281
|
closeX.addEventListener("click", closeDrawer);
|
|
267
282
|
}
|
|
268
283
|
|
|
284
|
+
document.removeEventListener("mousedown", onDocumentMouseDown);
|
|
285
|
+
document.addEventListener("mousedown", onDocumentMouseDown);
|
|
269
286
|
document.removeEventListener("click", onDocumentClick);
|
|
270
287
|
document.addEventListener("click", onDocumentClick);
|
|
271
288
|
document.removeEventListener("keydown", onKey);
|
|
@@ -1,28 +1,85 @@
|
|
|
1
|
-
// Logged-out / login page.
|
|
1
|
+
// Logged-out / login page — editorial split layout.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Left: full-bleed colour field painted with `--fuji-primary` and the site
|
|
4
|
+
// title set as a Saira Black masthead. Right: a calm form column using
|
|
5
|
+
// hairline-underline inputs and a single primary-colour CTA. The whole page
|
|
6
|
+
// re-tints when the palette switcher fires, because every chrome element
|
|
7
|
+
// reads from the same palette CSS variables as the rest of the theme.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// Devise emits the form into `#login` with formtastic markup; we don't
|
|
10
|
+
// override the template, so host apps that ship their own
|
|
11
|
+
// `views/active_admin/devise/sessions/new.html.erb` keep winning. To
|
|
12
|
+
// override the visuals here, declare your own rules after `@import
|
|
13
|
+
// "fuji_admin/base"` in `active_admin.scss` — every selector below is
|
|
14
|
+
// scoped to `body.logged_out` and uses normal specificity.
|
|
12
15
|
|
|
13
16
|
body.logged_out {
|
|
14
|
-
min-height: 100vh;
|
|
15
17
|
margin: 0;
|
|
18
|
+
min-height: 100vh;
|
|
19
|
+
background: color-mix(in srgb, var(--fuji-surface, #f5f3ee) 92%, white);
|
|
20
|
+
color: var(--fuji-text, #1a1a1a);
|
|
21
|
+
font-family: $font-family-body;
|
|
22
|
+
position: relative;
|
|
23
|
+
overflow-x: hidden;
|
|
24
|
+
|
|
25
|
+
// ─── Left colour field (decorative, full-bleed) ──────────────────────────
|
|
26
|
+
&::before {
|
|
27
|
+
content: "";
|
|
28
|
+
position: fixed;
|
|
29
|
+
inset: 0 auto 0 0;
|
|
30
|
+
width: 44vw;
|
|
31
|
+
background:
|
|
32
|
+
radial-gradient(120% 80% at 80% 20%,
|
|
33
|
+
color-mix(in srgb, var(--fuji-primary, #41549b) 70%, white) 0%,
|
|
34
|
+
var(--fuji-primary, #41549b) 45%,
|
|
35
|
+
color-mix(in srgb, var(--fuji-primary, #41549b) 78%, black) 100%);
|
|
36
|
+
z-index: 0;
|
|
37
|
+
pointer-events: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Subtle film-grain overlay on the colour field.
|
|
41
|
+
&::after {
|
|
42
|
+
content: "";
|
|
43
|
+
position: fixed;
|
|
44
|
+
inset: 0 auto 0 0;
|
|
45
|
+
width: 44vw;
|
|
46
|
+
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.55 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/></svg>");
|
|
47
|
+
background-size: 220px 220px;
|
|
48
|
+
mix-blend-mode: overlay;
|
|
49
|
+
opacity: 0.18;
|
|
50
|
+
z-index: 0;
|
|
51
|
+
pointer-events: none;
|
|
52
|
+
}
|
|
16
53
|
|
|
54
|
+
// ─── Layout shell (override default flex from earlier baseline) ──────────
|
|
17
55
|
#wrapper,
|
|
18
56
|
#content_wrapper {
|
|
57
|
+
all: unset;
|
|
58
|
+
display: block;
|
|
59
|
+
min-height: 100vh;
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#wrapper {
|
|
64
|
+
display: grid;
|
|
65
|
+
grid-template-columns: 44vw 1fr;
|
|
66
|
+
align-items: stretch;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Hairline magazine-style "column rule" between panels.
|
|
70
|
+
#content_wrapper {
|
|
71
|
+
grid-column: 2;
|
|
19
72
|
display: flex;
|
|
20
73
|
flex-direction: column;
|
|
21
|
-
|
|
74
|
+
justify-content: center;
|
|
75
|
+
padding: clamp(2rem, 5vw, 5rem) clamp(1.5rem, 6vw, 6rem);
|
|
76
|
+
position: relative;
|
|
77
|
+
z-index: 1;
|
|
78
|
+
border-left: 1px solid color-mix(in srgb, var(--fuji-primary, #41549b) 25%, transparent);
|
|
22
79
|
}
|
|
23
80
|
|
|
24
81
|
#active_admin_content {
|
|
25
|
-
flex:
|
|
82
|
+
flex: none;
|
|
26
83
|
padding: 0;
|
|
27
84
|
margin: 0;
|
|
28
85
|
background: transparent;
|
|
@@ -32,46 +89,432 @@ body.logged_out {
|
|
|
32
89
|
max-width: none;
|
|
33
90
|
}
|
|
34
91
|
|
|
92
|
+
// Hide footer on logged-out pages — adds noise to the editorial layout.
|
|
93
|
+
#footer { display: none; }
|
|
94
|
+
|
|
95
|
+
// ─── Flash banner — editorial notice that spans the form column ──────────
|
|
35
96
|
.flashes {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
text-align:
|
|
97
|
+
margin: 0 0 2.25rem;
|
|
98
|
+
padding: 0;
|
|
99
|
+
text-align: left;
|
|
100
|
+
width: 100%;
|
|
101
|
+
max-width: none;
|
|
102
|
+
align-self: stretch;
|
|
103
|
+
|
|
104
|
+
.flash {
|
|
105
|
+
display: block;
|
|
106
|
+
width: 100%;
|
|
107
|
+
padding: 1rem 1.25rem;
|
|
108
|
+
margin: 0 0 0.6rem;
|
|
109
|
+
border: 0;
|
|
110
|
+
border-radius: 0;
|
|
111
|
+
background: color-mix(in srgb, var(--fuji-primary, #41549b) 7%, transparent);
|
|
112
|
+
border-left: 3px solid var(--fuji-primary, #41549b);
|
|
113
|
+
border-bottom: 1px solid color-mix(in srgb, var(--fuji-primary, #41549b) 18%, transparent);
|
|
114
|
+
color: var(--fuji-text, #1a1a1a);
|
|
115
|
+
font-size: 0.85rem;
|
|
116
|
+
letter-spacing: 0.01em;
|
|
117
|
+
line-height: 1.5;
|
|
118
|
+
text-align: left;
|
|
119
|
+
animation: fuji-login-fade 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) both;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.flash_alert,
|
|
123
|
+
.flash_error {
|
|
124
|
+
background: rgba(185, 67, 67, 0.08);
|
|
125
|
+
border-left-color: #b94343;
|
|
126
|
+
border-bottom-color: rgba(185, 67, 67, 0.22);
|
|
127
|
+
color: #6e2727;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.flash_notice {
|
|
131
|
+
border-left-color: var(--fuji-primary, #41549b);
|
|
132
|
+
}
|
|
39
133
|
}
|
|
40
134
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
135
|
+
// ─── #login — the form column ────────────────────────────────────────────
|
|
136
|
+
#login {
|
|
137
|
+
align-self: center;
|
|
138
|
+
width: 100%;
|
|
139
|
+
max-width: 26rem;
|
|
140
|
+
// Intentionally NOT animated — any non-`none` transform on this
|
|
141
|
+
// element would make it the containing block for the fixed-positioned
|
|
142
|
+
// h2 and ::before, breaking their viewport anchoring. Children
|
|
143
|
+
// animate themselves below.
|
|
144
|
+
|
|
145
|
+
// The site title <h2> is hoisted onto the colour field.
|
|
146
|
+
// Fixed-positioned so it shares the coordinate space of the fixed
|
|
147
|
+
// colour band on body — works the same on desktop and mobile.
|
|
148
|
+
h2 {
|
|
149
|
+
position: fixed;
|
|
150
|
+
left: clamp(1.5rem, 3.5vw, 4rem);
|
|
151
|
+
bottom: clamp(2.5rem, 6vh, 5rem);
|
|
152
|
+
max-width: calc(44vw - clamp(2rem, 5vw, 6rem));
|
|
153
|
+
margin: 0;
|
|
154
|
+
padding: 0;
|
|
155
|
+
color: #fff;
|
|
156
|
+
font-family: $font-family-body;
|
|
157
|
+
font-weight: 900;
|
|
158
|
+
font-size: clamp(2.6rem, 5.4vw, 5.25rem);
|
|
159
|
+
line-height: 0.92;
|
|
160
|
+
letter-spacing: -0.035em;
|
|
161
|
+
text-wrap: balance;
|
|
162
|
+
opacity: 0.97;
|
|
163
|
+
z-index: 2;
|
|
164
|
+
pointer-events: none;
|
|
165
|
+
animation: fuji-login-masthead 0.9s cubic-bezier(0.2, 0.7, 0.3, 1) 0.15s both;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Colour-field eyebrow rendered via ::before on #login (decorative only).
|
|
169
|
+
&::before {
|
|
170
|
+
content: "ADMIN";
|
|
171
|
+
position: fixed;
|
|
172
|
+
left: clamp(1.5rem, 3.5vw, 4rem);
|
|
173
|
+
top: clamp(2rem, 5vh, 4rem);
|
|
174
|
+
color: rgba(255, 255, 255, 0.78);
|
|
175
|
+
font-size: 0.72rem;
|
|
176
|
+
font-weight: 600;
|
|
177
|
+
letter-spacing: 0.32em;
|
|
178
|
+
text-transform: uppercase;
|
|
179
|
+
z-index: 2;
|
|
180
|
+
animation: fuji-login-fade 0.9s cubic-bezier(0.2, 0.7, 0.3, 1) 0.05s both;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Eyebrow above the form on the right column.
|
|
184
|
+
form::before {
|
|
185
|
+
content: "Authentication";
|
|
186
|
+
display: block;
|
|
187
|
+
margin: 0 0 1.75rem;
|
|
188
|
+
font-size: 0.72rem;
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
letter-spacing: 0.28em;
|
|
191
|
+
text-transform: uppercase;
|
|
192
|
+
color: var(--fuji-text-muted, #8a8a8a);
|
|
193
|
+
animation: fuji-login-rise 0.7s cubic-bezier(0.2, 0.7, 0.3, 1) 0.1s both;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Forgot password link sits at the bottom of the column.
|
|
197
|
+
> a {
|
|
198
|
+
display: inline-block;
|
|
199
|
+
margin-top: 1.25rem;
|
|
200
|
+
font-size: 0.74rem;
|
|
201
|
+
font-weight: 600;
|
|
202
|
+
letter-spacing: 0.18em;
|
|
203
|
+
text-transform: uppercase;
|
|
204
|
+
color: var(--fuji-text-muted, #8a8a8a);
|
|
205
|
+
text-decoration: none;
|
|
206
|
+
border-bottom: 1px dotted color-mix(in srgb, var(--fuji-text-muted, #8a8a8a) 60%, transparent);
|
|
207
|
+
padding-bottom: 2px;
|
|
208
|
+
transition: color 0.2s ease, border-color 0.2s ease;
|
|
209
|
+
|
|
210
|
+
&:hover {
|
|
211
|
+
color: var(--fuji-primary, #41549b);
|
|
212
|
+
border-bottom-color: var(--fuji-primary, #41549b);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
> br { display: none; }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── Form fields — hairline underline aesthetic ──────────────────────────
|
|
220
|
+
#login form {
|
|
46
221
|
margin: 0;
|
|
47
|
-
background: transparent;
|
|
48
|
-
border: 0;
|
|
49
|
-
border-radius: 0;
|
|
50
|
-
overflow: visible;
|
|
51
222
|
|
|
52
|
-
|
|
53
|
-
|
|
223
|
+
fieldset.inputs,
|
|
224
|
+
fieldset.actions {
|
|
225
|
+
all: unset;
|
|
226
|
+
display: block;
|
|
227
|
+
margin: 0;
|
|
228
|
+
padding: 0;
|
|
229
|
+
background: transparent;
|
|
230
|
+
border: 0;
|
|
231
|
+
|
|
232
|
+
legend { display: none; }
|
|
54
233
|
}
|
|
55
234
|
|
|
56
|
-
ol {
|
|
235
|
+
fieldset.inputs > ol {
|
|
236
|
+
list-style: none;
|
|
57
237
|
padding: 0;
|
|
238
|
+
margin: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
li.input {
|
|
242
|
+
display: block;
|
|
243
|
+
position: relative;
|
|
244
|
+
margin: 0 0 1.6rem;
|
|
245
|
+
padding: 0;
|
|
246
|
+
background: transparent;
|
|
247
|
+
animation: fuji-login-rise 0.6s cubic-bezier(0.2, 0.7, 0.3, 1) both;
|
|
248
|
+
|
|
249
|
+
&.email { animation-delay: 0.18s; }
|
|
250
|
+
&.password { animation-delay: 0.26s; }
|
|
251
|
+
&.boolean { animation-delay: 0.34s; }
|
|
252
|
+
|
|
253
|
+
// Label sits above the input as a small-caps eyebrow.
|
|
254
|
+
> label.label {
|
|
255
|
+
display: block;
|
|
256
|
+
margin: 0 0 0.35rem;
|
|
257
|
+
padding: 0;
|
|
258
|
+
font-size: 0.7rem;
|
|
259
|
+
font-weight: 600;
|
|
260
|
+
letter-spacing: 0.22em;
|
|
261
|
+
text-transform: uppercase;
|
|
262
|
+
color: var(--fuji-text-muted, #8a8a8a);
|
|
263
|
+
background: transparent;
|
|
264
|
+
position: static;
|
|
265
|
+
transition: color 0.2s ease;
|
|
266
|
+
|
|
267
|
+
abbr {
|
|
268
|
+
display: none; // suppress the red asterisk; required is implicit
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Hairline-underline text input.
|
|
273
|
+
> input[type="email"],
|
|
274
|
+
> input[type="text"],
|
|
275
|
+
> input[type="password"] {
|
|
276
|
+
all: unset;
|
|
277
|
+
box-sizing: border-box;
|
|
278
|
+
display: block;
|
|
279
|
+
width: 100%;
|
|
280
|
+
padding: 0.55rem 0 0.6rem;
|
|
281
|
+
border: 0;
|
|
282
|
+
border-bottom: 1px solid color-mix(in srgb, var(--fuji-text, #1a1a1a) 22%, transparent);
|
|
283
|
+
border-radius: 0;
|
|
284
|
+
background: transparent;
|
|
285
|
+
color: var(--fuji-text, #1a1a1a);
|
|
286
|
+
font-family: $font-family-body;
|
|
287
|
+
font-size: 1.02rem;
|
|
288
|
+
font-weight: 400;
|
|
289
|
+
letter-spacing: 0.005em;
|
|
290
|
+
transition: border-color 0.25s ease;
|
|
291
|
+
|
|
292
|
+
&::placeholder { color: transparent; }
|
|
293
|
+
|
|
294
|
+
&:hover { border-bottom-color: color-mix(in srgb, var(--fuji-text, #1a1a1a) 45%, transparent); }
|
|
295
|
+
|
|
296
|
+
&:focus {
|
|
297
|
+
outline: none;
|
|
298
|
+
border-bottom-color: var(--fuji-primary, #41549b);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Animated underline thickener — bar that grows from centre on focus.
|
|
303
|
+
&.email::after,
|
|
304
|
+
&.password::after {
|
|
305
|
+
content: "";
|
|
306
|
+
position: absolute;
|
|
307
|
+
left: 50%;
|
|
308
|
+
bottom: 0;
|
|
309
|
+
width: 0;
|
|
310
|
+
height: 2px;
|
|
311
|
+
background: var(--fuji-primary, #41549b);
|
|
312
|
+
transition: width 0.35s cubic-bezier(0.2, 0.7, 0.3, 1),
|
|
313
|
+
left 0.35s cubic-bezier(0.2, 0.7, 0.3, 1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
&.email:focus-within::after,
|
|
317
|
+
&.password:focus-within::after {
|
|
318
|
+
width: 100%;
|
|
319
|
+
left: 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Raise the label colour when the field is focused.
|
|
323
|
+
&:focus-within > label.label {
|
|
324
|
+
color: var(--fuji-primary, #41549b);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ── Remember me — custom-painted square checkbox ────────────────────
|
|
329
|
+
li.boolean {
|
|
330
|
+
display: flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
margin-top: 1.8rem;
|
|
333
|
+
|
|
334
|
+
> label {
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
gap: 0.7rem;
|
|
338
|
+
font-size: 0.78rem;
|
|
339
|
+
font-weight: 500;
|
|
340
|
+
letter-spacing: 0.16em;
|
|
341
|
+
text-transform: uppercase;
|
|
342
|
+
color: var(--fuji-text-muted, #8a8a8a);
|
|
343
|
+
cursor: pointer;
|
|
344
|
+
user-select: none;
|
|
345
|
+
|
|
346
|
+
> input[type="checkbox"] {
|
|
347
|
+
appearance: none;
|
|
348
|
+
-webkit-appearance: none;
|
|
349
|
+
width: 1rem;
|
|
350
|
+
height: 1rem;
|
|
351
|
+
margin: 0;
|
|
352
|
+
border: 1px solid color-mix(in srgb, var(--fuji-text, #1a1a1a) 35%, transparent);
|
|
353
|
+
background: transparent;
|
|
354
|
+
border-radius: 2px;
|
|
355
|
+
position: relative;
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
transition: border-color 0.18s ease, background 0.18s ease;
|
|
358
|
+
|
|
359
|
+
&:hover { border-color: var(--fuji-primary, #41549b); }
|
|
360
|
+
|
|
361
|
+
&:checked {
|
|
362
|
+
background: var(--fuji-primary, #41549b);
|
|
363
|
+
border-color: var(--fuji-primary, #41549b);
|
|
364
|
+
|
|
365
|
+
&::after {
|
|
366
|
+
content: "";
|
|
367
|
+
position: absolute;
|
|
368
|
+
left: 3px;
|
|
369
|
+
top: -1px;
|
|
370
|
+
width: 5px;
|
|
371
|
+
height: 9px;
|
|
372
|
+
border: solid #fff;
|
|
373
|
+
border-width: 0 2px 2px 0;
|
|
374
|
+
transform: rotate(45deg);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
&:focus-visible {
|
|
379
|
+
outline: 2px solid color-mix(in srgb, var(--fuji-primary, #41549b) 50%, transparent);
|
|
380
|
+
outline-offset: 2px;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ── Submit button — full-width caps wordmark CTA ────────────────────
|
|
387
|
+
fieldset.actions {
|
|
388
|
+
margin-top: 2.25rem;
|
|
389
|
+
animation: fuji-login-rise 0.6s cubic-bezier(0.2, 0.7, 0.3, 1) 0.42s both;
|
|
390
|
+
|
|
391
|
+
// Override _inputs.scss which makes this ol/li flex (button-row layout
|
|
392
|
+
// for normal CRUD pages). The login button is single and full-width.
|
|
393
|
+
ol {
|
|
394
|
+
display: block;
|
|
395
|
+
list-style: none;
|
|
396
|
+
padding: 0;
|
|
397
|
+
margin: 0;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
li.action {
|
|
401
|
+
display: block;
|
|
402
|
+
margin: 0;
|
|
403
|
+
padding: 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
input[type="submit"] {
|
|
407
|
+
all: unset;
|
|
408
|
+
box-sizing: border-box;
|
|
409
|
+
display: block;
|
|
410
|
+
width: 100%;
|
|
411
|
+
padding: 1.05rem 1.5rem;
|
|
412
|
+
background: var(--fuji-primary, #41549b);
|
|
413
|
+
color: #fff;
|
|
414
|
+
font-family: $font-family-body;
|
|
415
|
+
font-size: 0.82rem;
|
|
416
|
+
font-weight: 700;
|
|
417
|
+
letter-spacing: 0.24em;
|
|
418
|
+
text-transform: uppercase;
|
|
419
|
+
text-align: center;
|
|
420
|
+
cursor: pointer;
|
|
421
|
+
border-radius: 2px;
|
|
422
|
+
box-shadow: 0 1px 0 color-mix(in srgb, var(--fuji-primary, #41549b) 60%, black);
|
|
423
|
+
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
|
|
424
|
+
|
|
425
|
+
&:hover {
|
|
426
|
+
transform: translateY(-1px);
|
|
427
|
+
background: color-mix(in srgb, var(--fuji-primary, #41549b) 88%, black);
|
|
428
|
+
box-shadow: 0 6px 18px -8px color-mix(in srgb, var(--fuji-primary, #41549b) 75%, transparent);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
&:active { transform: translateY(0); }
|
|
432
|
+
|
|
433
|
+
&:focus-visible {
|
|
434
|
+
outline: 2px solid color-mix(in srgb, var(--fuji-primary, #41549b) 60%, white);
|
|
435
|
+
outline-offset: 3px;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
58
438
|
}
|
|
59
439
|
}
|
|
60
440
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
441
|
+
// ─── Mobile — stack the colour field on top ──────────────────────────────
|
|
442
|
+
@media (max-width: 760px) {
|
|
443
|
+
overflow-x: hidden;
|
|
444
|
+
|
|
445
|
+
// Colour band shrinks to a top region (not full-bleed left).
|
|
446
|
+
$band-h: clamp(15rem, 32vh, 20rem);
|
|
447
|
+
|
|
448
|
+
&::before,
|
|
449
|
+
&::after {
|
|
450
|
+
position: fixed;
|
|
451
|
+
inset: 0 0 auto 0;
|
|
452
|
+
width: 100%;
|
|
453
|
+
height: $band-h;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#wrapper {
|
|
457
|
+
display: block;
|
|
458
|
+
min-height: 100vh;
|
|
459
|
+
padding-top: $band-h;
|
|
460
|
+
box-sizing: border-box;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
#content_wrapper {
|
|
464
|
+
grid-column: auto;
|
|
465
|
+
justify-content: flex-start;
|
|
466
|
+
padding: 2.5rem 1.5rem 3rem;
|
|
467
|
+
border-left: 0;
|
|
468
|
+
min-height: auto;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
#login {
|
|
472
|
+
max-width: 22rem;
|
|
473
|
+
margin: 0 auto;
|
|
474
|
+
|
|
475
|
+
h2 {
|
|
476
|
+
// Wordmark sits inside the colour band with generous breathing
|
|
477
|
+
// room between its baseline and the band's bottom edge.
|
|
478
|
+
left: 1.5rem;
|
|
479
|
+
bottom: auto;
|
|
480
|
+
top: calc(#{$band-h} - 4.5rem);
|
|
481
|
+
transform: translateY(-100%);
|
|
482
|
+
max-width: calc(100% - 3rem);
|
|
483
|
+
font-size: clamp(1.85rem, 8.5vw, 2.6rem);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
&::before {
|
|
487
|
+
left: 1.5rem;
|
|
488
|
+
top: 1.75rem;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.flashes { padding: 0 1.5rem; }
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ─── Reduced-motion ──────────────────────────────────────────────────────
|
|
496
|
+
@media (prefers-reduced-motion: reduce) {
|
|
497
|
+
*,
|
|
498
|
+
*::before,
|
|
499
|
+
*::after {
|
|
500
|
+
animation: none !important;
|
|
501
|
+
transition: none !important;
|
|
502
|
+
}
|
|
76
503
|
}
|
|
77
504
|
}
|
|
505
|
+
|
|
506
|
+
// ─── Keyframes ─────────────────────────────────────────────────────────────
|
|
507
|
+
@keyframes fuji-login-rise {
|
|
508
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
509
|
+
to { opacity: 1; transform: translateY(0); }
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
@keyframes fuji-login-fade {
|
|
513
|
+
from { opacity: 0; }
|
|
514
|
+
to { opacity: 1; }
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
@keyframes fuji-login-masthead {
|
|
518
|
+
from { opacity: 0; transform: translate(-12px, 50%); }
|
|
519
|
+
to { opacity: 0.97; transform: translate(0, 50%); }
|
|
520
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module FujiAdmin
|
|
2
|
+
# Surfaces FujiAdmin.config and the responsive viewport hint to the browser
|
|
3
|
+
# by writing into AA's head. We deliberately avoid `namespace.meta_tags`:
|
|
4
|
+
# AA renders that hash inside Arbre as `text_node(meta(...))`, and the inner
|
|
5
|
+
# `meta(...)` already auto-attaches to <head>, so each entry would render
|
|
6
|
+
# twice. Using `namespace.head` (a raw, html_safe string AA inserts once)
|
|
7
|
+
# sidesteps that.
|
|
8
|
+
VIEWPORT = "width=device-width, initial-scale=1".freeze
|
|
9
|
+
|
|
10
|
+
def self.install_meta_tags!
|
|
11
|
+
return unless defined?(::ActiveAdmin)
|
|
12
|
+
|
|
13
|
+
# Logged-out pages use a separate ERB layout that only renders
|
|
14
|
+
# `application.meta_tags_for_logged_out_pages` (and ignores
|
|
15
|
+
# `namespace.head`). We need viewport for mobile rendering and
|
|
16
|
+
# `fuji-default-palette` so the login screen paints in the configured
|
|
17
|
+
# brand colour. The picker UI itself is hidden for logged-out, so
|
|
18
|
+
# `fuji-palette-picker` isn't needed there.
|
|
19
|
+
ActiveAdmin.application.meta_tags_for_logged_out_pages =
|
|
20
|
+
ActiveAdmin.application.meta_tags_for_logged_out_pages.merge(
|
|
21
|
+
viewport: VIEWPORT,
|
|
22
|
+
"fuji-default-palette" => config.default_palette.to_s
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
ActiveSupport::Notifications.subscribe(ActiveAdmin::Namespace::RegisterEvent) do |_name, _start, _finish, _id, payload|
|
|
26
|
+
apply_to_namespace(payload[:active_admin_namespace])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
ActiveAdmin.application.namespaces.each { |ns| apply_to_namespace(ns) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.apply_to_namespace(namespace)
|
|
33
|
+
namespace.head = head_markup
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.head_markup
|
|
37
|
+
<<~HTML.html_safe
|
|
38
|
+
<meta name="viewport" content="#{VIEWPORT}">
|
|
39
|
+
<meta name="fuji-palette-picker" content="#{config.palette_picker}">
|
|
40
|
+
<meta name="fuji-default-palette" content="#{config.default_palette}">
|
|
41
|
+
HTML
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/fuji_admin/version.rb
CHANGED
data/lib/fuji_admin.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "fuji_admin/version"
|
|
2
2
|
require "fuji_admin/configuration"
|
|
3
|
+
require "fuji_admin/meta_tags"
|
|
3
4
|
|
|
4
5
|
module FujiAdmin
|
|
5
6
|
class << self
|
|
@@ -16,11 +17,11 @@ module FujiAdmin
|
|
|
16
17
|
|
|
17
18
|
module Rails
|
|
18
19
|
class Engine < ::Rails::Engine
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
20
|
+
# Subscribe to AA's namespace-creation notification so each namespace
|
|
21
|
+
# picks up our meta tags the moment it's born. Safe to register at
|
|
22
|
+
# `after_initialize` — the subscription fires later, when namespaces
|
|
23
|
+
# actually load (lazily, on first request).
|
|
22
24
|
config.after_initialize do
|
|
23
|
-
require "fuji_admin/active_admin_patch"
|
|
24
25
|
FujiAdmin.install_meta_tags!
|
|
25
26
|
end
|
|
26
27
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fuji_admin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- barbariccorgi
|
|
@@ -103,8 +103,8 @@ files:
|
|
|
103
103
|
- app/assets/stylesheets/fuji_admin/variables/_typography.scss
|
|
104
104
|
- app/assets/stylesheets/fuji_admin/variables/_variables.scss
|
|
105
105
|
- lib/fuji_admin.rb
|
|
106
|
-
- lib/fuji_admin/active_admin_patch.rb
|
|
107
106
|
- lib/fuji_admin/configuration.rb
|
|
107
|
+
- lib/fuji_admin/meta_tags.rb
|
|
108
108
|
- lib/fuji_admin/version.rb
|
|
109
109
|
homepage: https://github.com/BarbaricCorgi/fuji_admin
|
|
110
110
|
licenses:
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
module FujiAdmin
|
|
2
|
-
# Surfaces FujiAdmin.config to the browser by registering entries in each
|
|
3
|
-
# ActiveAdmin namespace's built-in `meta_tags` hash. AA renders those
|
|
4
|
-
# entries as <meta> tags inside <head> (lib/active_admin/views/pages/base.rb),
|
|
5
|
-
# so the palette JavaScript can read them on page load without any
|
|
6
|
-
# monkey-patching of arbre's view builders.
|
|
7
|
-
def self.install_meta_tags!
|
|
8
|
-
return unless defined?(::ActiveAdmin)
|
|
9
|
-
|
|
10
|
-
ActiveAdmin.application.namespaces.each do |namespace|
|
|
11
|
-
namespace.meta_tags["fuji-palette-picker"] = config.palette_picker.to_s
|
|
12
|
-
namespace.meta_tags["fuji-default-palette"] = config.default_palette.to_s
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|