fuji_admin 0.1.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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +143 -0
  4. data/app/assets/javascripts/fuji_admin/base.js +6 -0
  5. data/app/assets/javascripts/fuji_admin/filters.js +282 -0
  6. data/app/assets/javascripts/fuji_admin/floats.js +73 -0
  7. data/app/assets/javascripts/fuji_admin/menu.js +112 -0
  8. data/app/assets/javascripts/fuji_admin/palettes.js +237 -0
  9. data/app/assets/javascripts/fuji_admin/row_actions.js +123 -0
  10. data/app/assets/stylesheets/fuji_admin/_base.scss +23 -0
  11. data/app/assets/stylesheets/fuji_admin/_base_typography.scss +56 -0
  12. data/app/assets/stylesheets/fuji_admin/_grid.scss +111 -0
  13. data/app/assets/stylesheets/fuji_admin/_reset.scss +48 -0
  14. data/app/assets/stylesheets/fuji_admin/components/_buttons.scss +106 -0
  15. data/app/assets/stylesheets/fuji_admin/components/_comments.scss +44 -0
  16. data/app/assets/stylesheets/fuji_admin/components/_components.scss +22 -0
  17. data/app/assets/stylesheets/fuji_admin/components/_date_picker.scss +147 -0
  18. data/app/assets/stylesheets/fuji_admin/components/_dropdown_menu.scss +76 -0
  19. data/app/assets/stylesheets/fuji_admin/components/_filter_chips.scss +71 -0
  20. data/app/assets/stylesheets/fuji_admin/components/_filter_drawer.scss +224 -0
  21. data/app/assets/stylesheets/fuji_admin/components/_filter_form.scss +85 -0
  22. data/app/assets/stylesheets/fuji_admin/components/_flash.scss +55 -0
  23. data/app/assets/stylesheets/fuji_admin/components/_float_labels.scss +77 -0
  24. data/app/assets/stylesheets/fuji_admin/components/_inputs.scss +237 -0
  25. data/app/assets/stylesheets/fuji_admin/components/_menu_toggle.scss +61 -0
  26. data/app/assets/stylesheets/fuji_admin/components/_pagination.scss +70 -0
  27. data/app/assets/stylesheets/fuji_admin/components/_palette_switcher.scss +600 -0
  28. data/app/assets/stylesheets/fuji_admin/components/_panel.scss +44 -0
  29. data/app/assets/stylesheets/fuji_admin/components/_row_actions.scss +110 -0
  30. data/app/assets/stylesheets/fuji_admin/components/_scopes.scss +58 -0
  31. data/app/assets/stylesheets/fuji_admin/components/_select2.scss +194 -0
  32. data/app/assets/stylesheets/fuji_admin/components/_status_tag.scss +59 -0
  33. data/app/assets/stylesheets/fuji_admin/components/_table_tools.scss +14 -0
  34. data/app/assets/stylesheets/fuji_admin/components/_tables.scss +262 -0
  35. data/app/assets/stylesheets/fuji_admin/components/_watchlist_bar.scss +119 -0
  36. data/app/assets/stylesheets/fuji_admin/layouts/_footer.scss +21 -0
  37. data/app/assets/stylesheets/fuji_admin/layouts/_header.scss +80 -0
  38. data/app/assets/stylesheets/fuji_admin/layouts/_layouts.scss +7 -0
  39. data/app/assets/stylesheets/fuji_admin/layouts/_main_content.scss +118 -0
  40. data/app/assets/stylesheets/fuji_admin/layouts/_sidebar.scss +124 -0
  41. data/app/assets/stylesheets/fuji_admin/layouts/_sizes.scss +12 -0
  42. data/app/assets/stylesheets/fuji_admin/layouts/_wrapper.scss +28 -0
  43. data/app/assets/stylesheets/fuji_admin/mixins/_media.scss +30 -0
  44. data/app/assets/stylesheets/fuji_admin/mixins/_mixins.scss +2 -0
  45. data/app/assets/stylesheets/fuji_admin/pages/_form.scss +61 -0
  46. data/app/assets/stylesheets/fuji_admin/pages/_index.scss +77 -0
  47. data/app/assets/stylesheets/fuji_admin/pages/_login.scss +77 -0
  48. data/app/assets/stylesheets/fuji_admin/pages/_pages.scss +5 -0
  49. data/app/assets/stylesheets/fuji_admin/pages/_show.scss +19 -0
  50. data/app/assets/stylesheets/fuji_admin/variables/_breakpoints.scss +25 -0
  51. data/app/assets/stylesheets/fuji_admin/variables/_colors.scss +51 -0
  52. data/app/assets/stylesheets/fuji_admin/variables/_radii.scss +13 -0
  53. data/app/assets/stylesheets/fuji_admin/variables/_shadows.scss +10 -0
  54. data/app/assets/stylesheets/fuji_admin/variables/_spacing.scss +20 -0
  55. data/app/assets/stylesheets/fuji_admin/variables/_typography.scss +25 -0
  56. data/app/assets/stylesheets/fuji_admin/variables/_variables.scss +9 -0
  57. data/lib/fuji_admin/active_admin_patch.rb +19 -0
  58. data/lib/fuji_admin/configuration.rb +29 -0
  59. data/lib/fuji_admin/version.rb +3 -0
  60. data/lib/fuji_admin.rb +24 -0
  61. metadata +124 -0
@@ -0,0 +1,237 @@
1
+ // Fuji Admin — live palette switcher.
2
+ //
3
+ // Injects a floating trigger (bottom-right) that opens a grid of 30 Coolors-
4
+ // trending palettes. Clicking one sets `--fuji-primary` on <html>, persists
5
+ // the choice in localStorage, and updates primary-coloured components via the
6
+ // overrides in components/_palette_switcher.scss.
7
+ //
8
+ // Hidden on `body.logged_out` (login / password-reset pages, which run
9
+ // host-supplied themed templates that shouldn't be repainted).
10
+
11
+ (function () {
12
+ "use strict";
13
+
14
+ var STORAGE_KEY = "fuji-palette-id";
15
+
16
+ // --- Palettes (curated from coolors.co/palettes/trending) -----------------
17
+ var PALETTES = [
18
+ { id: "fuji-default", name: "Fuji Default", primary: "#41549b", swatch: ["#f2f5fc","#b2bce0","#41549b","#2a3664","#1f2849"] },
19
+ { id: "navy-amber", name: "Navy & Amber", primary: "#219ebc", swatch: ["#8ecae6","#219ebc","#023047","#ffb703","#fb8500"] },
20
+ { id: "warm-sunset", name: "Warm Sunset", primary: "#e76f51", swatch: ["#264653","#2a9d8f","#e9c46a","#f4a261","#e76f51"] },
21
+ { id: "forest-meadow", name: "Forest Meadow", primary: "#4f772d", swatch: ["#132a13","#31572c","#4f772d","#90a955","#ecf39e"] },
22
+ { id: "dusty-rose", name: "Dusty Rose", primary: "#da627d", swatch: ["#f9dbbd","#ffa5ab","#da627d","#a53860","#450920"] },
23
+ { id: "ocean-blue", name: "Ocean Blue", primary: "#0077b6", swatch: ["#03045e","#023e8a","#0077b6","#00b4d8","#48cae4"] },
24
+ { id: "terracotta", name: "Terracotta", primary: "#e07a5f", swatch: ["#f4f1de","#e07a5f","#3d405b","#81b29a","#f2cc8f"] },
25
+ { id: "coastal-sage", name: "Coastal Sage", primary: "#588157", swatch: ["#dad7cd","#a3b18a","#588157","#3a5a40","#344e41"],
26
+ theme: { surface: "#dad7cd", surfaceAlt: "#c2beb1", text: "#344e41", textMuted: "#a3b18a", border: "#b1ac9d" } },
27
+ { id: "royal-purple", name: "Royal Purple", primary: "#7b2cbf", swatch: ["#3c096c","#5a189a","#7b2cbf","#9d4edd","#c77dff"] },
28
+ { id: "editorial-navy", name: "Editorial Navy", primary: "#415a77", swatch: ["#0d1b2a","#1b263b","#415a77","#778da9","#e0e1dd"],
29
+ theme: { surface: "#e0e1dd", surfaceAlt: "#d0d1cb", text: "#0d1b2a", textMuted: "#778da9", border: "#bcc0b9" } },
30
+ { id: "cheerful", name: "Cheerful", primary: "#118ab2", swatch: ["#ef476f","#ffd166","#06d6a0","#118ab2","#073b4c"] },
31
+ { id: "crimson-spice", name: "Crimson Spice", primary: "#d62828", swatch: ["#003049","#d62828","#f77f00","#fcbf49","#eae2b7"] },
32
+ { id: "mint-ocean", name: "Mint Ocean", primary: "#168aad", swatch: ["#99d98c","#52b69a","#34a0a4","#168aad","#1e6091"] },
33
+ { id: "burgundy-gold", name: "Burgundy & Gold", primary: "#9e2a2b", swatch: ["#335c67","#fff3b0","#e09f3e","#9e2a2b","#540b0e"],
34
+ theme: { surface: "#fff3b0", surfaceAlt: "#f5e58c", text: "#540b0e", textMuted: "#335c67", border: "#e8d47a" } },
35
+ { id: "scarlet", name: "Scarlet", primary: "#ba181b", swatch: ["#0b090a","#660708","#a4161a","#ba181b","#e5383b"] },
36
+ { id: "teal-warmth", name: "Teal Warmth", primary: "#2a9d8f", swatch: ["#264653","#2a9d8f","#e9c46a","#f4a261","#e76f51"] },
37
+ { id: "deep-cyan", name: "Deep Cyan", primary: "#005f73", swatch: ["#001219","#005f73","#0a9396","#94d2bd","#e9d8a6"],
38
+ theme: { surface: "#e9d8a6", surfaceAlt: "#d6c58b", text: "#001219", textMuted: "#0a9396", border: "#c9b77e" } },
39
+ { id: "tropical", name: "Tropical", primary: "#0081a7", swatch: ["#0081a7","#00afb5","#fdfcdc","#fed9b7","#f07167"],
40
+ theme: { surface: "#fdfcdc", surfaceAlt: "#f3f0c0", text: "#003844", textMuted: "#00afb5", border: "#e6e3b5" } },
41
+ { id: "spice-road", name: "Spice Road", primary: "#fb8b24", swatch: ["#5f0f40","#9a031e","#fb8b24","#e36414","#0f4c5c"] },
42
+ { id: "sunset-gradient", name: "Sunset Gradient", primary: "#f3722c", swatch: ["#f94144","#f8961e","#f9c74f","#43aa8b","#277da1"] },
43
+ { id: "coral-reef", name: "Coral Reef", primary: "#006d77", swatch: ["#006d77","#83c5be","#edf6f9","#ffddd2","#e29578"] },
44
+ { id: "electric", name: "Electric", primary: "#ff006e", swatch: ["#ffbe0b","#fb5607","#ff006e","#8338ec","#3a86ff"] },
45
+ { id: "muted-taupe", name: "Muted Taupe", primary: "#4a4e69", swatch: ["#22223b","#4a4e69","#9a8c98","#c9ada7","#f2e9e4"],
46
+ theme: { surface: "#f2e9e4", surfaceAlt: "#e0d6d0", text: "#22223b", textMuted: "#9a8c98", border: "#d2c4c0" } },
47
+ { id: "warm-lilac", name: "Warm Lilac", primary: "#9d4edd", swatch: ["#240046","#5a189a","#7b2cbf","#9d4edd","#e0aaff"] },
48
+ { id: "rose-garden", name: "Rose Garden", primary: "#dd2d4a", swatch: ["#880d1e","#dd2d4a","#f26a8d","#f49cbb","#cbeef3"] },
49
+ { id: "forest-deep", name: "Forest Deep", primary: "#3a5a40", swatch: ["#dad7cd","#a3b18a","#588157","#3a5a40","#344e41"] },
50
+ { id: "pink-sunset", name: "Pink Sunset", primary: "#fb6f92", swatch: ["#ffe5ec","#ffc2d1","#ffb3c6","#ff8fab","#fb6f92"] },
51
+ { id: "twilight-purple", name: "Twilight Purple", primary: "#5a189a", swatch: ["#10002b","#240046","#3c096c","#5a189a","#7b2cbf"] },
52
+ { id: "olive-gold", name: "Olive & Gold", primary: "#bc6c25", swatch: ["#606c38","#283618","#fefae0","#dda15e","#bc6c25"],
53
+ theme: { surface: "#fefae0", surfaceAlt: "#f0ebc5", text: "#283618", textMuted: "#606c38", border: "#ded9a8" } },
54
+ { id: "sage-terracotta", name: "Sage & Terracotta", primary: "#e07a5f", swatch: ["#f4f1de","#e07a5f","#3d405b","#81b29a","#f2cc8f"],
55
+ theme: { surface: "#f4f1de", surfaceAlt: "#e7e2c6", text: "#3d405b", textMuted: "#81b29a", border: "#ddd8bc" } },
56
+ { id: "indigo-glow", name: "Indigo Glow", primary: "#3a86ff", swatch: ["#8338ec","#3a86ff","#06d6a0","#ffbe0b","#fb5607"] },
57
+ ];
58
+
59
+ // --- Apply & persist ------------------------------------------------------
60
+
61
+ function hexToRgb(hex) {
62
+ hex = hex.replace("#", "");
63
+ if (hex.length === 3) hex = hex.split("").map(function (c) { return c + c; }).join("");
64
+ var n = parseInt(hex, 16);
65
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
66
+ }
67
+
68
+ var THEME_VARS = [
69
+ "--fuji-surface",
70
+ "--fuji-surface-alt",
71
+ "--fuji-text",
72
+ "--fuji-text-muted",
73
+ "--fuji-border",
74
+ ];
75
+
76
+ function apply(palette) {
77
+ var rgb = hexToRgb(palette.primary);
78
+ var root = document.documentElement;
79
+ root.style.setProperty("--fuji-primary", palette.primary);
80
+ root.style.setProperty("--fuji-primary-rgb", rgb[0] + ", " + rgb[1] + ", " + rgb[2]);
81
+ document.body.setAttribute("data-fuji-palette", palette.id);
82
+
83
+ if (palette.theme) {
84
+ root.style.setProperty("--fuji-surface", palette.theme.surface);
85
+ root.style.setProperty("--fuji-surface-alt", palette.theme.surfaceAlt);
86
+ root.style.setProperty("--fuji-text", palette.theme.text);
87
+ root.style.setProperty("--fuji-text-muted", palette.theme.textMuted);
88
+ root.style.setProperty("--fuji-border", palette.theme.border);
89
+ document.body.setAttribute("data-fuji-theme", "on");
90
+ } else {
91
+ THEME_VARS.forEach(function (v) { root.style.removeProperty(v); });
92
+ document.body.removeAttribute("data-fuji-theme");
93
+ }
94
+ }
95
+
96
+ function save(id) {
97
+ try { localStorage.setItem(STORAGE_KEY, id); } catch (e) {}
98
+ }
99
+
100
+ function load() {
101
+ try { return localStorage.getItem(STORAGE_KEY); } catch (e) { return null; }
102
+ }
103
+
104
+ function getPalette(id) {
105
+ for (var i = 0; i < PALETTES.length; i++) if (PALETTES[i].id === id) return PALETTES[i];
106
+ return PALETTES[0];
107
+ }
108
+
109
+ // --- UI ------------------------------------------------------------------
110
+
111
+ function buildUI() {
112
+ if (document.querySelector(".fuji-palette-switcher")) return;
113
+
114
+ var wrapper = document.createElement("div");
115
+ wrapper.className = "fuji-palette-switcher";
116
+
117
+ var trigger = document.createElement("button");
118
+ trigger.type = "button";
119
+ trigger.className = "fuji-palette-switcher__trigger";
120
+ trigger.setAttribute("aria-label", "Try a colour palette");
121
+ trigger.innerHTML =
122
+ '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
123
+ '<circle cx="13.5" cy="6.5" r=".5" fill="currentColor"></circle>' +
124
+ '<circle cx="17.5" cy="10.5" r=".5" fill="currentColor"></circle>' +
125
+ '<circle cx="8.5" cy="7.5" r=".5" fill="currentColor"></circle>' +
126
+ '<circle cx="6.5" cy="12.5" r=".5" fill="currentColor"></circle>' +
127
+ '<path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.79 0 1.5-.71 1.5-1.5 0-.39-.15-.74-.39-1-.23-.27-.38-.62-.38-1 0-.79.71-1.5 1.5-1.5H16c3.31 0 6-2.69 6-6 0-5.5-4.5-10-10-10z"></path>' +
128
+ "</svg>";
129
+
130
+ var panel = document.createElement("div");
131
+ panel.className = "fuji-palette-switcher__panel";
132
+ panel.hidden = true;
133
+
134
+ var header = document.createElement("div");
135
+ header.className = "fuji-palette-switcher__header";
136
+ header.innerHTML = '<span class="fuji-palette-switcher__title">Palettes</span>';
137
+ panel.appendChild(header);
138
+
139
+ var grid = document.createElement("div");
140
+ grid.className = "fuji-palette-switcher__grid";
141
+
142
+ PALETTES.forEach(function (p) {
143
+ var item = document.createElement("button");
144
+ item.type = "button";
145
+ item.className = "fuji-palette-switcher__item";
146
+ item.setAttribute("data-palette-id", p.id);
147
+ item.setAttribute("title", p.name);
148
+ if (p.theme) item.classList.add("fuji-palette-switcher__item--themed");
149
+
150
+ var swatches = document.createElement("span");
151
+ swatches.className = "fuji-palette-switcher__swatches";
152
+ p.swatch.forEach(function (color) {
153
+ var bar = document.createElement("span");
154
+ bar.className = "fuji-palette-switcher__bar";
155
+ bar.style.background = color;
156
+ swatches.appendChild(bar);
157
+ });
158
+ item.appendChild(swatches);
159
+
160
+ var label = document.createElement("span");
161
+ label.className = "fuji-palette-switcher__label";
162
+ label.textContent = p.name;
163
+ if (p.theme) {
164
+ var badge = document.createElement("span");
165
+ badge.className = "fuji-palette-switcher__badge";
166
+ badge.title = "Full theme (surfaces + text repaint)";
167
+ badge.textContent = "◐";
168
+ label.appendChild(badge);
169
+ }
170
+ item.appendChild(label);
171
+
172
+ item.addEventListener("click", function (e) {
173
+ e.stopPropagation();
174
+ apply(p);
175
+ save(p.id);
176
+ markActive(p.id);
177
+ });
178
+
179
+ grid.appendChild(item);
180
+ });
181
+
182
+ panel.appendChild(grid);
183
+
184
+ trigger.addEventListener("click", function (e) {
185
+ e.stopPropagation();
186
+ panel.hidden = !panel.hidden;
187
+ });
188
+
189
+ document.addEventListener("click", function (e) {
190
+ if (!wrapper.contains(e.target)) panel.hidden = true;
191
+ });
192
+
193
+ document.addEventListener("keydown", function (e) {
194
+ if (e.key === "Escape") panel.hidden = true;
195
+ });
196
+
197
+ wrapper.appendChild(trigger);
198
+ wrapper.appendChild(panel);
199
+ document.body.appendChild(wrapper);
200
+ }
201
+
202
+ function markActive(id) {
203
+ document.querySelectorAll(".fuji-palette-switcher__item").forEach(function (el) {
204
+ el.classList.toggle(
205
+ "fuji-palette-switcher__item--active",
206
+ el.getAttribute("data-palette-id") === id
207
+ );
208
+ });
209
+ }
210
+
211
+ function readMeta(name) {
212
+ var el = document.querySelector('meta[name="' + name + '"]');
213
+ return el ? el.getAttribute("content") : null;
214
+ }
215
+
216
+ function init() {
217
+ // Config comes from <meta> tags injected by fuji_admin/active_admin_patch.rb
218
+ // (Rails initializer → FujiAdmin.configure). Fallbacks apply when the host
219
+ // app hasn't wired the gem yet.
220
+ var pickerEnabled = readMeta("fuji-palette-picker") === "true";
221
+ var defaultId = readMeta("fuji-default-palette") || "forest-meadow";
222
+
223
+ var stored = pickerEnabled ? load() : null;
224
+ var palette = getPalette(stored || defaultId);
225
+ apply(palette);
226
+
227
+ if (document.body.classList.contains("logged_out")) return;
228
+ if (!pickerEnabled) return;
229
+
230
+ buildUI();
231
+ markActive(palette.id);
232
+ }
233
+
234
+ document.addEventListener("DOMContentLoaded", init);
235
+ document.addEventListener("turbo:load", init);
236
+ document.addEventListener("turbolinks:load", init);
237
+ })();
@@ -0,0 +1,123 @@
1
+ // Fuji Admin — row-action dropdown.
2
+ //
3
+ // Replaces ActiveAdmin's inline row-action links (View / Edit / Delete / …)
4
+ // with a single vertical-ellipsis trigger that opens a menu. Keeps the
5
+ // actions column compact regardless of how many actions a resource exposes.
6
+ //
7
+ // Activated on `table.index_table td.col-actions` when the cell contains
8
+ // 2+ links. A single-link cell keeps its outlined-button fallback from
9
+ // components/_tables.scss — no dropdown needed.
10
+ //
11
+ // Idempotent across Turbo navigations.
12
+
13
+ (function () {
14
+ "use strict";
15
+
16
+ function triggerSVG() {
17
+ return (
18
+ '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true">' +
19
+ '<circle cx="12" cy="5" r="1.6"></circle>' +
20
+ '<circle cx="12" cy="12" r="1.6"></circle>' +
21
+ '<circle cx="12" cy="19" r="1.6"></circle>' +
22
+ "</svg>"
23
+ );
24
+ }
25
+
26
+ function wrapCell(cell) {
27
+ if (cell.dataset.fujiRowActionsWired === "1") return;
28
+ cell.dataset.fujiRowActionsWired = "1";
29
+
30
+ var links = cell.querySelectorAll(".table_actions > a, :scope > a");
31
+ if (links.length < 2) return; // keep fallback pill styling
32
+
33
+ var wrapper = document.createElement("div");
34
+ wrapper.className = "fuji-row-actions";
35
+
36
+ var trigger = document.createElement("button");
37
+ trigger.type = "button";
38
+ trigger.className = "fuji-row-actions__trigger";
39
+ trigger.setAttribute("aria-label", "Row actions");
40
+ trigger.setAttribute("aria-haspopup", "menu");
41
+ trigger.setAttribute("aria-expanded", "false");
42
+ trigger.innerHTML = triggerSVG();
43
+
44
+ var menu = document.createElement("div");
45
+ menu.className = "fuji-row-actions__menu";
46
+ menu.setAttribute("role", "menu");
47
+ menu.hidden = true;
48
+
49
+ Array.from(links).forEach(function (link) {
50
+ link.classList.add("fuji-row-actions__item");
51
+ link.setAttribute("role", "menuitem");
52
+ if (
53
+ link.classList.contains("delete_link") ||
54
+ link.getAttribute("data-method") === "delete"
55
+ ) {
56
+ link.classList.add("fuji-row-actions__item--danger");
57
+ }
58
+ menu.appendChild(link);
59
+ });
60
+
61
+ // Clear the original actions container.
62
+ cell.innerHTML = "";
63
+ wrapper.appendChild(trigger);
64
+ wrapper.appendChild(menu);
65
+ cell.appendChild(wrapper);
66
+
67
+ trigger.addEventListener("click", function (e) {
68
+ e.stopPropagation();
69
+ var wasOpen = !menu.hidden;
70
+ closeAll();
71
+ if (!wasOpen) openMenu(trigger, menu);
72
+ });
73
+ }
74
+
75
+ function openMenu(trigger, menu) {
76
+ menu.hidden = false;
77
+ trigger.setAttribute("aria-expanded", "true");
78
+
79
+ // Position the (fixed) menu just below the trigger, right-aligned to it.
80
+ var rect = trigger.getBoundingClientRect();
81
+ menu.style.top = (rect.bottom + 4) + "px";
82
+ menu.style.right = (window.innerWidth - rect.right) + "px";
83
+ menu.style.left = "auto";
84
+ }
85
+
86
+ function closeAll() {
87
+ document.querySelectorAll(".fuji-row-actions__menu").forEach(function (m) {
88
+ m.hidden = true;
89
+ m.style.top = "";
90
+ m.style.right = "";
91
+ m.style.left = "";
92
+ });
93
+ document.querySelectorAll(".fuji-row-actions__trigger").forEach(function (t) {
94
+ t.setAttribute("aria-expanded", "false");
95
+ });
96
+ }
97
+
98
+ function onDocumentClick(e) {
99
+ if (e.target.closest(".fuji-row-actions")) return;
100
+ closeAll();
101
+ }
102
+
103
+ function onKey(e) {
104
+ if (e.key === "Escape") closeAll();
105
+ }
106
+
107
+ function bind() {
108
+ document
109
+ .querySelectorAll("table.index_table td.col-actions")
110
+ .forEach(wrapCell);
111
+ }
112
+
113
+ document.addEventListener("DOMContentLoaded", function () {
114
+ bind();
115
+ document.addEventListener("click", onDocumentClick);
116
+ document.addEventListener("keydown", onKey);
117
+ // Fixed-positioned menu doesn't move with the page, so close on scroll/resize.
118
+ window.addEventListener("scroll", closeAll, true);
119
+ window.addEventListener("resize", closeAll);
120
+ });
121
+ document.addEventListener("turbo:load", bind);
122
+ document.addEventListener("turbolinks:load", bind);
123
+ })();
@@ -0,0 +1,23 @@
1
+ // Fuji Admin — MVP theme
2
+ //
3
+ // Build order:
4
+ // 1. tokens (variables/) ← done
5
+ // 2. mixins/ ← done (media queries)
6
+ // 3. reset + base typography ← done
7
+ // 4. grid / columns ← done
8
+ // 5. layout chrome ← done
9
+ // 6. components ← done
10
+ // 7. page-specific styling ← done
11
+
12
+ @import url("https://fonts.googleapis.com/css2?family=Saira:wght@300;400;500;600;700&display=swap");
13
+
14
+ @import "variables/variables";
15
+ @import "mixins/mixins";
16
+
17
+ @import "reset";
18
+ @import "base_typography";
19
+ @import "grid";
20
+
21
+ @import "layouts/layouts";
22
+ @import "components/components";
23
+ @import "pages/pages";
@@ -0,0 +1,56 @@
1
+ // Base typography. Applied at the document root so every AA page inherits
2
+ // consistent type without needing per-component resets.
3
+
4
+ body {
5
+ font-family: $font-family-body;
6
+ font-size: $font-size-base;
7
+ line-height: $line-height-base;
8
+ color: $text-color;
9
+ background-color: $body-background;
10
+ -webkit-font-smoothing: antialiased;
11
+ -moz-osx-font-smoothing: grayscale;
12
+ }
13
+
14
+ h1, h2, h3, h4, h5, h6 {
15
+ margin: 0 0 $space-3;
16
+ font-weight: $font-weight-semibold;
17
+ line-height: $line-height-tight;
18
+ color: $text-color;
19
+ }
20
+
21
+ h1 { font-size: $font-size-3xl; }
22
+ h2 { font-size: $font-size-2xl; }
23
+ h3 { font-size: $font-size-xl; }
24
+ h4 { font-size: $font-size-lg; }
25
+ h5 { font-size: $font-size-base; }
26
+ h6 { font-size: $font-size-sm; }
27
+
28
+ p {
29
+ margin: 0 0 $space-4;
30
+ }
31
+
32
+ a {
33
+ color: $link-primary-color;
34
+ text-decoration: none;
35
+ transition: color 0.15s ease;
36
+
37
+ &:hover {
38
+ color: $link-hover-color;
39
+ text-decoration: underline;
40
+ }
41
+ }
42
+
43
+ small {
44
+ font-size: $font-size-sm;
45
+ }
46
+
47
+ code, kbd, samp, pre {
48
+ font-family: $font-family-mono;
49
+ font-size: 0.9em;
50
+ }
51
+
52
+ hr {
53
+ border: 0;
54
+ border-top: 1px solid $surface-border;
55
+ margin: $space-5 0;
56
+ }
@@ -0,0 +1,111 @@
1
+ // 12-column flex grid with responsive modifiers.
2
+ //
3
+ // Prefixed `fuji-` so it never collides with ActiveAdmin's own `.columns` /
4
+ // `.column` classes. ActiveAdmin-specific columns will be styled separately
5
+ // in components/_columns.scss.
6
+ //
7
+ // Markup:
8
+ // <div class="fuji-row">
9
+ // <div class="fuji-col-12 fuji-col-md-6 fuji-col-lg-4">...</div>
10
+ // <div class="fuji-col-12 fuji-col-md-6 fuji-col-lg-8">...</div>
11
+ // </div>
12
+ //
13
+ // Semantics:
14
+ // fuji-col — equal-width flex child
15
+ // fuji-col-auto — shrink to content
16
+ // fuji-col-N — N/12 width at all breakpoints (N = 1..12)
17
+ // fuji-col-{bp}-N — N/12 width from breakpoint `bp` upwards (sm/md/lg/xl/xxl)
18
+
19
+ $grid-columns: 12;
20
+
21
+ .fuji-row {
22
+ display: flex;
23
+ flex-wrap: wrap;
24
+ margin-right: -#{$grid-gutter-sm / 2};
25
+ margin-left: -#{$grid-gutter-sm / 2};
26
+
27
+ @include media-up(md) {
28
+ margin-right: -#{$grid-gutter / 2};
29
+ margin-left: -#{$grid-gutter / 2};
30
+ }
31
+
32
+ > [class*="fuji-col"] {
33
+ padding-right: #{$grid-gutter-sm / 2};
34
+ padding-left: #{$grid-gutter-sm / 2};
35
+ box-sizing: border-box;
36
+
37
+ @include media-up(md) {
38
+ padding-right: #{$grid-gutter / 2};
39
+ padding-left: #{$grid-gutter / 2};
40
+ }
41
+ }
42
+ }
43
+
44
+ // Base equal-width column
45
+ .fuji-col {
46
+ flex: 1 0 0;
47
+ max-width: 100%;
48
+ }
49
+
50
+ // Shrink-to-content
51
+ .fuji-col-auto {
52
+ flex: 0 0 auto;
53
+ width: auto;
54
+ max-width: 100%;
55
+ }
56
+
57
+ // Helper to emit a width rule
58
+ @mixin fuji-col-width($n) {
59
+ flex: 0 0 percentage($n / $grid-columns);
60
+ max-width: percentage($n / $grid-columns);
61
+ }
62
+
63
+ // Unqualified (all breakpoints) columns: fuji-col-1 … fuji-col-12
64
+ @for $i from 1 through $grid-columns {
65
+ .fuji-col-#{$i} {
66
+ @include fuji-col-width($i);
67
+ }
68
+ }
69
+
70
+ // Responsive columns: fuji-col-sm-1 … fuji-col-xxl-12
71
+ @each $bp-name, $bp-value in $breakpoints {
72
+ @include media-up($bp-name) {
73
+ .fuji-col-#{$bp-name} {
74
+ flex: 1 0 0;
75
+ max-width: 100%;
76
+ }
77
+ .fuji-col-#{$bp-name}-auto {
78
+ flex: 0 0 auto;
79
+ width: auto;
80
+ max-width: 100%;
81
+ }
82
+ @for $i from 1 through $grid-columns {
83
+ .fuji-col-#{$bp-name}-#{$i} {
84
+ @include fuji-col-width($i);
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ // Container helpers — centered max-width at each breakpoint
91
+ .fuji-container {
92
+ width: 100%;
93
+ margin-right: auto;
94
+ margin-left: auto;
95
+ padding-right: $space-4;
96
+ padding-left: $space-4;
97
+
98
+ @each $bp-name, $max-width in $container-max-widths {
99
+ @include media-up($bp-name) {
100
+ max-width: $max-width;
101
+ }
102
+ }
103
+ }
104
+
105
+ .fuji-container-fluid {
106
+ width: 100%;
107
+ margin-right: auto;
108
+ margin-left: auto;
109
+ padding-right: $space-4;
110
+ padding-left: $space-4;
111
+ }
@@ -0,0 +1,48 @@
1
+ // Minimal modern reset. Not normalize.css — just the handful of declarations
2
+ // that every project wants. Touches only things that would interfere with
3
+ // theming; leaves sensible browser defaults alone.
4
+
5
+ *,
6
+ *::before,
7
+ *::after {
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ html {
12
+ -webkit-text-size-adjust: 100%;
13
+ text-size-adjust: 100%;
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ min-height: 100vh;
19
+ }
20
+
21
+ img,
22
+ svg,
23
+ video,
24
+ canvas,
25
+ audio,
26
+ iframe,
27
+ embed,
28
+ object {
29
+ display: block;
30
+ max-width: 100%;
31
+ }
32
+
33
+ button,
34
+ input,
35
+ select,
36
+ textarea {
37
+ font: inherit;
38
+ color: inherit;
39
+ }
40
+
41
+ table {
42
+ border-collapse: collapse;
43
+ border-spacing: 0;
44
+ }
45
+
46
+ [hidden] {
47
+ display: none !important;
48
+ }