active_admin_prism 0.1.3 → 0.1.4

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.
@@ -3,22 +3,36 @@
3
3
  module ActiveAdmin
4
4
  module Views
5
5
  # Reopens ActiveAdmin::Views::SidebarSection (used for every sidebar
6
- # section, e.g. "Filters", "Search Status", any custom
7
- # `sidebar :title do ... end` block) to inject a real inline SVG icon
8
- # into the title of the *Filters* section specifically matching the
9
- # id ActiveAdmin::SidebarSection#id computes for it
10
- # ("filters_sidebar_section"). Every other sidebar section is untouched.
6
+ # section "Filters", "Search Status", and any custom
7
+ # `sidebar "Title" do ... end` block a host app registers) to collapse
8
+ # *every* section to a single icon button by default, expanding to the
9
+ # full panel on click — not just the Filters section, which is all this
10
+ # used to do.
11
11
  #
12
- # The label text is wrapped in its own span so CSS can visually hide it
13
- # while collapsed without touching the icon (see
14
- # scss-src/_panels.scss's #filters_sidebar_section rules, and
15
- # prism.js's filters-collapse handler for the actual toggle).
12
+ # Why generalize this: the #sidebar column's own width only shrinks to
13
+ # a 72px icon gutter while every one of its sections is collapsed (see
14
+ # scss-src/_panels.scss's ":has(.prism-collapsible-panel):not(:has(...open))"
15
+ # rule) a section left OUT of that treatment still renders at its
16
+ # normal full width/height inside that shrunk 72px column, wrapping
17
+ # every word onto its own line (or, depending on its own CSS, spilling
18
+ # out unclipped over the main content) instead of collapsing cleanly
19
+ # alongside Filters. A host's own custom sidebar block (e.g. a "More
20
+ # Actions" link list) hit exactly this before this file generalized
21
+ # the treatment to cover it too.
22
+ #
23
+ # A host can pass its own icon via `sidebar "Title", icon: :list do
24
+ # ... end` (any name from lib/prism_icons.rb) — anything else, plus
25
+ # every section that predates this option, falls back to a generic
26
+ # icon (:filter for the Filters section specifically, matching its
27
+ # pre-existing look; :list for everything else).
16
28
  #
17
29
  # This only runs when ActiveAdminPrism.configuration.collapsible_filters
18
- # is true — when it's false the section is left exactly as ActiveAdmin
19
- # renders it by default (plain text title, no icon).
30
+ # is true — when it's false, every section is left exactly as
31
+ # ActiveAdmin renders it by default (plain text title, no icon, never
32
+ # collapsed).
20
33
  class SidebarSection < Panel
21
34
  FILTERS_SECTION_ID = "filters_sidebar_section"
35
+ DEFAULT_ICON = :list
22
36
 
23
37
  def build(section)
24
38
  @section = section
@@ -26,7 +40,7 @@ module ActiveAdmin
26
40
  add_class @section.custom_class if @section.custom_class
27
41
  self.id = @section.id
28
42
 
29
- if @section.id == FILTERS_SECTION_ID && ActiveAdminPrism.configuration.collapsible_filters
43
+ if ActiveAdminPrism.configuration.collapsible_filters
30
44
  add_class "prism-collapsible-panel"
31
45
 
32
46
  # Panel#build already appended the plain-text @title (h3) as the
@@ -48,7 +62,7 @@ module ActiveAdmin
48
62
  # be explicitly deleted from wherever it landed before unshifting
49
63
  # it to the front — otherwise it ends up in `children` twice.
50
64
  children.delete(@title)
51
- icon = ActiveAdminPrism::Icons.svg(:filter, css_class: "prism-filter-icon")
65
+ icon = ActiveAdminPrism::Icons.svg(icon_name, css_class: "prism-filter-icon")
52
66
  contents = @contents
53
67
  @contents = nil
54
68
  new_title = h3 do
@@ -64,6 +78,21 @@ module ActiveAdmin
64
78
 
65
79
  build_sidebar_content
66
80
  end
81
+
82
+ private
83
+
84
+ # `options[:icon]` is a new convention this gem adds to the stock
85
+ # `sidebar "Title", only: [...] do ... end` DSL — ActiveAdmin::
86
+ # SidebarSection itself just stores the whole options hash
87
+ # untouched, so a host can already pass `icon:` today without any
88
+ # of their own version needing to change; it's a no-op until this
89
+ # gem starts reading it here.
90
+ def icon_name
91
+ return @section.options[:icon] if @section.options[:icon]
92
+ return :filter if @section.id == FILTERS_SECTION_ID
93
+
94
+ DEFAULT_ICON
95
+ end
67
96
  end
68
97
  end
69
98
  end
@@ -221,7 +221,7 @@ module ActiveAdmin
221
221
  # #sidebar_collapsible has shrunk the sidebar to its icon-only rail
222
222
  # and .prism-nav-label itself is hidden (see scss-src/_sidebar.scss).
223
223
  span(class: "prism-nav-group-toggle", data: { "prism-toggle": true }, title: label) do
224
- text_node icon_for(item)
224
+ text_node icon_for(item, label)
225
225
  span(label, class: "prism-nav-label")
226
226
  text_node chevron_svg
227
227
  end
@@ -242,13 +242,14 @@ module ActiveAdmin
242
242
  end
243
243
 
244
244
  def link_body(item, label)
245
- helpers.safe_join([icon_for(item), helpers.content_tag(:span, label, class: "prism-nav-label")])
245
+ helpers.safe_join([icon_for(item, label), helpers.content_tag(:span, label, class: "prism-nav-label")])
246
246
  end
247
247
 
248
248
  # Always returns an html_safe string (possibly empty) — never escape-able
249
249
  # raw markup should cross a text_node/content_tag boundary unmarked.
250
- def icon_for(item)
250
+ def icon_for(item, label)
251
251
  name = item.html_options[:icon] || DEFAULT_ITEM_ICONS[item.id]
252
+ name ||= ActiveAdminPrism::Icons.auto_nav_icon_name(label) if ActiveAdminPrism.configuration.auto_nav_icons
252
253
  return "".html_safe unless name
253
254
 
254
255
  (ActiveAdminPrism::Icons.svg(name, css_class: "prism-nav-icon") || "").html_safe
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module Views
5
+ # Reopens ActiveAdmin::Views::TitleBar purely to render
6
+ # ActiveAdminPrism::Configuration#action_items_dropdown_threshold as a
7
+ # data attribute on "#titlebar_right" — js-src/prism.js reads it at
8
+ # runtime to decide whether the action_item buttons it finds inside
9
+ # need consolidating into a dropdown (see that file, and
10
+ # scss-src/_buttons.scss for the dropdown's own styling). Nothing else
11
+ # about title bar rendering changes; this can't be done in CSS alone
12
+ # since deciding whether to consolidate depends on *counting* how many
13
+ # action_items actually rendered, not just their presence.
14
+ class TitleBar
15
+ def build_titlebar_right
16
+ config = ActiveAdminPrism.configuration
17
+ data = {}
18
+ data[:"prism-action-items-threshold"] = config.action_items_dropdown_threshold if config.action_items_dropdown
19
+
20
+ div id: "titlebar_right", data: data do
21
+ build_action_items
22
+ end
23
+ end
24
+ private :build_titlebar_right
25
+ end
26
+ end
27
+ end
@@ -183,6 +183,43 @@ module ActiveAdminPrism
183
183
  # before this flag existed.
184
184
  attr_accessor :sidebar_collapsible
185
185
 
186
+ # Whether a title bar with more than #action_items_dropdown_threshold
187
+ # `action_item` buttons collapses them into a single "Actions"
188
+ # dropdown instead of rendering every one inline. A resource that
189
+ # registers many (a dozen+ CSV upload / bulk-action links is a real
190
+ # example this was built for) turns the title bar into a multi-row
191
+ # wall of buttons otherwise — see js-src/prism.js's action-items
192
+ # handler, which does the actual consolidation client-side, and
193
+ # lib/active_admin/views/title_bar.rb for the threshold this reads.
194
+ # false never consolidates — every action_item always renders inline,
195
+ # matching this gem's behavior before this flag existed.
196
+ attr_accessor :action_items_dropdown
197
+
198
+ # How many `action_item`s (not counting the auto-added "New Resource"
199
+ # button, which is never counted or consolidated — see
200
+ # lib/active_admin/views/action_items.rb) a title bar can have before
201
+ # #action_items_dropdown (if true) collapses them into a dropdown.
202
+ # Default 0 means the "Actions" Select2 dropdown is what a host sees
203
+ # by default the moment they add even a single custom action_item,
204
+ # rather than only once they've piled up "many" of them — raise this
205
+ # if you'd rather a page with just one or two extra actions kept
206
+ # rendering them inline instead.
207
+ attr_accessor :action_items_dropdown_threshold
208
+
209
+ # Whether a Pages nav item with no explicit `icon:` (and not one of
210
+ # the handful this gem itself always icons — the current-user/logout
211
+ # rows) gets one anyway instead of rendering label-less, or as just a
212
+ # bare dot once the sidebar is collapsed to its icon-only rail (see
213
+ # ActiveAdminPrism::Configuration#sidebar_collapsible). Deterministic
214
+ # per item, not re-rolled on every request — the same menu label
215
+ # always picks the same icon from lib/prism_icons.rb's
216
+ # ActiveAdminPrism::Icons::NAV_ICON_POOL (see
217
+ # lib/active_admin/views/prism_sidebar.rb#icon_for), so it doesn't
218
+ # visibly shuffle around between page loads. false renders no icon at
219
+ # all for such an item, matching this gem's behavior before this flag
220
+ # existed.
221
+ attr_accessor :auto_nav_icons
222
+
186
223
  # Milliseconds equivalent of #flash_auto_dismiss_seconds, or nil when
187
224
  # #flash_auto_dismiss is off — the exact number both flash-rendering
188
225
  # paths this gem ships (lib/active_admin/views/flash_messages.rb's
@@ -223,6 +260,9 @@ module ActiveAdminPrism
223
260
  @select2 = false
224
261
  @active_filters_bar = true
225
262
  @sidebar_collapsible = true
263
+ @action_items_dropdown = true
264
+ @action_items_dropdown_threshold = 0
265
+ @auto_nav_icons = true
226
266
  end
227
267
  end
228
268
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveAdminPrism
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
5
5
  end
@@ -13,6 +13,8 @@ require_relative "active_admin/views/index_table_actions"
13
13
  require_relative "active_admin/views/flash_messages"
14
14
  require_relative "active_admin/views/filters_sidebar"
15
15
  require_relative "active_admin/views/active_filters_bar"
16
+ require_relative "active_admin/views/title_bar"
17
+ require_relative "active_admin/views/action_items"
16
18
  require_relative "formtastic/inputs/prism_toggle_input"
17
19
 
18
20
  module ActiveAdminPrism
data/lib/prism_icons.rb CHANGED
@@ -43,6 +43,27 @@ module ActiveAdminPrism
43
43
  info: '<circle cx="12" cy="12" r="9"/><path d="M12 11v5.5"/><circle cx="12" cy="7.8" r="0.1" fill="currentColor" stroke-width="2.5"/>'
44
44
  }.freeze
45
45
 
46
+ # Candidates for ActiveAdminPrism::Configuration#auto_nav_icons — a
47
+ # generic-enough subset of ICONS above that any of them reads as
48
+ # plausible next to an arbitrary menu label (excludes chevron_down,
49
+ # check/x, the alert/info trio and logout: purpose-specific shapes
50
+ # that would look wrong or actively misleading picked at random for,
51
+ # say, a "Reports" link).
52
+ NAV_ICON_POOL = %i[
53
+ dashboard users cart box receipt credit_card message settings
54
+ home list folder bell search globe eye pencil filter
55
+ ].freeze
56
+
57
+ # Deterministic, not actually random — the same `label` always maps
58
+ # to the same icon (a simple sum-of-bytes hash, stable across
59
+ # processes/restarts unlike Ruby's own salted String#hash), so a
60
+ # nav item's auto-picked icon doesn't visibly shuffle between page
61
+ # loads. See lib/active_admin/views/prism_sidebar.rb#icon_for, the
62
+ # only caller.
63
+ def self.auto_nav_icon_name(label)
64
+ NAV_ICON_POOL[label.to_s.each_byte.sum % NAV_ICON_POOL.length]
65
+ end
66
+
46
67
  # Returns an inline <svg>...</svg> string for `name`, or nil if unknown.
47
68
  def self.svg(name, css_class: "prism-icon", size: 18)
48
69
  inner = ICONS[name.to_sym]
@@ -43,6 +43,76 @@ form input[type="submit"].danger {
43
43
  margin: 0;
44
44
  }
45
45
 
46
+ // #prism-titlebar-actions-row (below) and this button are appended as
47
+ // plain DOM siblings inside .action_items via prismTitlebarActionsRow() in
48
+ // js-src/prism.js, with no whitespace text node between them — inline-flex
49
+ // elements packed that way sit flush with zero gap, so `gap` on the row
50
+ // itself (which only spaces *its own* children) can't create the visual
51
+ // separation on its own. Margin goes on this span itself, not its inner
52
+ // `<a>`, since the rule just above pins that `<a>`'s own margin to 0.
53
+ #title_bar .action_items span.action_item.prism-action-item-new {
54
+ margin-left: 10px;
55
+ }
56
+
57
+ // Shared row for this theme's own title-bar "jump menu" dropdowns —
58
+ // js-src/prism.js's prismTitlebarActionsRow() creates this once and both
59
+ // the consolidated action_items dropdown and the consolidated "More
60
+ // Actions" sidebar-panels dropdown (below) append themselves into it, so
61
+ // they render side by side rather than as two disconnected mechanisms in
62
+ // different parts of the page. The row as a whole (not each dropdown
63
+ // individually) is what a later IIFE aligns to the index table's right
64
+ // edge — see that file for why.
65
+ .prism-titlebar-actions-row {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ gap: 10px;
69
+ }
70
+
71
+ // Consolidated "Actions" jump menu (ActiveAdminPrism::Configuration
72
+ // #action_items_dropdown/#action_items_dropdown_threshold) — see
73
+ // js-src/prism.js, which builds a real Select2-enhanced <select> here and
74
+ // moves the actual action_item elements (their <a>/<button> still fully
75
+ // live, just never shown) into .prism-action-items-holding, clicking the
76
+ // right one when an option is picked. The visible widget is entirely
77
+ // Select2's own generated markup, so none of the button-primary styling
78
+ // above ever reaches it — this only needs to size/align the wrapper
79
+ // itself, not fight any inherited button look.
80
+ .prism-action-items-dropdown {
81
+ display: inline-block;
82
+
83
+ .select2-container {
84
+ min-width: 220px;
85
+ }
86
+ }
87
+
88
+ .prism-action-items-holding {
89
+ display: none;
90
+ }
91
+
92
+ // Consolidated "Sidebar Actions" jump menu (every collapsible sidebar
93
+ // section besides Filters — see scss-src/_panels.scss and
94
+ // lib/active_admin/views/filters_sidebar.rb). Every link/button inside
95
+ // those sections' content is flattened into one option here, same
96
+ // click-and-reset flow as .prism-action-items-dropdown above — the two
97
+ // are styled identically (see scss-src/_select2.scss) since they're the
98
+ // same kind of "jump menu", not a persistent value.
99
+ .prism-sidebar-panel-select-wrapper {
100
+ display: inline-block;
101
+
102
+ .select2-container {
103
+ min-width: 220px;
104
+ }
105
+ }
106
+
107
+ // Same technique as .prism-action-items-holding above: the real
108
+ // links/buttons stay attached to the document (just hidden), rather than
109
+ // being fully removed, so Rails UJS's document-level click delegation
110
+ // (data-method/data-confirm) still reaches them when the dropdown clicks
111
+ // one on the option's behalf.
112
+ .prism-sidebar-panel-holding {
113
+ display: none;
114
+ }
115
+
46
116
  // Secondary / lower-emphasis buttons: batch actions trigger, pagination,
47
117
  // "clear filters", "cancel" form link.
48
118
  .dropdown_menu .dropdown_menu_button,
@@ -53,12 +53,12 @@
53
53
  }
54
54
 
55
55
  // Reflow #main_content (the table/grid) and #sidebar's own width to match
56
- // whether the collapsible Filters panel is expanded or not driven by a
57
- // "prism-filters-open" class prism.js toggles on <body> in lockstep with
58
- // #filters_sidebar_section's own ".open" (a body class is necessary since
59
- // #main_content is a *sibling* of #sidebar, not a descendant CSS can't
60
- // reach across siblings any other way). Both of ActiveAdmin's own rules
61
- // here are triple-ID-scoped / double-ID-scoped
56
+ // whether ANY collapsible sidebar section is currently expanded —
57
+ // ":has(.prism-collapsible-panel.open)" is what actually drives this
58
+ // (every section collapsed = squeeze to the icon gutter; any one of them
59
+ // open = back to full width), so #sidebar only needs to squeeze while
60
+ // every one of its sections agrees to collapse. Both of ActiveAdmin's own
61
+ // rules here are triple-ID-scoped / double-ID-scoped
62
62
  // (structure/_main_structure.scss), so matching that path (rather than a
63
63
  // flatter selector) is what lets this reliably win regardless of load order.
64
64
  $prism-filters-collapsed-gutter: 72px !default;
@@ -69,7 +69,7 @@ $prism-filters-collapsed-gutter: 72px !default;
69
69
  width 280ms cubic-bezier(0.4, 0, 0.2, 1), margin-left 280ms cubic-bezier(0.4, 0, 0.2, 1);
70
70
  }
71
71
 
72
- body:has(.prism-collapsible-panel):not(.prism-filters-open) {
72
+ body:has(.prism-collapsible-panel):not(:has(.prism-collapsible-panel.open)) {
73
73
  #active_admin_content:not(.without_sidebar) #main_content_wrapper #main_content {
74
74
  margin-right: $prism-filters-collapsed-gutter;
75
75
  }
@@ -80,14 +80,17 @@ body:has(.prism-collapsible-panel):not(.prism-filters-open) {
80
80
  }
81
81
  }
82
82
 
83
- // "Filters" sidebar panel — collapses to a single icon button by default
83
+ // Every sidebar section — collapses to a single icon button by default
84
84
  // (ActiveAdminPrism::Configuration#collapsible_filters), expanding to
85
- // the full form on click (see prism.js's filters-collapse handler, and
86
- // lib/active_admin/views/filters_sidebar.rb for the icon + label-span
87
- // markup this targets). AA gives this specific SidebarSection a stable id
88
- // ("#{name}_sidebar_section" => "filters_sidebar_section"), so this is
89
- // scoped to it alone any other panel (dashboard panels, custom sidebar
90
- // sections a host adds) is untouched.
85
+ // its full content on click (see prism.js's collapsible-panel handler,
86
+ // and lib/active_admin/views/filters_sidebar.rb for the icon + label-span
87
+ // markup this targets, and for how a section picks its own icon). Used to
88
+ // be scoped to the "Filters" section alone (AA gives it a stable id,
89
+ // "filters_sidebar_section")generalized to every section, including a
90
+ // host's own custom `sidebar "Title" do ... end` block, since one left
91
+ // out of this treatment still rendered at its full width/height inside
92
+ // the #sidebar column's own squeeze above instead of collapsing alongside
93
+ // it, wrapping or overflowing instead.
91
94
  .prism-collapsible-panel {
92
95
  // Collapsed (default): no floating white card — just the icon button —
93
96
  // so it doesn't read as a stray tiny panel. Restored in full under
@@ -204,6 +207,17 @@ body:has(.prism-collapsible-panel):not(.prism-filters-open) {
204
207
  }
205
208
  }
206
209
 
210
+ // The consolidated "Sidebar Actions" dropdown itself
211
+ // (.prism-sidebar-panel-select-wrapper + .prism-sidebar-panel-holding)
212
+ // lives in scss-src/_buttons.scss, next to .prism-action-items-dropdown —
213
+ // both render in the title bar's shared row (js-src/prism.js's
214
+ // prismTitlebarActionsRow), not here in #sidebar at all. Filters is
215
+ // excluded from this dropdown entirely and keeps living here, unchanged,
216
+ // with its own plain icon button — every *other* collapsible section has
217
+ // its links flattened into that dropdown and the section itself discarded
218
+ // outright (see buildOtherSectionsDropdown in that file), so #sidebar
219
+ // never has to make room for more than Filters alone.
220
+
207
221
  // Backstop for ActiveAdminPrism::Configuration#collapsible_filters =
208
222
  // false: always show the full panel, matching ActiveAdmin's own default
209
223
  // (prism.js also skips attaching the click handler in this case, and
@@ -259,8 +259,18 @@ form fieldset.inputs li.select > select:not(.select2-hidden-accessible) {
259
259
  }
260
260
  }
261
261
 
262
+ // Padding on the *list* (not .select2-dropdown itself, which also
263
+ // contains .select2-search--dropdown above with its own padding) is what
264
+ // keeps a highlighted option's square corners from touching the
265
+ // dropdown's own rounded ones — without it, a highlighted first/last
266
+ // option reads as flush, square-edged chrome rather than an inset row.
267
+ .select2-results__options {
268
+ padding: 4px;
269
+ }
270
+
262
271
  .select2-results__option {
263
272
  padding: 8px 12px;
273
+ border-radius: $prism-radius-sm;
264
274
  color: $prism-text-body;
265
275
 
266
276
  &--highlighted {
@@ -275,6 +285,52 @@ form fieldset.inputs li.select > select:not(.select2-hidden-accessible) {
275
285
  }
276
286
  }
277
287
 
288
+ // Shared look for this theme's own "jump menu" Select2 widgets — the
289
+ // consolidated action_items dropdown (lib/active_admin/views/title_bar.rb
290
+ // / js-src/prism.js, ".prism-action-items-dropdown") and the consolidated
291
+ // non-Filters sidebar section dropdown ("Sidebar Actions" —
292
+ // ".prism-sidebar-panel-select-wrapper"). Both fire a click on a real,
293
+ // hidden link/button and immediately reset rather than holding a
294
+ // persistent value, so they're styled to read as a highlighted
295
+ // button-like menu instead of a plain form field —
296
+ // consistent with each other, which a plain select2 reskin alone doesn't
297
+ // give them (its own text naturally comes out right-aligned here, not
298
+ // centered: "#titlebar_right { text-align: right }" in ActiveAdmin core's
299
+ // own _title_bar.scss is inherited all the way down to
300
+ // .select2-selection__rendered's own text otherwise, and the sidebar
301
+ // dropdown lives in the same inherited-alignment tree via #sidebar).
302
+ .prism-action-items-dropdown,
303
+ .prism-sidebar-panel-select-wrapper {
304
+ .select2-selection--single {
305
+ border-color: rgba($prism-color-primary, 0.4);
306
+ background: $prism-color-primary-light;
307
+ }
308
+
309
+ .select2-container--open .select2-selection--single,
310
+ .select2-container--focus .select2-selection--single {
311
+ border-color: $prism-color-primary;
312
+ }
313
+
314
+ // !important: the base ".select2-container .select2-selection--single
315
+ // .select2-selection__rendered" rule above this one is three classes
316
+ // deep (0,3,0) — more specific than this wrapper-scoped selector
317
+ // (0,2,0) alone, so color/font-weight here would otherwise lose
318
+ // regardless of source order.
319
+ .select2-selection__rendered {
320
+ text-align: center;
321
+ color: $prism-color-primary-dark !important;
322
+ font-weight: 700 !important;
323
+ }
324
+
325
+ .select2-selection__placeholder {
326
+ color: $prism-color-primary-dark !important;
327
+ }
328
+
329
+ .select2-selection__arrow b {
330
+ border-top-color: $prism-color-primary-dark !important;
331
+ }
332
+ }
333
+
278
334
  .select2-results__message {
279
335
  color: $prism-text-muted;
280
336
  padding: 8px 12px;
@@ -1,8 +1,106 @@
1
- // ActiveAdmin wraps the index table + pagination in this container —
2
- // letting it scroll horizontally, rather than the columns visually
3
- // colliding, is the robust fallback on narrow viewports.
4
- .paginated_collection_contents {
1
+ // ActiveAdmin wraps the index table + pagination/download-links footer
2
+ // (`#index_footer`) together in ".paginated_collection_contents", but the
3
+ // table itself (or grid/block index, for `config.index_as :grid`/etc.)
4
+ // renders inside a narrower ".index_content" div nested inside that —
5
+ // a sibling of `#index_footer`, not an ancestor of it (see ActiveAdmin
6
+ // core's own lib/active_admin/views/pages/index.rb#render_index). Scoping
7
+ // the scroll to ".index_content" instead of the outer container means
8
+ // only the table itself scrolls horizontally on a narrow viewport/wide
9
+ // table, not the footer sitting under it, which has no columns to align
10
+ // with anything and would otherwise scroll off out of sync with what
11
+ // it's describing.
12
+ .index_content {
13
+ position: relative;
5
14
  overflow-x: auto;
15
+
16
+ // A native scrollbar is the one scroll affordance every visitor
17
+ // already recognizes on sight — thin/auto-hiding trackpad-style
18
+ // scrollbars (this gem's default OS/browser behavior otherwise) don't
19
+ // read that way until actively dragged. Made permanently visible,
20
+ // thick, and brand-colored instead, so its mere presence — not just
21
+ // the shadow/chevron below — already says "this scrolls".
22
+ scrollbar-width: auto;
23
+ scrollbar-color: $prism-color-primary rgba($prism-color-primary, 0.12);
24
+
25
+ &::-webkit-scrollbar {
26
+ height: 12px;
27
+ }
28
+
29
+ &::-webkit-scrollbar-track {
30
+ background: rgba($prism-color-primary, 0.12);
31
+ border-radius: $prism-radius-pill;
32
+ }
33
+
34
+ &::-webkit-scrollbar-thumb {
35
+ background: $prism-color-primary;
36
+ border-radius: $prism-radius-pill;
37
+ border: 2px solid $prism-bg-card;
38
+
39
+ &:hover {
40
+ background: $prism-color-primary-dark;
41
+ }
42
+ }
43
+
44
+ // Scroll-shadow-plus-chevron affordance (js-src/prism.js toggles the
45
+ // two classes below based on actual scroll position) — a table simply
46
+ // cut off flush at this wrapper's edge otherwise looks identical
47
+ // whether there's more hidden past it or not. A colored (not just
48
+ // gray) gradient, a bold bouncing chevron (`content` renders it as
49
+ // real generated text, no extra markup needed), and a
50
+ // `drop-shadow` for legibility over any cell content are deliberately
51
+ // more assertive than a plain subtle fade — paired with the scrollbar
52
+ // above, this is meant to be unmissable, not merely present if you
53
+ // look closely.
54
+ &::before,
55
+ &::after {
56
+ position: absolute;
57
+ top: 0;
58
+ bottom: 12px;
59
+ width: 56px;
60
+ display: flex;
61
+ align-items: center;
62
+ pointer-events: none;
63
+ opacity: 0;
64
+ transition: opacity $prism-transition-fast;
65
+ z-index: 2;
66
+ font-size: 26px;
67
+ font-weight: 700;
68
+ color: $prism-color-primary-dark;
69
+ filter: drop-shadow(0 1px 1px rgba(255, 255, 255, 0.9));
70
+ }
71
+
72
+ &::before {
73
+ content: "‹";
74
+ left: 0;
75
+ justify-content: flex-start;
76
+ padding-left: 4px;
77
+ background: linear-gradient(to right, rgba($prism-color-primary, 0.22), transparent);
78
+ animation: prism-scroll-hint-left 1.4s ease-in-out infinite;
79
+ }
80
+
81
+ &::after {
82
+ content: "›";
83
+ right: 0;
84
+ justify-content: flex-end;
85
+ padding-right: 4px;
86
+ background: linear-gradient(to left, rgba($prism-color-primary, 0.22), transparent);
87
+ animation: prism-scroll-hint-right 1.4s ease-in-out infinite;
88
+ }
89
+
90
+ &.prism-scroll-left::before,
91
+ &.prism-scroll-right::after {
92
+ opacity: 1;
93
+ }
94
+ }
95
+
96
+ @keyframes prism-scroll-hint-left {
97
+ 0%, 100% { transform: translateX(0); }
98
+ 50% { transform: translateX(-6px); }
99
+ }
100
+
101
+ @keyframes prism-scroll-hint-right {
102
+ 0%, 100% { transform: translateX(0); }
103
+ 50% { transform: translateX(6px); }
6
104
  }
7
105
 
8
106
  table.index_table {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_admin_prism
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ram Laxman Yadav
@@ -102,11 +102,13 @@ files:
102
102
  - app/views/active_admin/devise/unlocks/new.html.erb
103
103
  - app/views/layouts/active_admin_logged_out.html.erb
104
104
  - js-src/prism.js
105
+ - lib/active_admin/views/action_items.rb
105
106
  - lib/active_admin/views/active_filters_bar.rb
106
107
  - lib/active_admin/views/filters_sidebar.rb
107
108
  - lib/active_admin/views/flash_messages.rb
108
109
  - lib/active_admin/views/index_table_actions.rb
109
110
  - lib/active_admin/views/prism_sidebar.rb
111
+ - lib/active_admin/views/title_bar.rb
110
112
  - lib/active_admin_prism.rb
111
113
  - lib/active_admin_prism/configuration.rb
112
114
  - lib/active_admin_prism/engine.rb