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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 826553bf7f65cca6b8ae4a32de6da7fd74c698fad3d674f173fd26a4816c9475
4
- data.tar.gz: bfab5e1c447b458cebb6e6809c928e210542105ba58231de05b2d67cd65d0935
3
+ metadata.gz: 75684a6385b8b7b262e2be3df545d053634ae598bd0ad0b3f2a970fa16e2df7d
4
+ data.tar.gz: 27c25b625df9bacc85cbfe126f3459cb596b5bf3c632ab3209b2ef4f9cd9d2bf
5
5
  SHA512:
6
- metadata.gz: f80c521d6cae049d7e6f9c7d5e70026c6a641bb0b92d6be6f100c7791f40484f7a31ebd4169126ba09b21925f82b7ec052768ff4d0075aa5b439aceeca47a999
7
- data.tar.gz: b69ca7a0eb6353ff2a336518f01e191b0e6c824113d4fe29b5dab9c0d0f602f5ff6cde664b1f2d755a1f789ddbe36cf1d0fd988959430b8aab2ac55b0c314461
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
+ | ![Index page](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/screenshot-001.png) | ![Filter drawer](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/screenshot-002.png) |
12
+
13
+ | New record form with float labels & date picker | Show page |
14
+ | --- | --- |
15
+ | ![New record form](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/screenshot-003.png) | ![Show page](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/screenshot-004.png) |
16
+
17
+ | Live palette switcher | Login (editorial split) |
18
+ | --- | --- |
19
+ | ![Palette switcher](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/screenshot-005.png) | ![Login page](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/screenshot-006.png) |
20
+
21
+ ### Mobile
22
+
23
+ | Index (hamburger nav, horizontal scroll) | Row-actions dropdown | Show page (flattened panel) | Login (stacked) |
24
+ | --- | --- | --- | --- |
25
+ | ![Mobile index](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/mobile-001.png) | ![Mobile row actions](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/mobile-002.png) | ![Mobile show](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/mobile-003.png) | ![Mobile login](https://raw.githubusercontent.com/BarbaricCorgi/fuji_admin/main/docs/screenshots/mobile-004.png) |
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.1"
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 && sb.contains(e.target)) return;
104
+ if (isInside(sb, e.target) || isInside(sb, mouseDownTarget)) return;
90
105
  var t = toggleBtn();
91
- if (t && t.contains(e.target)) return;
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
- // Omnia (and other host apps) commonly override the devise session template
4
- // with a fully-branded login design (logos, backgrounds, colour palettes).
5
- // Rather than fight those overrides, we provide only a minimal safe baseline
6
- // here: center the content, kill default AA chrome, and let the host's own
7
- // styles define the visual card.
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
- // The key principle: **don't force a white card around #active_admin_content**
10
- // on the login page that card will always appear as an unwanted white
11
- // border around a branded dark login (as with Omnia's tiger theme).
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
- min-height: 100vh;
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: 1 1 auto;
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
- padding: $space-4;
37
- margin: 0;
38
- text-align: center;
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
- // Neutralise fieldset legend header bar inside the login form host
42
- // stylesheets (Omnia's tiger theme etc.) expect standard input layout.
43
- #login fieldset.inputs,
44
- #login_form fieldset.inputs {
45
- padding: 0;
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
- legend {
53
- display: none;
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
- // Neutralise float labels inside the login form the custom template
62
- // renders its own label treatment; overlaying our floating labels produces
63
- // duplicated / misaligned text.
64
- #login .fuji-float > label,
65
- #login_form .fuji-float > label {
66
- position: static;
67
- top: auto;
68
- left: auto;
69
- padding: 0;
70
- margin: 0 0 $space-2;
71
- background: transparent;
72
- pointer-events: auto;
73
- font-size: inherit;
74
- color: inherit;
75
- transition: none;
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
@@ -1,3 +1,3 @@
1
1
  module FujiAdmin
2
- VERSION = "1.0.0"
2
+ VERSION = "1.2.0"
3
3
  end
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
- # Register meta tags after all initializers have run (so both
20
- # FujiAdmin.configure and ActiveAdmin.setup have populated their state)
21
- # and AA's namespaces are available to iterate.
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.0.0
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