diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..95633f4
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,91 @@
+name: CI
+on:
+ pull_request:
+ push:
+ branches: [master]
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+jobs:
+ test:
+ name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA ${{ matrix.activeadmin }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby: ['3.2', '3.3', '3.4']
+ rails: ['7.1.0', '7.2.0', '8.0.0']
+ activeadmin: ['3.2.0', '3.3.0', '3.4.0', '3.5.0']
+ exclude:
+ - rails: '8.0.0'
+ activeadmin: '3.2.0'
+ env:
+ RAILS: ${{ matrix.rails }}
+ AA: ${{ matrix.activeadmin }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby }}
+ bundler-cache: true
+ - name: Run tests
+ run: bundle exec rspec spec
+
+ - name: Generate badge.json
+ if: matrix.ruby == '3.4' && matrix.rails == '8.0.0' && matrix.activeadmin == '3.5.0'
+ run: |
+ LAST_RUN="coverage/.last_run.json"
+ if [ ! -f "$LAST_RUN" ]; then
+ mkdir -p badge
+ echo '{"schemaVersion":1,"label":"coverage","message":"unknown","color":"lightgrey"}' > badge/badge.json
+ exit 0
+ fi
+ PERCENT=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line').round(1)")
+ PERCENT_NUM=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line')")
+ if ruby -e "exit(($PERCENT_NUM >= 90) ? 0 : 1)"; then COLOR="brightgreen"
+ elif ruby -e "exit(($PERCENT_NUM >= 75) ? 0 : 1)"; then COLOR="green"
+ elif ruby -e "exit(($PERCENT_NUM >= 60) ? 0 : 1)"; then COLOR="yellow"
+ else COLOR="red"; fi
+ mkdir -p badge
+ echo "{\"schemaVersion\":1,\"label\":\"coverage\",\"message\":\"${PERCENT}%\",\"color\":\"${COLOR}\"}" > badge/badge.json
+
+ - name: Upload badge artifact
+ if: matrix.ruby == '3.4' && matrix.rails == '8.0.0' && matrix.activeadmin == '3.5.0'
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-badge
+ path: badge
+
+ deploy-coverage:
+ needs: test
+ if: github.ref == 'refs/heads/master' && github.event_name == 'push'
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: gh-pages
+
+ - name: Download coverage badge
+ uses: actions/download-artifact@v4
+ with:
+ name: coverage-badge
+ path: .
+
+ - name: Setup Pages
+ uses: actions/configure-pages@v5
+
+ - name: Upload Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: .
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f8a9240
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+/.bundle/
+/Gemfile.lock
+/pkg/
+/tmp/
+spec/rails/rails-*
+/coverage
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 210ac46..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-sudo: required
-
-language: ruby
-
-addons:
- chrome: stable
-
-before_install:
- - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
- - gem install bundler -v '< 2'
-
-script: bundle exec rspec
-
-env:
- matrix:
- - RAILS=5.1.6 AA=1.1.0
- - RAILS=5.2.1 AA=1.4.0
- - RAILS=5.2.2 AA=2.8.0
- - RAILS=6.0.0 AA=2.8.0
-
-rvm:
- - 2.5
- - 2.6
diff --git a/Gemfile b/Gemfile
index d98c914..165e561 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,18 +1,20 @@
source 'https://rubygems.org'
-
-# Specify your gem's dependencies in activeadmin_scoped_collection_actions.gemspec
gemspec
-group :test do
- gem 'rails', "~> #{ENV['RAILS'] || '5.2.1'}"
- gem 'activeadmin', "~> #{ENV['AA'] || '1.3.1'}"
+default_rails_version = '7.1.0'
+default_activeadmin_version = '3.2.0'
+gem 'rails', "~> #{ENV['RAILS'] || default_rails_version}"
+gem 'activeadmin', "~> #{ENV['AA'] || default_activeadmin_version}"
+gem 'sprockets-rails'
+gem 'sass-rails'
+
+group :test do
+ gem 'simplecov', require: false
gem 'rspec-rails'
- gem 'sqlite3'
+ gem 'sqlite3', '~> 2.0'
gem 'database_cleaner'
gem 'capybara'
- gem 'selenium-webdriver'
- gem 'chromedriver-helper'
- gem 'byebug'
- gem 'sassc-rails'
+ gem 'cuprite'
+ gem 'webrick', require: false
end
diff --git a/README.md b/README.md
index 700754c..9ccfe6d 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,35 @@
[](https://badge.fury.io/rb/active_admin_sidebar)
[](https://badge.fury.io/js/@activeadmin-plugins%2Factive_admin_sidebar)

+
# ActiveAdmin Sidebar
-Provides ability to manipulate sidebar position for ActiveAdmin (tested with ActiveAdmin ~> 1.0.0)
+Manipulate sidebar position and add collapsible sidebar support for ActiveAdmin 3.x.
## Install
-Add following line to the `Gemfile`
+Add to your `Gemfile`:
```ruby
gem 'active_admin_sidebar'
```
-##### Using assets via Sprockets
-Add following line to the `app/assets/stylesheets/active_admin.css.scss`
+### Using assets via Sprockets
+
+Add to `app/assets/stylesheets/active_admin.scss`:
```scss
- @import "active_admin_sidebar";
+@import "active_admin_sidebar";
```
-If you want to use collapsing feature, add following line
+Add to `app/assets/javascripts/active_admin.js`:
-```javascripts
- //= require active_admin_sidebar
+```javascript
+//= require active_admin_sidebar
```
-to the `app/assets/javascripts/active_admin.js`
-
-##### Using assets via Webpacker (or any other assets bundler) as a NPM module (Yarn package)
-
-Execute:
+### Using assets via NPM
$ npm i @activeadmin-plugins/active_admin_sidebar
@@ -39,81 +37,62 @@ Or
$ yarn add @activeadmin-plugins/active_admin_sidebar
-Or add manually to `package.json`:
-
-```json
-"dependencies": {
- "@activeadmin-plugins/active_admin_sidebar": "2.0.0"
-}
-```
-and execute:
-
- $ yarn
-
-Add the following line into `app/assets/javascripts/active_admin.js`:
+Add to `app/assets/javascripts/active_admin.js`:
```javascript
import '@activeadmin-plugins/active_admin_sidebar';
```
-Add the following line into `app/assets/stylesheets/active_admin.scss`:
+Add to `app/assets/stylesheets/active_admin.scss`:
-```css
+```scss
@import '@activeadmin-plugins/active_admin_sidebar';
```
-# Configuration per resource
+## Configuration per resource
-Changing sidebar position dynamically with before_action
+Change sidebar position with `before_action`:
```ruby
- # app/admin/posts.rb
- ActiveAdmin.register Post do
- before_action :left_sidebar!, only: [:show]
+# app/admin/posts.rb
+ActiveAdmin.register Post do
+ before_action only: [:index] do
+ left_sidebar!
end
+end
- # app/admin/comments.rb
- ActiveAdmin.register Comment do
- before_action :right_sidebar!
+# app/admin/comments.rb
+ActiveAdmin.register Comment do
+ before_action do
+ right_sidebar!
end
+end
```
## Global configuration
-Moving sidebar to the left within all resource. Set configuration in `config/initializers/active_admin.rb`
+Move sidebar to the left for all resources in `config/initializers/active_admin.rb`:
```ruby
- # == Controller before-actions
- #
- # You can add before, after and around actions to all of your resources
- ActiveAdmin.setup do |config|
- config.before_action do
- left_sidebar! if respond_to?(:left_sidebar!)
- end
+ActiveAdmin.setup do |config|
+ config.before_action do
+ left_sidebar! if respond_to?(:left_sidebar!)
end
+end
```
-## Collapsing sidebar
+## Collapsible sidebar
-You can use sidebar collapsing.
-It will add "hide/show" button. Shown/Hidden state is persisted across all pages.
+Add a toggle button to collapse/expand the sidebar. State is persisted per-resource across page navigations.
```ruby
- left_sidebar!(collapsed: true)
-```
+# Collapsible sidebar (starts expanded)
+left_sidebar!(collapsible: true)
+right_sidebar!(collapsible: true)
-You can override button color according to your color theme. For example:
-
-```scss
- body.active_admin {
- #active_admin_content.left_sidebar, #active_admin_content.collapsed_sidebar {
- .collapse_btn, .uncollapse_btn {
- background-color: #767270;
- }
- }
- }
+# Collapsible sidebar (starts collapsed)
+left_sidebar!(collapsible: true, start_collapsed: true)
+right_sidebar!(collapsible: true, start_collapsed: true)
```
-Example
-
-
+
diff --git a/Rakefile b/Rakefile
index a5cc536..dedca81 100644
--- a/Rakefile
+++ b/Rakefile
@@ -2,6 +2,4 @@ require "bundler"
require 'rake'
Bundler.setup
Bundler::GemHelper.install_tasks
-
-# Import all our rake tasks
FileList['tasks/**/*.rake'].each { |task| import task }
diff --git a/active_admin_sidebar.gemspec b/active_admin_sidebar.gemspec
index 7db8dd3..353c8f5 100644
--- a/active_admin_sidebar.gemspec
+++ b/active_admin_sidebar.gemspec
@@ -1,4 +1,3 @@
-# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "active_admin_sidebar/version"
@@ -7,15 +6,17 @@ Gem::Specification.new do |s|
s.version = ActiveAdminSidebar::VERSION
s.authors = ["Igor"]
s.email = ["fedoronchuk@gmail.com"]
- s.homepage = "https://github.com/Fivell/active_admin_sidebar"
+ s.homepage = "https://github.com/activeadmin-plugins/active_admin_sidebar"
s.summary = %q{active_admin_sidebar gem}
s.description = %q{extension for activeadmin gem to manage sidebar}
+ s.license = "MIT"
- s.add_dependency "activeadmin"
+ s.required_ruby_version = '>= 3.1.0'
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
+ s.add_dependency "activeadmin", ">= 3.0", "< 4.0"
end
diff --git a/app/assets/javascripts/active_admin_sidebar.js b/app/assets/javascripts/active_admin_sidebar.js
index 070d9f3..fc66fcd 100644
--- a/app/assets/javascripts/active_admin_sidebar.js
+++ b/app/assets/javascripts/active_admin_sidebar.js
@@ -1,29 +1,29 @@
(function() {
$(document).ready(function() {
- var $aa_content, set_collapsed_sidebar;
if ($('body').hasClass('index') && ($('#active_admin_content').hasClass('collapsible_sidebar'))) {
- $aa_content = $('#active_admin_content');
- $aa_content.find('.sidebar_section:first>h3').append('');
- $aa_content.prepend('');
- set_collapsed_sidebar = function(value) {
+ var $aa_content = $('#active_admin_content');
+ var $sidebar = $aa_content.find('#sidebar');
+
+ var $toggleBtn = $('');
+ $sidebar.prepend($toggleBtn);
+
+ var set_collapsed_sidebar = function(value) {
return $.getJSON(this.href, {
collapsed_sidebar: value
});
};
- return $aa_content.on('click', '.collapse_btn, .uncollapse_btn', function(e) {
+
+ $toggleBtn.on('click', function(e) {
if (!$aa_content.hasClass('collapsed_sidebar')) {
set_collapsed_sidebar(true);
- $aa_content.removeClass('left_sidebar');
$aa_content.addClass('collapsed_sidebar');
- return $aa_content.trigger('collapsible_sidebar:collapsed');
+ $aa_content.trigger('collapsible_sidebar:collapsed');
} else {
set_collapsed_sidebar(false);
$aa_content.removeClass('collapsed_sidebar');
- $aa_content.addClass('left_sidebar');
- return $aa_content.trigger('collapsible_sidebar:uncollapsed');
+ $aa_content.trigger('collapsible_sidebar:uncollapsed');
}
});
}
});
-
}).call(this);
diff --git a/app/assets/stylesheets/active_admin_sidebar.scss b/app/assets/stylesheets/active_admin_sidebar.scss
index 112a1e8..878bb60 100644
--- a/app/assets/stylesheets/active_admin_sidebar.scss
+++ b/app/assets/stylesheets/active_admin_sidebar.scss
@@ -1,7 +1,11 @@
-@import "active_admin_sidebar_pure_icons";
+$toggle-btn-width: 12px;
+$toggle-btn-height: 48px;
+// === Left sidebar layout ===
+// Moves sidebar before main content in the DOM, so we need to
+// reverse the default AA float layout.
body.active_admin {
- #active_admin_content.left_sidebar, #active_admin_content.collapsed_sidebar {
+ #active_admin_content.left_sidebar {
#sidebar {
display: block;
margin-left: 0;
@@ -25,67 +29,119 @@ body.active_admin {
float: inherit;
margin-left: 298px;
width: auto;
+
#main_content {
float: inherit;
margin: 0;
- .tabs .comments {
- .active_admin_comment {
- clear: none;
- }
+
+ .tabs .comments .active_admin_comment {
+ clear: none;
}
}
-
}
.table_tools:after {
clear: none;
padding-bottom: 16px;
}
+ }
+}
- .collapse_btn, .uncollapse_btn {
- background-color: #767270;
- border-radius: 5px;
- color: #ffffff;
- cursor: pointer;
- }
+// === Collapsible sidebar: toggle button ===
+body.active_admin #active_admin_content.collapsible_sidebar {
+ #sidebar {
+ position: relative;
+ }
- .collapse_btn {
- clear: both;
- display: block;
- float: right;
+ .sidebar_toggle_btn {
+ width: $toggle-btn-width;
+ height: $toggle-btn-height;
+ background-color: #767270;
+ border-radius: 3px;
+ cursor: pointer;
+ z-index: 10;
+ position: absolute;
+ top: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: #5a5857;
}
- .uncollapse_btn {
- display: none;
- margin-top: 5px;
- position: absolute;
+ .chevron {
+ width: 0;
+ height: 0;
+ border-top: 4px solid transparent;
+ border-bottom: 4px solid transparent;
}
+ }
+
+ // Left sidebar expanded: button on right edge, chevron points left (collapse)
+ &.left_sidebar .sidebar_toggle_btn {
+ right: -$toggle-btn-width;
+ .chevron { border-right: 5px solid #fff; border-left: none; }
+ }
+
+ // Left sidebar collapsed: chevron points right (expand)
+ &.left_sidebar.collapsed_sidebar .sidebar_toggle_btn {
+ .chevron { border-left: 5px solid #fff; border-right: none; }
+ }
+
+ // Right sidebar expanded: button on left edge, chevron points right (collapse)
+ &:not(.left_sidebar) .sidebar_toggle_btn {
+ left: -$toggle-btn-width;
+ .chevron { border-left: 5px solid #fff; border-right: none; }
+ }
+ // Right sidebar collapsed: chevron points left (expand)
+ &:not(.left_sidebar).collapsed_sidebar .sidebar_toggle_btn {
+ .chevron { border-right: 5px solid #fff; border-left: none; }
}
}
-body.active_admin #active_admin_content.collapsed_sidebar {
+// === Collapsed state: left sidebar ===
+body.active_admin #active_admin_content.collapsible_sidebar.collapsed_sidebar.left_sidebar {
+ #sidebar {
+ width: 0;
+ overflow: visible;
+
+ .sidebar_section {
+ display: none;
+ }
+ }
#main_content_wrapper {
- margin-left: 30px;
+ margin-left: $toggle-btn-width + 8px;
}
+}
+// === Collapsed state: right sidebar (default AA position) ===
+body.active_admin #active_admin_content.collapsible_sidebar.collapsed_sidebar:not(.left_sidebar) {
#sidebar {
- display: none;
- }
+ width: 0;
+ margin-left: 0;
+ overflow: visible;
- .uncollapse_btn {
- display: block;
+ .sidebar_section {
+ display: none;
+ }
}
+ #main_content_wrapper #main_content {
+ margin-right: $toggle-btn-width + 8px;
+ }
}
-body.active_admin.index #active_admin_content.with_sidebar.collapsible_sidebar {
+// === Left sidebar expanded + collapsible: fix margin ===
+body.active_admin.index #active_admin_content.with_sidebar.left_sidebar.collapsible_sidebar {
#main_content_wrapper #main_content {
margin-right: 0;
}
}
+// === Comments overflow fix ===
.with_sidebar .comments .active_admin_comment {
overflow: auto;
}
diff --git a/app/assets/stylesheets/active_admin_sidebar_pure_icons.scss b/app/assets/stylesheets/active_admin_sidebar_pure_icons.scss
deleted file mode 100644
index ed4e085..0000000
--- a/app/assets/stylesheets/active_admin_sidebar_pure_icons.scss
+++ /dev/null
@@ -1,66 +0,0 @@
-/* .icono-caret-left */
-.icono-caret-right, .icono-caret-left {
- height: 19px;
- width: 19px;
-}
-
-.icono-caret-right:before,
-.icono-caret-right:after,
-.icono-caret-left:before,
-.icono-caret-left:after {
- bottom: 1px;
- box-shadow: inset 0 0 0 32px;
- height: 2px;
- margin: auto 0;
- position: absolute;
- right: 6px;
- transform-origin: right;
- width: 8px;
-}
-
-.icono-caret-right:before, .icono-caret-left:before {
- top: 2px;
- -moz-transform: rotate(45deg);
- -ms-transform: rotate(45deg);
- -o-transform: rotate(45deg);
- -webkit-transform: rotate(45deg);
- transform: rotate(45deg);
-}
-
-.icono-caret-right:after, .icono-caret-left:after {
- top: 0;
- -moz-transform: rotate(-45deg);
- -ms-transform: rotate(-45deg);
- -o-transform: rotate(-45deg);
- -webkit-transform: rotate(-45deg);
- transform: rotate(-45deg);
-}
-
-.icono-caret-left {
- -moz-transform: rotate(180deg);
- -ms-transform: rotate(180deg);
- -o-transform: rotate(180deg);
- -webkit-transform: rotate(180deg);
- transform: rotate(180deg);
-}
-
-[class*="icono-"] {
- direction: ltr;
- display: inline-block;
- font-style: normal;
- position: relative;
- text-align: left;
- text-indent: -9999px;
- vertical-align: middle;
-}
-
-[class*="icono-"]:before,
-[class*="icono-"]:after {
- content: "";
- pointer-events: none;
-}
-
-[class*="icono-"],
-[class*="icono-"] * {
- box-sizing: border-box;
-}
diff --git a/lib/active_admin_sidebar/activeadmin_views_pages_base.rb b/lib/active_admin_sidebar/activeadmin_views_pages_base.rb
index 6ccf2d9..7d0f337 100644
--- a/lib/active_admin_sidebar/activeadmin_views_pages_base.rb
+++ b/lib/active_admin_sidebar/activeadmin_views_pages_base.rb
@@ -1,6 +1,9 @@
module ActiveAdminSidebar::ActiveAdminViewsPagesBase
def build_page_content
+ # When no sidebar options are set, fall back to vanilla ActiveAdmin behavior
+ return super unless sidebar_options_present?
+
build_flash_messages
div id: "active_admin_content", class: main_content_classes do
build_sidebar unless skip_sidebar? || right_sidebar?
@@ -17,12 +20,16 @@ def build_sidebar
end
end
+ def sidebar_options_present?
+ assigns[:sidebar_options].present?
+ end
+
def left_sidebar?
assigns[:sidebar_options].try!(:[], :position) == :left
end
def collapsible_sidebar?
- left_sidebar? && !!assigns[:sidebar_options].try!(:[], :collapsed)
+ !!assigns[:sidebar_options].try!(:[], :collapsible)
end
def sidebar_is_collapsed?
@@ -40,6 +47,7 @@ def main_content_classes
else
classes << "with_sidebar"
classes << "left_sidebar" if left_sidebar?
+ classes << "right_sidebar" if right_sidebar?
if collapsible_sidebar?
classes << "collapsible_sidebar"
classes << "collapsed_sidebar" if sidebar_is_collapsed?
diff --git a/lib/active_admin_sidebar/positions.rb b/lib/active_admin_sidebar/positions.rb
index 3bddb25..fa29fb4 100644
--- a/lib/active_admin_sidebar/positions.rb
+++ b/lib/active_admin_sidebar/positions.rb
@@ -2,26 +2,56 @@ module ActiveAdminSidebar
module Positions
def left_sidebar!(options = {})
@sidebar_options = { position: :left }
- if options.fetch(:collapsed, false)
- collapsed_sidebar
- @sidebar_options.merge!(
- is_collapsed: session[:collapsed_sidebar],
- collapsed: true
- )
- end
+
+ validate_sidebar_options!(options)
+
+ collapsible = options.fetch(:collapsible, false)
+ start_collapsed = options.fetch(:start_collapsed, false)
+
+ apply_collapsible_options(start_collapsed) if collapsible
end
- def right_sidebar!
+ def right_sidebar!(options = {})
@sidebar_options = { position: :right }
+
+ validate_sidebar_options!(options)
+
+ collapsible = options.fetch(:collapsible, false)
+ start_collapsed = options.fetch(:start_collapsed, false)
+
+ apply_collapsible_options(start_collapsed) if collapsible
+ end
+
+ private
+
+ def validate_sidebar_options!(options)
+ if options.key?(:collapsed)
+ raise ArgumentError,
+ "The :collapsed option has been removed in v3. " \
+ "Use `collapsible: true, start_collapsed: true` instead."
+ end
+ end
+
+ def apply_collapsible_options(start_collapsed)
+ handle_sidebar_toggle_request
+ toggled = (session[:aas_toggled] || []).include?(controller_name)
+ is_collapsed = toggled ? !start_collapsed : start_collapsed
+ @sidebar_options.merge!(
+ collapsible: true,
+ is_collapsed: is_collapsed
+ )
end
- def collapsed_sidebar
- if request.xhr?
- if params[:collapsed_sidebar].present?
- collapsed = params[:collapsed_sidebar].to_s == 'true'
- session[:collapsed_sidebar] = collapsed
- render json: { collapsed_sidebar: collapsed } and return
+ def handle_sidebar_toggle_request
+ if request.xhr? && params[:collapsed_sidebar].present?
+ toggled = session[:aas_toggled] || []
+ if toggled.include?(controller_name)
+ toggled -= [controller_name]
+ else
+ toggled += [controller_name]
end
+ session[:aas_toggled] = toggled
+ render json: { collapsed_sidebar: params[:collapsed_sidebar] } and return
end
end
diff --git a/lib/active_admin_sidebar/version.rb b/lib/active_admin_sidebar/version.rb
index 03a392b..47e21ef 100644
--- a/lib/active_admin_sidebar/version.rb
+++ b/lib/active_admin_sidebar/version.rb
@@ -1,3 +1,3 @@
module ActiveAdminSidebar
- VERSION = '2.0.0'.freeze
+ VERSION = '3.0.0'.freeze
end
diff --git a/package.json b/package.json
index 4f6115c..566b5e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@activeadmin-plugins/active_admin_sidebar",
- "version": "2.0.0",
+ "version": "3.0.0",
"description": "Extension for ActiveAdmin gem to manage sidebar",
"main": "src/active_admin_sidebar.js",
"style": "src/active_admin_sidebar.scss",
diff --git a/screen/sidebar.jpg b/screen/sidebar.jpg
deleted file mode 100644
index cd96484..0000000
Binary files a/screen/sidebar.jpg and /dev/null differ
diff --git a/spec/sidebars_spec.rb b/spec/sidebars_spec.rb
index e16f297..203884e 100644
--- a/spec/sidebars_spec.rb
+++ b/spec/sidebars_spec.rb
@@ -5,49 +5,184 @@
before do
Author.create!(name: 'John', last_name: 'Doe')
Author.create!(name: 'Jane', last_name: 'Roe')
- add_author_resource
- add_post_resource
end
- context 'left-sidebar with default settings' do
+ context 'left-sidebar with collapsible: true (starts expanded)' do
before do
+ add_author_resource
+ add_post_resource
visit '/admin/authors'
end
- it 'has left-sidebar with colapse-button' do
+ it 'has left-sidebar with toggle button' do
expect(page).to have_css('#filters_sidebar_section')
- expect(page).to have_css('#filters_sidebar_section .collapse_btn.icono-caret-left')
+ expect(page).to have_css('.sidebar_toggle_btn')
expect(page).to have_css('#active_admin_content.with_sidebar.left_sidebar.collapsible_sidebar')
end
- context 'when click on Collapse' do
+ it 'sidebar starts expanded' do
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+ end
+
+ context 'when click on toggle to collapse' do
before do
- page.find('#filters_sidebar_section .collapse_btn').click
+ page.find('.sidebar_toggle_btn').click
end
it "sidebar is hidden, and save it's state after going to another page" do
- expect(page).to have_css('#sidebar', visible: :hidden)
+ expect(page).to have_css('#active_admin_content.collapsed_sidebar')
# Posts page is configured as: "before_action :skip_sidebar!"
visit '/admin/posts'
- # sidebar does not exists at all
+ # sidebar does not exist at all
expect(page).to have_css('#page_title', text: 'Posts')
expect(page).not_to have_css('#sidebar', visible: :all)
visit '/admin/authors'
- # sidebar is hidden
+ # sidebar is collapsed
expect(page).to have_css('#page_title', text: 'Authors')
- expect(page).to have_css('#sidebar', visible: :hidden)
+ expect(page).to have_css('#active_admin_content.collapsed_sidebar')
- page.find('.uncollapse_btn').click
+ page.find('.sidebar_toggle_btn').click
# sidebar is visible
- expect(page).to have_css('#sidebar', visible: :visible)
- expect(page).to have_css('.collapse_btn')
- expect(page).not_to have_css('.uncollapse_btn')
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
end
end
end
+ context 'left-sidebar with collapsible: true, start_collapsed: true' do
+ before do
+ add_author_resource_with_start_collapsed
+ add_post_resource
+ visit '/admin/authors'
+ end
+
+ it 'has collapsible sidebar that starts collapsed' do
+ expect(page).to have_css('#active_admin_content.collapsible_sidebar.collapsed_sidebar')
+ expect(page).to have_css('.sidebar_toggle_btn')
+ end
+
+ it 'can be expanded by clicking toggle button' do
+ page.find('.sidebar_toggle_btn').click
+
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+
+ # session remembers expanded state
+ visit '/admin/authors'
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+ end
+ end
+
+ context 'deprecated collapsed option raises error' do
+ it 'raises ArgumentError for left_sidebar!' do
+ obj = Object.new
+ obj.extend(ActiveAdminSidebar::Positions)
+ expect { obj.left_sidebar!(collapsed: true) }.to raise_error(ArgumentError, /removed in v3/)
+ end
+
+ it 'raises ArgumentError for right_sidebar!' do
+ obj = Object.new
+ obj.extend(ActiveAdminSidebar::Positions)
+ expect { obj.right_sidebar!(collapsed: true) }.to raise_error(ArgumentError, /removed in v3/)
+ end
+ end
+
+ context 'right-sidebar with collapsible: true (starts expanded)' do
+ before do
+ add_author_resource_right_sidebar
+ add_post_resource
+ visit '/admin/authors'
+ end
+
+ it 'has right-sidebar with toggle button' do
+ expect(page).to have_css('#filters_sidebar_section')
+ expect(page).to have_css('.sidebar_toggle_btn')
+ expect(page).to have_css('#active_admin_content.with_sidebar.collapsible_sidebar')
+ expect(page).not_to have_css('#active_admin_content.left_sidebar')
+ end
+
+ it 'sidebar starts expanded' do
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+ end
+
+ context 'when click on toggle to collapse' do
+ before do
+ page.find('.sidebar_toggle_btn').click
+ end
+
+ it 'sidebar is hidden and toggle button is on the right side' do
+ expect(page).to have_css('#active_admin_content.collapsed_sidebar')
+
+ btn_left = page.evaluate_script("document.querySelector('.sidebar_toggle_btn').getBoundingClientRect().left")
+ page_width = page.evaluate_script('document.documentElement.clientWidth')
+ expect(btn_left).to be > (page_width / 2), "toggle button should be on the right side of the page"
+
+ page.find('.sidebar_toggle_btn').click
+
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ end
+ end
+ end
+
+ context 'right-sidebar with collapsible: true, start_collapsed: true' do
+ before do
+ add_author_resource_right_sidebar_start_collapsed
+ add_post_resource
+ visit '/admin/authors'
+ end
+
+ it 'has collapsible right-sidebar that starts collapsed' do
+ expect(page).to have_css('#active_admin_content.collapsible_sidebar.collapsed_sidebar')
+ expect(page).to have_css('.sidebar_toggle_btn')
+ end
+
+ it 'can be expanded by clicking toggle button' do
+ page.find('.sidebar_toggle_btn').click
+
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+
+ # session remembers expanded state
+ visit '/admin/authors'
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+ end
+ end
+
+ context 'per-resource sidebar state isolation' do
+ before do
+ Post.create!(title: 'Test', body: 'Body', author: Author.first)
+ add_author_resource_with_start_collapsed
+ add_post_resource_with_sidebar
+ end
+
+ it 'changing sidebar state on one resource does not affect another' do
+ # Authors starts collapsed (collapsible: true, start_collapsed: true)
+ visit '/admin/authors'
+ expect(page).to have_css('#active_admin_content.collapsed_sidebar')
+
+ # Expand authors
+ page.find('.sidebar_toggle_btn').click
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+
+ # Posts should still be in its default state (expanded, not collapsed)
+ visit '/admin/posts'
+ expect(page).to have_css('#active_admin_content.collapsible_sidebar')
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+
+ # Authors should still be expanded (from earlier toggle)
+ visit '/admin/authors'
+ expect(page).to have_css('#sidebar .sidebar_section', visible: :visible)
+ expect(page).not_to have_css('#active_admin_content.collapsed_sidebar')
+ end
+ end
+
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 37ef3f7..4caf0b4 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,3 +1,8 @@
+require 'simplecov'
+SimpleCov.start do
+ add_filter '/spec/'
+end
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH << File.expand_path('../support', __FILE__)
@@ -6,12 +11,10 @@
Bundler.setup
ENV['RAILS_ENV'] = 'test'
-# Ensure the Active Admin load path is happy
require 'rails'
ENV['RAILS'] = Rails.version
ENV['RAILS_ROOT'] = File.expand_path("../rails/rails-#{ENV['RAILS']}", __FILE__)
-# Create the test app if it doesn't exists
-unless File.exists?(ENV['RAILS_ROOT'])
+unless File.exist?(ENV['RAILS_ROOT'])
system 'rake setup'
end
@@ -19,15 +22,12 @@
require 'active_admin'
ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + "/app/admin"]
require ENV['RAILS_ROOT'] + '/config/environment.rb'
-# Disabling authentication in specs so that we don't have to worry about
-# it allover the place
ActiveAdmin.application.authentication_method = false
ActiveAdmin.application.current_user_method = false
require 'rspec/rails'
require 'capybara/rails'
require 'capybara/rspec'
-require 'selenium-webdriver'
require 'support/admin'
require 'support/capybara'
@@ -36,6 +36,7 @@
config.use_transactional_fixtures = false
config.before(:suite) do
+ ActiveRecord::Migration.maintain_test_schema!
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
@@ -46,5 +47,4 @@
config.after(:each) do
DatabaseCleaner.clean
end
-
end
diff --git a/spec/support/admin.rb b/spec/support/admin.rb
index 987b712..55928c7 100644
--- a/spec/support/admin.rb
+++ b/spec/support/admin.rb
@@ -8,6 +8,44 @@ def add_author_resource(options = {}, &block)
end
+def add_author_resource_with_start_collapsed(options = {}, &block)
+
+ ActiveAdmin.register Author do
+ config.filters = true
+ before_action do
+ left_sidebar!(collapsible: true, start_collapsed: true)
+ end
+ end
+
+ Rails.application.reload_routes!
+
+end
+
+def add_author_resource_right_sidebar(options = {}, &block)
+
+ ActiveAdmin.register Author do
+ config.filters = true
+ before_action do
+ right_sidebar!(collapsible: true)
+ end
+ end
+
+ Rails.application.reload_routes!
+
+end
+
+def add_author_resource_right_sidebar_start_collapsed(options = {}, &block)
+
+ ActiveAdmin.register Author do
+ config.filters = true
+ before_action do
+ right_sidebar!(collapsible: true, start_collapsed: true)
+ end
+ end
+
+ Rails.application.reload_routes!
+
+end
def add_post_resource(options = {}, &block)
@@ -19,3 +57,17 @@ def add_post_resource(options = {}, &block)
Rails.application.reload_routes!
end
+
+def add_post_resource_with_sidebar(options = {}, &block)
+
+ ActiveAdmin.register Post do
+ config.filters = true
+ skip_before_action :skip_sidebar!, raise: false
+ before_action do
+ left_sidebar!(collapsible: true)
+ end
+ end
+
+ Rails.application.reload_routes!
+
+end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 87f4846..e09d7e9 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -1,13 +1,8 @@
-Capybara.configure do |config|
- config.match = :prefer_exact
-end
-
-Capybara.register_driver :selenium_chrome do |app|
- options = Selenium::WebDriver::Chrome::Options.new(
- args: %w[headless disable-gpu no-sandbox]
- )
- Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
-end
+require 'capybara/cuprite'
Capybara.server = :webrick
-Capybara.javascript_driver = :selenium_chrome
+Capybara.register_driver :cuprite do |app|
+ Capybara::Cuprite::Driver.new(app, headless: true, window_size: [1280, 800])
+end
+Capybara.javascript_driver = :cuprite
+Capybara.default_max_wait_time = 5
diff --git a/spec/support/rails_template.rb b/spec/support/rails_template.rb
index 57608f2..cc2ffcb 100644
--- a/spec/support/rails_template.rb
+++ b/spec/support/rails_template.rb
@@ -1,21 +1,34 @@
-# Rails template to build the sample app for specs
+# Ensure Sprockets manifest exists (required by Rails 8+)
+FileUtils.mkdir_p("app/assets/config")
+File.write("app/assets/config/manifest.js",
+ "//= link_directory ../javascripts .js\n//= link_directory ../stylesheets .css\n")
-generate :model, 'author name:string{10}:uniq last_name:string birthday:date'
-generate :model, 'post title:string:uniq body:text author:references'
+generate :model, 'author name:string{10}:uniq last_name:string birthday:date --force'
+generate :model, 'post title:string:uniq body:text author:references --force'
-#Add validation
-inject_into_file "app/models/author.rb", " validates_presence_of :name\n validates_uniqueness_of :last_name\n", after: "Base\n"
+inject_into_file "app/models/author.rb", " validates_presence_of :name\n validates_uniqueness_of :last_name\n", after: "ApplicationRecord\n"
inject_into_file "app/models/post.rb", " validates_presence_of :author\n", after: ":author\n"
-# Configure default_url_options in test environment
-inject_into_file "config/environments/test.rb", " config.action_mailer.default_url_options = { :host => 'example.com' }\n", after: "config.cache_classes = true\n"
-
-# Add our local Active Admin to the load path
-inject_into_file "config/environment.rb",
- "\n$LOAD_PATH.unshift('#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))}')\nrequire \"active_admin\"\n",
- after: "require File.expand_path('../application', __FILE__)"
-
-run "rm Gemfile"
+# Add ransackable_attributes for Ransack 4+
+inject_into_file "app/models/author.rb",
+ " def self.ransackable_attributes(auth_object = nil)\n" \
+ " [\"name\", \"last_name\", \"birthday\", \"created_at\", \"updated_at\"]\n" \
+ " end\n",
+ after: "ApplicationRecord\n"
+
+inject_into_file "app/models/post.rb",
+ " def self.ransackable_attributes(auth_object = nil)\n" \
+ " [\"title\", \"body\", \"author_id\"]\n" \
+ " end\n" \
+ " def self.ransackable_associations(auth_object = nil)\n" \
+ " [\"author\"]\n" \
+ " end\n",
+ after: "ApplicationRecord\n"
+
+# Add our local Active Admin to the load path (Rails 7.1+)
+gsub_file "config/environment.rb",
+ 'require_relative "application"',
+ "require_relative \"application\"\n$LOAD_PATH.unshift('#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))}')\nrequire \"active_admin\"\n"
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
@@ -24,26 +37,19 @@
# Initialize plugin
inject_into_file "config/initializers/active_admin.rb",
- " config.before_action do\n left_sidebar!(collapsed: true) if respond_to?(:left_sidebar!)\n end\n\n",
+ " config.before_action do\n left_sidebar!(collapsible: true) if respond_to?(:left_sidebar!)\n end\n\n",
after: "ActiveAdmin.setup do |config|\n"
inject_into_file "app/assets/stylesheets/active_admin.scss",
"@import \"active_admin_sidebar\";\n",
after: "@import \"active_admin/base\";\n"
-if File.file?("app/assets/javascripts/active_admin.js")
- inject_into_file "app/assets/javascripts/active_admin.js",
- "//= require active_admin_sidebar\n",
- after: "//= require active_admin/base\n"
-else
- inject_into_file "app/assets/javascripts/active_admin.js.coffee",
- "#= require active_admin_sidebar\n",
- after: "#= require active_admin/base\n"
-end
-
-run "rm -r test"
-run "rm -r spec"
+inject_into_file "app/assets/javascripts/active_admin.js",
+ "//= require active_admin_sidebar\n",
+ after: "//= require active_admin/base\n"
+run "rm -rf test"
route "root :to => 'admin/dashboard#index'"
-
rake "db:migrate"
+
+run "rm -f Gemfile Gemfile.lock"
diff --git a/tasks/test.rake b/tasks/test.rake
index a32c625..f46bf45 100644
--- a/tasks/test.rake
+++ b/tasks/test.rake
@@ -1,13 +1,11 @@
desc "Creates a test rails app for the specs to run against"
task :setup do
require 'rails/version'
- system("mkdir spec/rails") unless File.exists?("spec/rails")
rails_new_opts = %w(
--skip-turbolinks
--skip-spring
--skip-bootsnap
- --skip-webpack-install
-m
spec/support/rails_template.rb
)