jav_madmin 2.6.7 → 3.3.2

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -4
  3. data/app/assets/stylesheets/madmin/actiontext.css +31 -0
  4. data/app/assets/stylesheets/madmin/base.css +22 -11
  5. data/app/assets/stylesheets/madmin/buttons.css +9 -9
  6. data/app/assets/stylesheets/madmin/forms.css +271 -11
  7. data/app/assets/stylesheets/madmin/pagination.css +25 -21
  8. data/app/assets/stylesheets/madmin/reset.css +1 -1
  9. data/app/assets/stylesheets/madmin/sidebar.css +6 -6
  10. data/app/assets/stylesheets/madmin/tables.css +2 -1
  11. data/app/controllers/madmin/base_controller.rb +1 -5
  12. data/app/controllers/madmin/resource_controller.rb +53 -19
  13. data/app/helpers/madmin/application_helper.rb +33 -2
  14. data/app/helpers/madmin/filters_helper.rb +26 -0
  15. data/app/helpers/madmin/sort_helper.rb +1 -1
  16. data/app/javascript/madmin/application.js +4 -0
  17. data/app/javascript/madmin/controllers/application.js +0 -3
  18. data/app/javascript/madmin/controllers/filters_controller.js +35 -0
  19. data/app/javascript/madmin/controllers/nested_form_controller.js +5 -0
  20. data/app/javascript/madmin/controllers/select_controller.js +269 -22
  21. data/app/views/layouts/madmin/application.html.erb +1 -0
  22. data/app/views/madmin/application/_active_filters.html.erb +15 -0
  23. data/app/views/madmin/application/_filter.html.erb +13 -0
  24. data/app/views/madmin/application/_filters.html.erb +30 -0
  25. data/app/views/madmin/application/_javascript.html.erb +0 -2
  26. data/app/views/madmin/application/_pagination.html.erb +33 -0
  27. data/app/views/madmin/application/_per_page.html.erb +10 -0
  28. data/app/views/madmin/application/edit.html.erb +1 -1
  29. data/app/views/madmin/application/index.html.erb +9 -12
  30. data/app/views/madmin/application/new.html.erb +1 -1
  31. data/app/views/madmin/application/show.html.erb +2 -2
  32. data/app/views/madmin/fields/array/_form.html.erb +1 -0
  33. data/app/views/madmin/fields/array/_index.html.erb +1 -0
  34. data/app/views/madmin/fields/array/_show.html.erb +1 -0
  35. data/app/views/madmin/fields/attachment/_form.html.erb +1 -5
  36. data/app/views/madmin/fields/attachment/_show.html.erb +3 -1
  37. data/app/views/madmin/fields/attachments/_form.html.erb +1 -1
  38. data/app/views/madmin/fields/belongs_to/_form.html.erb +1 -1
  39. data/app/views/madmin/fields/belongs_to/_index.html.erb +5 -2
  40. data/app/views/madmin/fields/belongs_to/_show.html.erb +1 -1
  41. data/app/views/madmin/fields/enum/_form.html.erb +1 -1
  42. data/app/views/madmin/fields/file/_form.html.erb +1 -1
  43. data/app/views/madmin/fields/has_many/_form.html.erb +1 -1
  44. data/app/views/madmin/fields/has_many/_show.html.erb +3 -11
  45. data/app/views/madmin/fields/has_one/_index.html.erb +5 -1
  46. data/app/views/madmin/fields/has_one/_show.html.erb +1 -1
  47. data/app/views/madmin/fields/json/_form.html.erb +1 -1
  48. data/app/views/madmin/fields/json/_show.html.erb +1 -1
  49. data/app/views/madmin/fields/nested_has_many/_show.html.erb +3 -11
  50. data/app/views/madmin/fields/nested_has_one/_form.html.erb +18 -0
  51. data/app/views/madmin/fields/polymorphic/_form.html.erb +1 -2
  52. data/app/views/madmin/fields/polymorphic/_index.html.erb +5 -1
  53. data/app/views/madmin/fields/polymorphic/_show.html.erb +1 -1
  54. data/app/views/madmin/fields/select/_form.html.erb +1 -1
  55. data/config/importmap.rb +0 -3
  56. data/config/locales/el.yml +47 -0
  57. data/config/locales/en.yml +47 -0
  58. data/config/locales/fr.yml +47 -0
  59. data/config/locales/ko.yml +48 -0
  60. data/lib/generators/madmin/resource/templates/resource.rb.tt +3 -0
  61. data/lib/madmin/field.rb +26 -0
  62. data/lib/madmin/fields/array.rb +16 -0
  63. data/lib/madmin/fields/attachment.rb +7 -0
  64. data/lib/madmin/fields/belongs_to.rb +2 -1
  65. data/lib/madmin/fields/boolean.rb +1 -0
  66. data/lib/madmin/fields/currency.rb +7 -0
  67. data/lib/madmin/fields/date.rb +1 -0
  68. data/lib/madmin/fields/date_time.rb +1 -0
  69. data/lib/madmin/fields/decimal.rb +1 -0
  70. data/lib/madmin/fields/float.rb +1 -0
  71. data/lib/madmin/fields/has_many.rb +8 -23
  72. data/lib/madmin/fields/integer.rb +1 -0
  73. data/lib/madmin/fields/json.rb +21 -0
  74. data/lib/madmin/fields/nested_has_many.rb +2 -2
  75. data/lib/madmin/fields/nested_has_one.rb +48 -0
  76. data/lib/madmin/fields/polymorphic.rb +5 -0
  77. data/lib/madmin/fields/string.rb +2 -0
  78. data/lib/madmin/fields/text.rb +2 -0
  79. data/lib/madmin/filter.rb +113 -0
  80. data/lib/madmin/menu.rb +2 -1
  81. data/lib/madmin/page.rb +89 -0
  82. data/lib/madmin/pagination.rb +50 -0
  83. data/lib/madmin/resource.rb +86 -19
  84. data/lib/madmin/search.rb +5 -7
  85. data/lib/madmin/version.rb +1 -1
  86. data/lib/madmin.rb +45 -3
  87. metadata +17 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4901ab95118bdd1ee7273a72c2485dc63ebd3eeaa59206b1e96230920acae84
4
- data.tar.gz: c6816fbe255ce60c1c1af161da6120795c5ac45cf7ab56e72979589c2a5ace42
3
+ metadata.gz: d0f0ad77942110b06248a559111a99acbfad316f184fc9995693b0fe0a595e19
4
+ data.tar.gz: 1527cf749f6ba242beb721e97e29ad9344448a3d349e8afea98806a289a212fa
5
5
  SHA512:
6
- metadata.gz: 58d2ecaa79969d5f781b02654d4f55f1728bcef1d232f8dc94d33f90f850176600cf86b9b527ad0c333486e10128115cc3122d858ba1efca5c426b9dd71f8aae
7
- data.tar.gz: 40b2e8d203803b1c019404919a256df54c04be3789f2c8d15ab216459f7e691be6009848cc985a408607039537509436fa7fee24ff2cb633ef7cd4d98961f5f4
6
+ metadata.gz: 01d15ae1508200bea5021f8727bcf928a319ed22d601b4c45eb53ee1de9e4df975e237e3ade196e29d9c6fb02426760dde67efcc0bfe1980a2fe1ba19088c076
7
+ data.tar.gz: ab7992f2d05777ad689b4f084748389ad3db03838ef47544ca8369dee4f6e0ba4d4ea32cdbfe497259e309a373703ed68a973fc1d155f813dc7fb7e2db1372cd
data/README.md CHANGED
@@ -7,11 +7,14 @@
7
7
  Why another Ruby on Rails admin? We wanted an admin that was:
8
8
 
9
9
  - Familiar and customizable like Rails scaffolds (less DSL)
10
- - Supports all the Rails features out of the box (ActionText, ActionMailbox, has_secure_password, etc)
11
- - Stimulus / Turbolinks / Hotwire ready
12
- - Works with Import maps and Sprockets
10
+ - Supports Rails features out of the box (Action Text, Active Storage, Action Mailbox, has_secure_password, etc)
11
+ - Built with Hotwire: Turbo, Stimulus and import maps, with no Node or build step
12
+ - Search, filters and scopes on index pages, and dark mode
13
+ - Works with Propshaft and Sprockets
13
14
 
14
- ![Madmin Screenshot](docs/images/screenshot.png)
15
+ ![Madmin index page with filters](docs/images/index.png)
16
+
17
+ ![Madmin edit form in dark mode, searching for a user](docs/images/edit-dark.png)
15
18
 
16
19
  ## Installation
17
20
 
@@ -93,6 +96,35 @@ en:
93
96
  published: Live
94
97
  ```
95
98
 
99
+ ### Filters
100
+
101
+ Index pages have a Filters button for narrowing records by their columns, like "Admin is true" or "Created at after Aug 15". Filters combine with search, scopes and sorting, and they're kept in the URL so filtered lists can be bookmarked and shared.
102
+
103
+ String, text, number, date, datetime and boolean columns can be filtered. Encrypted columns and attributes that aren't database columns are left out. Turn filtering off for an attribute with `filter: false`:
104
+
105
+ ```ruby
106
+ class UserResource < Madmin::Resource
107
+ attribute :api_token, filter: false
108
+ end
109
+ ```
110
+
111
+ Custom fields can offer a filter by returning `:string`, `:number`, `:date`, `:datetime` or `:boolean` from `self.filter_type`.
112
+
113
+ ### Resource Names
114
+
115
+ Resource names in the menu, headings and buttons come from the model's translation when it has one, so they follow Rails' own `activerecord.models` keys:
116
+
117
+ ```yaml
118
+ en:
119
+ activerecord:
120
+ models:
121
+ post:
122
+ one: Article
123
+ other: Articles
124
+ ```
125
+
126
+ Models without a translation keep their default names, like `Posts` and `Active Storage / Blobs`.
127
+
96
128
  For labels that need logic (e.g. looking up a record name), override `scope_label`:
97
129
 
98
130
  ```ruby
@@ -178,6 +210,38 @@ class BookResource < Madmin::Resource
178
210
  end
179
211
  ```
180
212
 
213
+ ### Nested Forms
214
+
215
+ Use `:nested_has_many` or `:nested_has_one` to edit associated records inside the parent's form. The model needs `accepts_nested_attributes_for`, with `allow_destroy: true` for the Remove link to work, and the associated model needs a resource. Use `skip` to leave out attributes of the associated resource:
216
+
217
+ ```ruby
218
+ class User < ApplicationRecord
219
+ has_many :posts
220
+ has_one :profile
221
+ accepts_nested_attributes_for :posts, :profile, allow_destroy: true
222
+ end
223
+
224
+ class UserResource < Madmin::Resource
225
+ attribute :posts, :nested_has_many, skip: %I[user]
226
+ attribute :profile, :nested_has_one, skip: %I[user]
227
+ end
228
+ ```
229
+
230
+ A nested has one shows its fields when the record exists, or an "Add" link when it doesn't, so saving the parent doesn't create an empty record.
231
+
232
+ ### Direct Uploads
233
+
234
+ File fields upload through the form by default. Add `direct_upload: true` to upload straight from the browser to your Active Storage service instead, which keeps large files off your app servers and avoids request timeouts:
235
+
236
+ ```ruby
237
+ class PostResource < Madmin::Resource
238
+ attribute :image, direct_upload: true
239
+ attribute :attachments, direct_upload: true
240
+ end
241
+ ```
242
+
243
+ Cloud services like S3 need CORS configured to accept uploads from the browser. See the [Active Storage guide](https://guides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration).
244
+
181
245
  ## Custom Fields
182
246
 
183
247
  You can generate a custom field with:
@@ -281,6 +345,27 @@ Hooks registered after a class has already loaded run immediately, so ordering i
281
345
  You can use a couple of strategies to authenticate users who are trying to
282
346
  access your madmin panel: [Authentication Docs](docs/authentication.md)
283
347
 
348
+ ## Dark Mode
349
+
350
+ Madmin follows the system's light or dark setting. To always use one, add a stylesheet with `Madmin.stylesheets << "madmin_overrides"` (see [Assets](docs/assets.md)) and set `color-scheme`:
351
+
352
+ ```css
353
+ :root {
354
+ color-scheme: light;
355
+ }
356
+ ```
357
+
358
+ Madmin's colors are CSS variables on `:root`, defined with `light-dark()` so each has a light and a dark value. Custom fields and views can use them to match both modes, and you can override them to change the theme:
359
+
360
+ ```css
361
+ :root {
362
+ --primary-color: rgb(79 70 229); /* Button backgrounds */
363
+ --link-color: light-dark(rgb(79 70 229), rgb(129 140 248));
364
+ }
365
+ ```
366
+
367
+ The variables are `--primary-color`, `--link-color`, `--text-color`, `--light-text-color`, `--surface-color` (page and input backgrounds), `--background-color` (table headers), `--hover-color`, `--border-color`, `--input-border-color` and `--danger-color`.
368
+
284
369
  ## Assets
285
370
  You can customize the JavaScript and CSS assets used by Madmin for your application. To learn how
286
371
  see the [Assets Doc](docs/assets.md)
@@ -29,3 +29,34 @@
29
29
  padding: 0 !important;
30
30
  max-width: 100% !important;
31
31
  }
32
+
33
+ /* Trix draws its toolbar icons in black, so its buttons stay light */
34
+ trix-toolbar .trix-button-group {
35
+ background: light-dark(transparent, rgb(209 213 219));
36
+ }
37
+
38
+ trix-toolbar .trix-dialog {
39
+ background: var(--background-color);
40
+ }
41
+
42
+ /* Lexxy has no dark palette. `html:root` outranks Lexxy's own :root variables, which load after Madmin's */
43
+ html:root {
44
+ --lexxy-color-ink: light-dark(oklch(20% 0 0), var(--text-color));
45
+ --lexxy-color-ink-medium: light-dark(oklch(40% 0 0), rgb(209 213 219));
46
+ --lexxy-color-ink-light: light-dark(oklch(60% 0 0), var(--light-text-color));
47
+ --lexxy-color-ink-lighter: light-dark(oklch(85% 0 0), var(--border-color));
48
+ --lexxy-color-ink-lightest: light-dark(oklch(96% 0 0), var(--background-color));
49
+ --lexxy-color-ink-inverted: var(--surface-color);
50
+
51
+ --lexxy-color-accent-light: light-dark(oklch(88% 0.026 254), rgb(30 64 175));
52
+ --lexxy-color-accent-lightest: light-dark(oklch(92% 0.026 254), rgb(30 58 138));
53
+
54
+ --lexxy-color-code-token-att: light-dark(#d73a49, #f97583);
55
+ --lexxy-color-code-token-comment: light-dark(#6a737d, #959da5);
56
+ --lexxy-color-code-token-function: light-dark(#6f42c1, #b392f0);
57
+ --lexxy-color-code-token-operator: light-dark(#d73a49, #f97583);
58
+ --lexxy-color-code-token-property: light-dark(#005cc5, #79b8ff);
59
+ --lexxy-color-code-token-punctuation: light-dark(#24292e, #e1e4e8);
60
+ --lexxy-color-code-token-selector: light-dark(#22863a, #85e89d);
61
+ --lexxy-color-code-token-variable: light-dark(#e36209, #ffab70);
62
+ }
@@ -1,13 +1,23 @@
1
1
  :root {
2
+ /* Follows the system setting. Set `color-scheme: light` or `dark` on :root to force one */
3
+ color-scheme: light dark;
4
+
5
+ /* Button backgrounds, with white text */
2
6
  --primary-color: rgb(37 99 235);
3
- --border-color: rgb(229 231 235);
4
- --background-color: rgb(249 250 251);
5
- --text-color: rgb(2 6 23);
6
- --light-text-color: rgb(71 85 105);
7
+ --link-color: light-dark(rgb(37 99 235), rgb(96 165 250));
8
+ --border-color: light-dark(rgb(229 231 235), rgb(55 65 81));
9
+ --input-border-color: rgb(156 163 175);
10
+ --surface-color: light-dark(white, rgb(17 24 39));
11
+ --background-color: light-dark(rgb(249 250 251), rgb(31 41 55));
12
+ --hover-color: light-dark(rgb(243 244 246), rgb(31 41 55));
13
+ --text-color: light-dark(rgb(2 6 23), rgb(243 244 246));
14
+ --light-text-color: light-dark(rgb(71 85 105), rgb(156 163 175));
15
+ --danger-color: light-dark(rgb(239 68 68), rgb(248 113 113));
7
16
  --sidebar-width: 16rem;
8
17
  }
9
18
 
10
19
  body {
20
+ background-color: var(--surface-color);
11
21
  color: var(--text-color);
12
22
  font-size: 14px;
13
23
  -webkit-font-smoothing: antialiased;
@@ -19,7 +29,7 @@ body {
19
29
  }
20
30
 
21
31
  a {
22
- color: var(--primary-color);
32
+ color: var(--link-color);
23
33
  }
24
34
 
25
35
  .size-5 {
@@ -46,8 +56,8 @@ a {
46
56
  }
47
57
 
48
58
  &.alert-danger {
49
- background-color: oklch(.936 .032 17.717);
50
- color: oklch(.444 .177 26.899);
59
+ background-color: light-dark(oklch(.936 .032 17.717), oklch(.258 .092 26.042));
60
+ color: light-dark(oklch(.444 .177 26.899), oklch(.885 .062 18.334));
51
61
 
52
62
  svg {
53
63
  color: oklch(.637 .237 25.331);
@@ -55,13 +65,14 @@ a {
55
65
  }
56
66
 
57
67
  &.alert-notice {
58
- background-color: oklch(.962 .044 156.743);
59
- color: oklch(.448 .119 151.328);
68
+ background-color: light-dark(oklch(.962 .044 156.743), oklch(.266 .065 152.934));
69
+ color: light-dark(oklch(.448 .119 151.328), oklch(.925 .084 155.995));
60
70
 
61
71
  svg {
62
72
  color: oklch(.723 .219 149.579);
63
73
  }
64
74
  }
75
+
65
76
  }
66
77
 
67
78
  .search {
@@ -104,14 +115,14 @@ a {
104
115
 
105
116
 
106
117
  .metric {
107
- border: 1px solid rgb(229 231 235);
118
+ border: 1px solid var(--border-color);
108
119
  border-radius: 0.25rem;
109
120
  padding: 1rem;
110
121
  margin: 1rem;
111
122
  flex: 1 1 0%;
112
123
 
113
124
  h4 {
114
- color: rgb(75 85 99);
125
+ color: var(--light-text-color);
115
126
  font-weight: 600;
116
127
  margin-top: 0;
117
128
  margin-bottom: 0.5rem;
@@ -28,26 +28,26 @@
28
28
  }
29
29
 
30
30
  &.btn-secondary {
31
- background-color: white;
32
- color: rgb(31 41 55);
33
- box-shadow: inset 0 0 0 1px rgb(156 163 175);
31
+ background-color: var(--surface-color);
32
+ color: var(--text-color);
33
+ box-shadow: inset 0 0 0 1px var(--input-border-color);
34
34
 
35
35
  &:hover {
36
- background-color: rgb(243 244 246);
36
+ background-color: var(--hover-color);
37
37
  }
38
38
 
39
39
  &.active {
40
- background-color: rgb(243 244 246);
40
+ background-color: var(--hover-color);
41
41
  }
42
42
  }
43
43
 
44
44
  &.btn-danger {
45
- background-color: white;
46
- color: rgb(239 68 68);
47
- box-shadow: inset 0 0 0 1px rgb(239 68 68);
45
+ background-color: var(--surface-color);
46
+ color: var(--danger-color);
47
+ box-shadow: inset 0 0 0 1px var(--danger-color);
48
48
 
49
49
  &:hover {
50
- background-color: rgb(243 244 246);
50
+ background-color: var(--hover-color);
51
51
  }
52
52
  }
53
53
  }
@@ -1,5 +1,11 @@
1
1
  .error {
2
- color: rgb(239 68 68);
2
+ color: var(--danger-color);
3
+ }
4
+
5
+ /* Apps often add .error to invalid fields with a field_error_proc. Outline those fields instead of coloring their text, which would also tint Lexxy's toolbar */
6
+ :is(input, select, textarea, lexxy-editor, trix-editor).error {
7
+ color: inherit;
8
+ border-color: var(--danger-color);
3
9
  }
4
10
 
5
11
  .form-hint {
@@ -20,7 +26,7 @@
20
26
  }
21
27
 
22
28
  label {
23
- color: rgb(17 24 39);
29
+ color: var(--text-color);
24
30
  line-height: 1.5rem;
25
31
  font-weight: 500;
26
32
  font-size: 0.875rem;
@@ -62,29 +68,283 @@ select {
62
68
  -webkit-appearance: none;
63
69
  -moz-appearance: none;
64
70
  appearance: none;
65
- background-color: #fff;
71
+ background-color: var(--surface-color);
66
72
 
73
+ border: 1px solid var(--input-border-color);
67
74
  border-radius: 0.375rem;
68
- box-shadow: inset 0 0 0 1px rgb(156 163 175);
69
75
  line-height: 1.5rem;
70
- padding-bottom: 0.375rem;
71
- padding-left: 0.75rem;
72
- padding-right: 0.75rem;
73
- padding-top: 0.375rem;
76
+ /* Subtract the border so fields keep their size */
77
+ padding: calc(0.375rem - 1px) calc(0.75rem - 1px);
74
78
 
75
79
  &:focus {
76
- box-shadow: inset 0 0 0 1px rgb(var(--primary-color));
80
+ border-color: var(--primary-color);
77
81
  }
78
82
  }
79
83
 
80
84
  .required {
81
- color: rgb(239 68 68);
85
+ color: var(--danger-color);
82
86
  font-weight: 600;
83
87
  }
84
88
 
85
89
  .nested-fields {
86
- border: 1px solid #e5e7eb;
90
+ border: 1px solid var(--border-color);
87
91
  border-radius: 0.5rem;
88
92
  padding: 1.25rem;
89
93
  margin-bottom: 1rem;
90
94
  }
95
+
96
+ /* Native selects lose their arrow with `appearance: none` */
97
+ select:not([multiple]) {
98
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
99
+ background-position: right 0.5rem center;
100
+ background-repeat: no-repeat;
101
+ background-size: 1.5em 1.5em;
102
+ padding-right: 2.5rem;
103
+ }
104
+
105
+ /* Combobox (select_controller.js) */
106
+ .combobox {
107
+ position: relative;
108
+ }
109
+
110
+ .combobox-control {
111
+ display: flex;
112
+ flex-wrap: wrap;
113
+ align-items: center;
114
+ gap: 0.25rem;
115
+ background-color: var(--surface-color);
116
+ border: 1px solid var(--input-border-color);
117
+ border-radius: 0.375rem;
118
+ padding: calc(0.375rem - 1px) calc(0.75rem - 1px);
119
+ cursor: text;
120
+
121
+ &:focus-within {
122
+ border-color: var(--primary-color);
123
+ }
124
+ }
125
+
126
+ .combobox .combobox-input,
127
+ .combobox .combobox-input:focus {
128
+ flex: 1;
129
+ min-width: 8rem;
130
+ background: transparent;
131
+ border: none;
132
+ outline: none;
133
+ padding: 0;
134
+ }
135
+
136
+ .combobox-chip {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ gap: 0.25rem;
140
+ background-color: var(--hover-color);
141
+ border-radius: 0.25rem;
142
+ padding-left: 0.5rem;
143
+ font-size: 0.875rem;
144
+
145
+ button {
146
+ background: none;
147
+ border: none;
148
+ color: var(--light-text-color);
149
+ cursor: pointer;
150
+ padding: 0 0.5rem;
151
+
152
+ &:hover {
153
+ color: var(--text-color);
154
+ }
155
+ }
156
+ }
157
+
158
+ .combobox-listbox {
159
+ position: absolute;
160
+ z-index: 10;
161
+ top: 100%;
162
+ left: 0;
163
+ right: 0;
164
+ margin-top: 0.25rem;
165
+ max-height: 16rem;
166
+ overflow-y: auto;
167
+ list-style: none;
168
+ padding: 0.25rem 0;
169
+ background-color: var(--surface-color);
170
+ border: 1px solid var(--border-color);
171
+ border-radius: 0.375rem;
172
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
173
+
174
+ &[hidden] {
175
+ display: none;
176
+ }
177
+
178
+ li {
179
+ padding: 0.375rem 0.75rem;
180
+ cursor: pointer;
181
+ }
182
+
183
+ [aria-selected="true"] {
184
+ background-color: var(--hover-color);
185
+ }
186
+
187
+ .combobox-empty {
188
+ color: var(--light-text-color);
189
+ cursor: default;
190
+ }
191
+ }
192
+
193
+ /* Index filters (filters_controller.js) */
194
+ .filters-count {
195
+ background-color: var(--primary-color);
196
+ border-radius: 999px;
197
+ color: white;
198
+ display: inline-block;
199
+ font-size: 0.75rem;
200
+ line-height: 1.125rem;
201
+ margin-left: 0.25rem;
202
+ min-width: 1.125rem;
203
+ padding: 0 0.3rem;
204
+ text-align: center;
205
+ }
206
+
207
+ .filters {
208
+ background-color: var(--surface-color);
209
+ border: 1px solid var(--border-color);
210
+ border-radius: 0.5rem;
211
+ box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.2);
212
+ color: var(--text-color);
213
+ inset: auto;
214
+ margin: 0;
215
+ padding: 1rem;
216
+ right: 1rem;
217
+ top: 4.5rem;
218
+ width: min(44rem, calc(100vw - 2rem));
219
+ }
220
+
221
+ /* Place it under the button where anchor positioning is supported */
222
+ @supports (position-anchor: --filters) {
223
+ #filters-button {
224
+ anchor-name: --filters;
225
+ }
226
+
227
+ .filters {
228
+ position-anchor: --filters;
229
+ right: anchor(right);
230
+ top: calc(anchor(bottom) + 0.5rem);
231
+
232
+ /* Phones use the full width instead of lining up with the button */
233
+ @media (max-width: 767px) {
234
+ left: 1rem;
235
+ right: 1rem;
236
+ width: auto;
237
+ }
238
+ }
239
+ }
240
+
241
+ .filters-heading {
242
+ font-weight: 600;
243
+ margin: 0 0 0.75rem;
244
+ }
245
+
246
+ .filter-rows {
247
+ display: grid;
248
+ gap: 0.5rem;
249
+ }
250
+
251
+ .filter-row {
252
+ align-items: center;
253
+ display: grid;
254
+ gap: 0.5rem;
255
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) minmax(0, 1.2fr) auto;
256
+
257
+ select, input {
258
+ min-width: 0;
259
+ width: 100%;
260
+ }
261
+
262
+ .filter-remove {
263
+ grid-column: 4;
264
+ grid-row: 1;
265
+ }
266
+
267
+ @media (max-width: 767px) {
268
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
269
+
270
+ input {
271
+ grid-column: 1 / 3;
272
+ }
273
+
274
+ .filter-remove {
275
+ grid-column: 3;
276
+ }
277
+ }
278
+ }
279
+
280
+ .filter-remove {
281
+ background: none;
282
+ border: none;
283
+ border-radius: 0.375rem;
284
+ color: var(--light-text-color);
285
+ cursor: pointer;
286
+ font-size: 1.125rem;
287
+ height: 2rem;
288
+ width: 2rem;
289
+
290
+ &:hover {
291
+ background-color: var(--hover-color);
292
+ color: var(--danger-color);
293
+ }
294
+ }
295
+
296
+ .filters-footer {
297
+ align-items: center;
298
+ border-top: 1px solid var(--border-color);
299
+ display: flex;
300
+ flex-wrap: wrap;
301
+ gap: 0.5rem;
302
+ justify-content: space-between;
303
+ margin-top: 0.875rem;
304
+ padding-top: 0.875rem;
305
+ }
306
+
307
+ .btn.btn-link {
308
+ background: none;
309
+ color: var(--link-color);
310
+ font-weight: 500;
311
+ padding-left: 0.25rem;
312
+ padding-right: 0.25rem;
313
+ }
314
+
315
+ .active-filters {
316
+ align-items: center;
317
+ display: flex;
318
+ flex-wrap: wrap;
319
+ gap: 0.5rem;
320
+ margin-bottom: 1rem;
321
+ }
322
+
323
+ .filter-chip {
324
+ align-items: center;
325
+ background-color: light-dark(rgb(239 246 255), rgb(30 41 82));
326
+ border-radius: 999px;
327
+ color: light-dark(rgb(30 64 175), rgb(191 219 254));
328
+ display: inline-flex;
329
+ font-size: 0.8125rem;
330
+ font-weight: 500;
331
+ gap: 0.25rem;
332
+ padding: 0.125rem 0.25rem 0.125rem 0.75rem;
333
+
334
+ strong {
335
+ font-weight: 600;
336
+ }
337
+
338
+ a {
339
+ border-radius: 999px;
340
+ color: inherit;
341
+ line-height: 1.5rem;
342
+ text-align: center;
343
+ text-decoration: none;
344
+ width: 1.5rem;
345
+
346
+ &:hover {
347
+ background-color: var(--surface-color);
348
+ }
349
+ }
350
+ }
@@ -1,11 +1,15 @@
1
1
  .pagination {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ align-items: center;
2
5
  padding: 0.5rem;
3
6
  }
4
7
 
5
- .pagy {
8
+ .pagination .pages {
6
9
  display: inline-flex;
7
10
  isolation: isolate;
8
11
  border-radius: 0.375rem;
12
+ column-gap: 0.4rem;
9
13
 
10
14
  a {
11
15
  display: inline-flex;
@@ -17,21 +21,21 @@
17
21
  align-items: center;
18
22
  box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
19
23
  --tw-ring-inset: inset;
20
- --tw-ring-color: #D1D5DB;
24
+ --tw-ring-color: var(--input-border-color);
21
25
  font-size: 0.875rem;
22
26
  line-height: 1.25rem;
23
- color: #111827;
24
- background-color: #ffffff;
27
+ color: var(--text-color);
28
+ background-color: var(--surface-color);
25
29
  border-radius: 0.25rem;
26
30
  text-decoration: none;
27
31
  }
28
32
 
29
33
  a:hover {
30
- background-color: #F3F4F6;
34
+ background-color: var(--hover-color);
31
35
  }
32
36
 
33
- a:not([href]) {
34
- color: #D1D5DB;
37
+ a[aria-disabled="true"] {
38
+ color: var(--input-border-color);
35
39
  cursor: default;
36
40
  }
37
41
 
@@ -39,21 +43,21 @@
39
43
  color: #ffffff;
40
44
  background-color: var(--primary-color);
41
45
  }
46
+ }
42
47
 
43
- label {
44
- display: inline-block;
45
- padding-top: 0.125rem;
46
- padding-bottom: 0.125rem;
47
- padding-left: 0.75rem;
48
- padding-right: 0.75rem;
49
- border-radius: 0.5rem;
50
- white-space: nowrap;
51
- background-color: #E5E7EB;
52
- }
48
+ .pagination-info {
49
+ margin-left: 0.5rem;
50
+ }
51
+
52
+ .per-page {
53
+ display: inline-flex;
54
+ gap: 0.5rem;
55
+ align-items: center;
56
+ margin-left: auto;
57
+ font-size: 0.875rem;
53
58
 
54
- label input {
55
- border-radius: 0.375rem;
56
- border-style: none;
57
- background-color: #F3F4F6;
59
+ a[aria-current="true"] {
60
+ color: var(--text-color);
61
+ font-weight: 600;
58
62
  }
59
63
  }
@@ -2,7 +2,7 @@
2
2
  box-sizing: border-box;
3
3
  border-width: 0;
4
4
  border-style: solid;
5
- border-color: #e5e7eb;
5
+ border-color: var(--border-color, #e5e7eb);
6
6
  }
7
7
 
8
8
  body {