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,85 @@
1
+ // Filter form — lives in the sidebar on index pages. AA emits each filter
2
+ // as `.filter_form_field` with a label and a control; search submit at the
3
+ // end. We keep the stacked layout and match the panel aesthetic.
4
+
5
+ form.filter_form {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: $space-3;
9
+ padding: 0;
10
+ margin: 0;
11
+
12
+ .filter_form_field {
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: $space-2;
16
+ margin: 0;
17
+
18
+ label {
19
+ font-size: $font-size-xs;
20
+ font-weight: $font-weight-medium;
21
+ text-transform: uppercase;
22
+ letter-spacing: 0.04em;
23
+ color: $text-color-secondary;
24
+ margin: 0;
25
+ }
26
+
27
+ input[type="text"],
28
+ input[type="email"],
29
+ input[type="number"],
30
+ input[type="search"],
31
+ input[type="date"],
32
+ select,
33
+ textarea {
34
+ @include fuji-input-base;
35
+ font-size: $font-size-sm;
36
+ }
37
+
38
+ // Date range widget — pair of inputs with a separator.
39
+ &.filter_date_range {
40
+ flex-direction: column;
41
+
42
+ .seperator,
43
+ .separator {
44
+ color: $text-color-muted;
45
+ font-size: $font-size-xs;
46
+ padding: 0 $space-1;
47
+ }
48
+ }
49
+
50
+ // Check-box lists for boolean filters
51
+ .check_boxes,
52
+ .radio_buttons {
53
+ display: flex;
54
+ flex-wrap: wrap;
55
+ gap: $space-2;
56
+
57
+ label {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ gap: $space-1;
61
+ text-transform: none;
62
+ letter-spacing: 0;
63
+ color: $text-color;
64
+ font-weight: $font-weight-regular;
65
+ font-size: $font-size-sm;
66
+ }
67
+ }
68
+ }
69
+
70
+ .buttons,
71
+ fieldset.buttons {
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: $space-2;
75
+ padding-top: $space-2;
76
+ border: 0;
77
+ margin: 0;
78
+ }
79
+
80
+ a.clear_filters_btn {
81
+ @include fuji-button-secondary;
82
+ width: 100%;
83
+ justify-content: center;
84
+ }
85
+ }
@@ -0,0 +1,55 @@
1
+ // Flash messages — rendered by AA as `<div class="flashes"><div class="flash flash_notice">...</div></div>`.
2
+ // Shown at the top of the main content area.
3
+
4
+ // `.flashes` lives in #wrapper as a sibling of #title_bar / #active_admin_content.
5
+ // Since those siblings supply their own horizontal padding, we repeat the same
6
+ // values here so a flash message sits in the same visual gutter.
7
+ .flashes {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: $space-2;
11
+ padding: $space-4 $content-padding-mobile 0;
12
+ margin: 0;
13
+
14
+ @include media-up(md) {
15
+ padding: $space-4 $content-padding 0;
16
+ }
17
+
18
+ &:empty {
19
+ display: none; // avoid reserving space when no flash present
20
+ }
21
+ }
22
+
23
+ .flash {
24
+ padding: $space-3 $space-4;
25
+ border-radius: $border-radius;
26
+ font-size: $font-size-sm;
27
+ font-weight: $font-weight-medium;
28
+ border: 1px solid transparent;
29
+
30
+ &.flash_notice,
31
+ &.flash_success {
32
+ background-color: rgba($color-success, 0.1);
33
+ border-color: rgba($color-success, 0.3);
34
+ color: darken($color-success, 12%);
35
+ }
36
+
37
+ &.flash_alert,
38
+ &.flash_error {
39
+ background-color: rgba($color-danger, 0.1);
40
+ border-color: rgba($color-danger, 0.3);
41
+ color: darken($color-danger, 8%);
42
+ }
43
+
44
+ &.flash_warning {
45
+ background-color: rgba($color-warning, 0.12);
46
+ border-color: rgba($color-warning, 0.3);
47
+ color: darken($color-warning, 14%);
48
+ }
49
+
50
+ &.flash_info {
51
+ background-color: rgba($color-info, 0.1);
52
+ border-color: rgba($color-info, 0.3);
53
+ color: darken($color-info, 10%);
54
+ }
55
+ }
@@ -0,0 +1,77 @@
1
+ // Float labels — applied by fuji_admin/floats.js to text-like formtastic
2
+ // wrappers inside `fieldset.inputs`. The label rests inside the input at
3
+ // baseline, then animates up to sit on the top border when the input has
4
+ // focus or a value (matching PrimeReact Verona's float label pattern).
5
+ //
6
+ // Only `.fuji-float` wrappers get this treatment; selects, booleans, radios
7
+ // and date pickers keep their stacked label so their native controls stay
8
+ // predictable.
9
+
10
+ fieldset.inputs ol > li.fuji-float {
11
+ position: relative;
12
+
13
+ // The original fieldset rule adds label { margin-bottom: $space-2 } —
14
+ // override so the absolute label doesn't drag layout.
15
+ > label {
16
+ position: absolute;
17
+ top: calc(#{$input-padding-y} + 1px); // aligns with input's text line
18
+ left: calc(#{$input-padding-x} - #{$space-1} + 1px);
19
+ padding: 0 $space-1;
20
+ margin: 0;
21
+ font-size: $font-size-sm;
22
+ font-weight: $font-weight-regular;
23
+ line-height: 1.4;
24
+ color: $text-color-muted;
25
+ background-color: $surface-0; // masks input border when floated
26
+ pointer-events: none;
27
+ text-transform: none;
28
+ letter-spacing: 0;
29
+ white-space: nowrap;
30
+ max-width: calc(100% - #{$space-4});
31
+ overflow: hidden;
32
+ text-overflow: ellipsis;
33
+ z-index: 1;
34
+ transition:
35
+ top 0.18s ease,
36
+ font-size 0.18s ease,
37
+ color 0.18s ease;
38
+
39
+ // The required asterisk / abbr should sit tightly next to the label.
40
+ abbr {
41
+ border: 0;
42
+ text-decoration: none;
43
+ color: inherit;
44
+ margin-left: $space-1;
45
+ }
46
+ }
47
+
48
+ // Floated state — label sits on the input's top border, smaller & coloured.
49
+ &.fuji-float-focused > label,
50
+ &.fuji-float-filled > label {
51
+ top: calc(-#{$space-1} - 1px); // straddles the input top border
52
+ font-size: $font-size-xs;
53
+ font-weight: $font-weight-medium;
54
+ }
55
+
56
+ &.fuji-float-focused > label {
57
+ color: $primary-color;
58
+ }
59
+
60
+ &.fuji-float-filled:not(.fuji-float-focused) > label {
61
+ color: $text-color-secondary;
62
+ }
63
+
64
+ // Hints under the input; same spacing whether floated or resting.
65
+ > .inline-hints,
66
+ > .inline-errors {
67
+ margin-top: $space-1;
68
+ }
69
+ }
70
+
71
+ // Textarea variant — label docks at the top of the textarea when resting,
72
+ // not centred (textareas are tall so centring would look awkward).
73
+ fieldset.inputs ol > li.text.fuji-float {
74
+ > label {
75
+ top: calc(#{$input-padding-y} + 1px);
76
+ }
77
+ }
@@ -0,0 +1,237 @@
1
+ // Form inputs — text, email, password, number, search, tel, url, textarea,
2
+ // and native selects. Select2 and date pickers get their own components.
3
+ //
4
+ // Sizing approach: pad to ~40px total height (10px y / 12px x + 1px borders +
5
+ // ~1.4 line-height on 14px text). Matches Verona's InputText aesthetic so
6
+ // stacked forms breathe and feel touch-friendly.
7
+
8
+ $input-padding-y: 10px;
9
+ $input-padding-x: $space-3;
10
+
11
+ @mixin fuji-input-base {
12
+ display: block;
13
+ width: 100%;
14
+ padding: $input-padding-y $input-padding-x;
15
+ font-family: inherit;
16
+ font-size: $font-size-sm;
17
+ line-height: 1.4;
18
+ color: $text-color;
19
+ background-color: $surface-0;
20
+ border: 1px solid $surface-border;
21
+ border-radius: $border-radius;
22
+ transition:
23
+ background-color 0.2s ease,
24
+ border-color 0.2s ease,
25
+ box-shadow 0.2s ease,
26
+ color 0.2s ease;
27
+ appearance: none;
28
+ -webkit-appearance: none;
29
+
30
+ &::placeholder {
31
+ color: $text-color-muted;
32
+ opacity: 1; // Firefox applies its own opacity otherwise
33
+ }
34
+
35
+ &:hover:not(:disabled):not(:focus) {
36
+ border-color: $surface-400;
37
+ }
38
+
39
+ &:focus {
40
+ outline: none;
41
+ border-color: $primary-500;
42
+ box-shadow: 0 0 0 3px rgba($primary-500, 0.18);
43
+ }
44
+
45
+ &:disabled,
46
+ &[readonly] {
47
+ background-color: $surface-50;
48
+ color: $text-color-muted;
49
+ cursor: not-allowed;
50
+ border-color: $surface-200;
51
+
52
+ &::placeholder {
53
+ color: $surface-400;
54
+ }
55
+ }
56
+
57
+ &.invalid,
58
+ &[aria-invalid="true"] {
59
+ border-color: $color-danger;
60
+
61
+ &:focus {
62
+ box-shadow: 0 0 0 3px rgba($color-danger, 0.18);
63
+ }
64
+ }
65
+ }
66
+
67
+ form {
68
+ input[type="text"],
69
+ input[type="email"],
70
+ input[type="password"],
71
+ input[type="number"],
72
+ input[type="search"],
73
+ input[type="tel"],
74
+ input[type="url"],
75
+ input[type="date"],
76
+ input[type="datetime-local"],
77
+ input[type="month"],
78
+ input[type="time"],
79
+ input[type="week"],
80
+ textarea {
81
+ @include fuji-input-base;
82
+ }
83
+
84
+ textarea {
85
+ height: 144px; // sensible default regardless of `rows` attribute
86
+ min-height: 108px;
87
+ line-height: 1.5;
88
+ resize: vertical; // user can drag taller if needed
89
+ }
90
+
91
+ select {
92
+ @include fuji-input-base;
93
+ padding-right: $space-7;
94
+ background-image: linear-gradient(45deg, transparent 50%, $text-color-secondary 50%),
95
+ linear-gradient(135deg, $text-color-secondary 50%, transparent 50%);
96
+ background-position: right $space-4 center, right $space-3 center;
97
+ background-size: 5px 5px, 5px 5px;
98
+ background-repeat: no-repeat;
99
+ }
100
+
101
+ // Fieldset wrapper used throughout AA forms. We treat it as a card with a
102
+ // distinct header bar; the legend replaces the fieldset's top border so the
103
+ // border never passes behind the title.
104
+ fieldset.inputs {
105
+ background-color: $surface-0;
106
+ border: 1px solid $surface-border;
107
+ border-radius: $border-radius-card;
108
+ padding: 0;
109
+ margin: 0 0 $space-5;
110
+ overflow: hidden; // keep the header's rounded top corners clean
111
+
112
+ legend {
113
+ display: block;
114
+ float: left; // lets the legend own the card's top row
115
+ width: 100%;
116
+ padding: $space-3 $space-5;
117
+ margin: 0;
118
+ background-color: $surface-50;
119
+ color: $text-color;
120
+ font-size: $font-size-xs;
121
+ font-weight: $font-weight-semibold;
122
+ text-transform: uppercase;
123
+ letter-spacing: 0.06em;
124
+ border-bottom: 1px solid $surface-200;
125
+
126
+ span {
127
+ background: transparent;
128
+ color: inherit;
129
+ padding: 0;
130
+ }
131
+ }
132
+
133
+ ol {
134
+ clear: both; // clear the floated legend above
135
+ list-style: none;
136
+ padding: $space-5;
137
+ margin: 0;
138
+
139
+ > li {
140
+ padding: 0 0 $space-4;
141
+ margin: 0;
142
+
143
+ &:last-child {
144
+ padding-bottom: 0;
145
+ }
146
+
147
+ > label {
148
+ display: block;
149
+ margin-bottom: $space-2;
150
+ font-size: $font-size-sm;
151
+ font-weight: $font-weight-medium;
152
+ color: $text-color-secondary;
153
+ }
154
+
155
+ p.inline-hints,
156
+ .inline-hints {
157
+ margin-top: $space-1;
158
+ font-size: $font-size-xs;
159
+ color: $text-color-muted;
160
+ }
161
+
162
+ p.inline-errors,
163
+ .inline-errors {
164
+ margin-top: $space-1;
165
+ font-size: $font-size-xs;
166
+ color: $color-danger;
167
+ }
168
+ }
169
+
170
+ // Boolean input — AA wraps the checkbox + label text inside a single
171
+ // <label>, so we just style that label as an inline-flex row and let
172
+ // the hint drop naturally below.
173
+ li.boolean {
174
+ > label {
175
+ display: inline-flex;
176
+ align-items: center;
177
+ gap: $space-2;
178
+ margin: 0;
179
+ font-size: $font-size-sm;
180
+ font-weight: $font-weight-regular;
181
+ color: $text-color;
182
+ text-transform: none;
183
+ letter-spacing: 0;
184
+ cursor: pointer;
185
+
186
+ input[type="checkbox"] {
187
+ margin: 0;
188
+ }
189
+ }
190
+
191
+ > .inline-hints,
192
+ > p.inline-hints {
193
+ display: block;
194
+ margin: $space-1 0 0;
195
+ font-size: $font-size-xs;
196
+ color: $text-color-muted;
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ // Button row at the bottom of a form.
203
+ fieldset.actions,
204
+ fieldset.buttons {
205
+ display: flex;
206
+ gap: $space-3;
207
+ padding: $space-4 0 0;
208
+ margin: 0;
209
+ border: 0;
210
+
211
+ ol {
212
+ display: flex;
213
+ gap: $space-3;
214
+ list-style: none;
215
+ padding: 0;
216
+ margin: 0;
217
+ }
218
+
219
+ li {
220
+ display: inline-flex;
221
+ }
222
+ }
223
+ }
224
+
225
+ // Radio + checkbox native styling — tidy size + primary accent.
226
+ input[type="checkbox"],
227
+ input[type="radio"] {
228
+ width: 16px;
229
+ height: 16px;
230
+ accent-color: $primary-color;
231
+ cursor: pointer;
232
+
233
+ &:focus-visible {
234
+ outline: none;
235
+ box-shadow: $shadow-focus;
236
+ }
237
+ }
@@ -0,0 +1,61 @@
1
+ // Mobile menu toggle (hamburger). Injected by fuji_admin/menu.js into the
2
+ // header. Visible below lg; hidden on desktop where the sidebar is fixed.
3
+
4
+ .fuji-menu-toggle {
5
+ display: inline-flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ flex-direction: column;
9
+ gap: 4px;
10
+ width: 40px;
11
+ height: 40px;
12
+ margin-left: $space-2;
13
+ padding: 0;
14
+ background: transparent;
15
+ border: 0;
16
+ border-radius: $border-radius;
17
+ cursor: pointer;
18
+ color: $text-color-secondary;
19
+ transition: background-color 0.15s ease, color 0.15s ease;
20
+
21
+ &:hover {
22
+ background-color: $surface-100;
23
+ color: $text-color;
24
+ }
25
+
26
+ &:focus-visible {
27
+ outline: none;
28
+ box-shadow: $shadow-focus;
29
+ }
30
+
31
+ &__bar {
32
+ display: block;
33
+ width: 20px;
34
+ height: 2px;
35
+ background-color: currentColor;
36
+ border-radius: 1px;
37
+ }
38
+
39
+ @include media-up(lg) {
40
+ display: none;
41
+ }
42
+ }
43
+
44
+ // Shared backdrop for both drawers (mobile menu, filters). Pseudo-element can
45
+ // only exist once per element, so both states listed together.
46
+ body.fuji-menu-open::after,
47
+ body.fuji-filters-open::after {
48
+ content: "";
49
+ position: fixed;
50
+ inset: 0;
51
+ background-color: rgba(0, 0, 0, 0.35);
52
+ z-index: 14;
53
+ }
54
+
55
+ // Menu drawer is only a drawer below lg (sidebar is inline at lg+). Hide the
56
+ // backdrop above lg *unless* the filters drawer is the reason it's there.
57
+ @include media-up(lg) {
58
+ body.fuji-menu-open:not(.fuji-filters-open)::after {
59
+ display: none;
60
+ }
61
+ }
@@ -0,0 +1,70 @@
1
+ // Pagination — AA uses kaminari, rendered as a `.pagination` div containing
2
+ // `<span>` wrappers around `<a>` links, plus `span.page.current`.
3
+
4
+ .pagination {
5
+ display: flex;
6
+ flex-wrap: wrap;
7
+ gap: $space-1;
8
+ align-items: center;
9
+ padding: $space-4 0 0;
10
+ font-size: $font-size-sm;
11
+
12
+ > span,
13
+ > a,
14
+ > em {
15
+ display: inline-flex;
16
+ }
17
+
18
+ a,
19
+ span.page.current,
20
+ span.current,
21
+ .first a,
22
+ .last a,
23
+ .prev a,
24
+ .next a {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ min-width: 32px;
29
+ height: 32px;
30
+ padding: 0 $space-3;
31
+ border: 1px solid $surface-border;
32
+ border-radius: $border-radius;
33
+ color: $text-color-secondary;
34
+ background-color: $surface-0;
35
+ text-decoration: none;
36
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
37
+
38
+ &:hover {
39
+ background-color: $surface-100;
40
+ border-color: $surface-400;
41
+ color: $text-color;
42
+ text-decoration: none;
43
+ }
44
+ }
45
+
46
+ span.page.current,
47
+ span.current {
48
+ background-color: $primary-color;
49
+ border-color: $primary-color;
50
+ color: $primary-color-text;
51
+
52
+ &:hover {
53
+ background-color: $primary-700;
54
+ border-color: $primary-700;
55
+ color: $primary-color-text;
56
+ }
57
+ }
58
+
59
+ .gap {
60
+ padding: 0 $space-2;
61
+ color: $text-color-muted;
62
+ }
63
+ }
64
+
65
+ // Pagination summary text ("Displaying 1 - 30 of 521")
66
+ .pagination_information {
67
+ padding: $space-3 0 0;
68
+ color: $text-color-muted;
69
+ font-size: $font-size-sm;
70
+ }