Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Toolbar button component #2800

Merged
merged 1 commit into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions app/components/alchemy/admin/toolbar_button.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
module Alchemy
module Admin
# Renders a toolbar button for the Alchemy toolbar
#
# == Example
#
# <%= render Alchemy::Admin::ToolbarButton.new(
# url: new_resource_path,
# icon: :plus,
# label: 'Create Resource',
# hotkey: 'alt+n',
# dialog_options: {
# title: 'Create Resource',
# size: "430x400"
# },
# if_permitted_to: [:create, resource_model]
# ) %>
#
# @param [String] :url
# Url for link.
# @param [String] :icon
# Icon name. See https://remixicon.com for available icons.
# @param [String] :label
# Text for button tooltip.
# @param [String] :hotkey
# Keyboard shortcut for this button. I.E +alt-n+
# @param [Hash] :dialog_options
# Overlay options. See link_to_dialog helper.
# @param [Array] :if_permitted_to ([:action, :controller])
# Check permission for button. Exactly how you defined the permission in your +authorization_rules.rb+. Defaults to controller and action from button url.
# @param [Boolean] :skip_permission_check (false)
# Skip the permission check. NOT RECOMMENDED!
#
class ToolbarButton < ViewComponent::Base
erb_template <<-ERB
<div class="toolbar_button">
<sl-tooltip content="<%= label %>" placement="<%= tooltip_placement %>">
<%= link_to(render_icon(icon, style: icon_style), url, {
class: css_classes,
"data-dialog-options" => dialog ? dialog_options.to_json : nil,
"data-alchemy-hotkey" => hotkey,
:is => dialog ? "alchemy-dialog-link" : nil
}.merge(link_options)) %>
</sl-tooltip>
</div>
ERB

delegate :can?, :link_to, :link_to_dialog, :render_icon, to: :helpers

attr_reader :url,
:icon,
:label,
:hotkey,
:dialog,
:dialog_options,
:skip_permission_check,
:if_permitted_to,
:active,
:link_options,
:icon_style,
:tooltip_placement

def initialize(
url:,
icon:,
label:,
hotkey: nil,
title: nil,
dialog: true,
dialog_options: {},
skip_permission_check: false,
if_permitted_to: [],
active: false,
link_options: {},
icon_style: "line",
tooltip_placement: "top-start"
)
@url = url
@icon = icon
@label = label
@hotkey = hotkey
@dialog = dialog
@dialog_options = dialog_options
@skip_permission_check = skip_permission_check
@if_permitted_to = if_permitted_to
@active = active
@link_options = link_options
@icon_style = icon_style
@tooltip_placement = tooltip_placement
end

def render?
skip_permission_check || can?(*permission_options)
end

private

def css_classes = ["icon_button", active && "active"].compact

def permission_options = if_permitted_to.presence || permissions_from_url

def permissions_from_url
action_controller = url.gsub(/\A\//, "").split("/")
[
action_controller.last.to_sym,
action_controller[0..action_controller.length - 2].join("_").to_sym
]
end
end
end
end
36 changes: 1 addition & 35 deletions app/helpers/alchemy/admin/base_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -268,23 +268,7 @@ def render_alchemy_title
# Shows the please wait dialog while loading. Only for buttons not opening an dialog.
#
def toolbar_button(options = {})
options = {
dialog: true,
skip_permission_check: false,
active: false,
link_options: {},
dialog_options: {},
loading_indicator: false
}.merge(options.symbolize_keys)
button = render(
"alchemy/admin/partials/toolbar_button",
options: options
)
if options[:skip_permission_check] || can?(*permission_from_options(options))
button
else
""
end
render Alchemy::Admin::ToolbarButton.new(**options)
end

# Renders a textfield ready to display a datepicker
Expand Down Expand Up @@ -396,24 +380,6 @@ def page_layout_missing_warning
Alchemy.t(:page_definition_missing)
)
end

private

def permission_from_options(options)
if options[:if_permitted_to].blank?
options[:if_permitted_to] = permission_array_from_url(options)
else
options[:if_permitted_to]
end
end

def permission_array_from_url(options)
action_controller = options[:url].gsub(/\A\//, "").split("/")
[
action_controller.last.to_sym,
action_controller[0..action_controller.length - 2].join("_").to_sym
]
end
end
end
end
29 changes: 0 additions & 29 deletions app/views/alchemy/admin/partials/_toolbar_button.html.erb

This file was deleted.

168 changes: 168 additions & 0 deletions spec/components/alchemy/admin/toolbar_button_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
require "rails_helper"

RSpec.describe Alchemy::Admin::ToolbarButton, type: :component do
before do
allow_any_instance_of(described_class).to receive(:render_icon) do |component|
Alchemy::Admin::Icon.new(component.icon, style: component.icon_style).call
end
end

let(:component) do
described_class.new(url: admin_dashboard_path, icon: "info", label: "Show Info")
end

context "with permission" do
before { expect(component).to receive(:can?) { true } }

it "renders a toolbar button" do
render_inline component
expect(page).to have_css %(sl-tooltip a.icon_button[href="#{admin_dashboard_path}"])
end

context "with dialog option set to false" do
let(:component) do
described_class.new(
url: admin_dashboard_path,
icon: "info",
label: "Show Info",
dialog: false
)
end

it "renders a normal link" do
render_inline component
expect(page).to have_css(%(a[href="#{admin_dashboard_path}"]))
expect(page).not_to have_css("[data-dialog-options]")
end
end

context "with dialog_options set" do
let(:component) do
described_class.new(
url: admin_dashboard_path,
icon: "info",
label: "Show Info",
dialog_options: {
title: "Info",
size: "300x200"
}
)
end

it "passes them to the link" do
render_inline component
expect(page).to have_css(%(a[data-dialog-options='{"title":"Info","size":"300x200"}']))
end
end

context "with hotkey set" do
let(:component) do
described_class.new(
url: admin_dashboard_path,
icon: "info",
label: "Show Info",
hotkey: "alt+i"
)
end

it "passes it to the link" do
render_inline component
expect(page).to have_css('a[data-alchemy-hotkey="alt+i"]')
end
end

context "with icon_style set" do
let(:component) do
described_class.new(
url: admin_dashboard_path,
icon: "info",
label: "Show Info",
icon_style: "fill"
)
end

it "passes it to the icon" do
render_inline component
expect(page).to have_css('alchemy-icon[icon-style="fill"]')
end
end

context "with tooltip_placement set" do
let(:component) do
described_class.new(
url: admin_dashboard_path,
icon: "info",
label: "Show Info",
tooltip_placement: "bottom-center"
)
end

it "passes it to the icon" do
render_inline component
expect(page).to have_css('sl-tooltip[placement="bottom-center"]')
end
end

context "with active set to true" do
let(:component) do
described_class.new(
url: admin_dashboard_path,
icon: "info",
label: "Show Info",
active: true
)
end

it "button has active class" do
render_inline component
expect(page).to have_css("a.active")
end
end
end

context "without permission" do
before { expect(component).to receive(:can?) { false } }

it "returns empty string" do
render_inline component
expect(page.native.inner_html).to be_empty
end
end

context "with disabled permission check" do
before { expect(component).not_to receive(:can?) { false } }

let(:component) do
described_class.new(
url: admin_dashboard_path,
icon: "info",
label: "Show Info",
skip_permission_check: true
)
end

it "renders a toolbar button" do
render_inline component
expect(page).to have_css %(sl-tooltip .icon_button[href="#{admin_dashboard_path}"])
end
end

context "with empty permission option" do
before { expect(component).to receive(:can?) { true } }

let(:component) do
described_class.new(
url: admin_dashboard_path,
icon: "info",
label: "Show Info",
if_permitted_to: ""
)
end

it "returns reads the permission from url" do
expect(component).to receive(:permissions_from_url)
render_inline component
expect(page).to have_css %(sl-tooltip .icon_button[href="#{admin_dashboard_path}"])
end
end
end
Loading