solidus_admin 0.0.0 → 0.0.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.
- checksums.yaml +5 -5
- data/LICENSE +7 -0
- data/README.md +31 -0
- data/Rakefile +21 -0
- data/app/assets/config/solidus_admin_manifest.js +4 -0
- data/app/assets/images/solidus_admin/.keep +0 -0
- data/app/assets/images/solidus_admin/arrow_down_s_fill_gray_700.svg +3 -0
- data/app/assets/images/solidus_admin/arrow_down_s_fill_red_400.svg +3 -0
- data/app/assets/images/solidus_admin/arrow_right_up_line.svg +5 -0
- data/app/assets/images/solidus_admin/favicon.ico +0 -0
- data/app/assets/images/solidus_admin/remixicon.symbol.svg +11 -0
- data/app/assets/stylesheets/solidus_admin/application.css +3 -0
- data/app/assets/stylesheets/solidus_admin/application.tailwind.css.erb +35 -0
- data/app/components/solidus_admin/base_component.rb +42 -0
- data/app/components/solidus_admin/feedback/component.html.erb +11 -0
- data/app/components/solidus_admin/feedback/component.rb +4 -0
- data/app/components/solidus_admin/feedback/component.yml +5 -0
- data/app/components/solidus_admin/orders/index/component.html.erb +31 -0
- data/app/components/solidus_admin/orders/index/component.rb +118 -0
- data/app/components/solidus_admin/orders/index/component.yml +13 -0
- data/app/components/solidus_admin/products/index/component.html.erb +30 -0
- data/app/components/solidus_admin/products/index/component.rb +126 -0
- data/app/components/solidus_admin/products/index/component.yml +13 -0
- data/app/components/solidus_admin/products/show/component.html.erb +149 -0
- data/app/components/solidus_admin/products/show/component.js +9 -0
- data/app/components/solidus_admin/products/show/component.rb +26 -0
- data/app/components/solidus_admin/products/show/component.yml +17 -0
- data/app/components/solidus_admin/products/status/component.rb +31 -0
- data/app/components/solidus_admin/products/status/component.yml +3 -0
- data/app/components/solidus_admin/sidebar/account_nav/component.html.erb +67 -0
- data/app/components/solidus_admin/sidebar/account_nav/component.rb +15 -0
- data/app/components/solidus_admin/sidebar/account_nav/component.yml +3 -0
- data/app/components/solidus_admin/sidebar/component.html.erb +39 -0
- data/app/components/solidus_admin/sidebar/component.js +14 -0
- data/app/components/solidus_admin/sidebar/component.rb +21 -0
- data/app/components/solidus_admin/sidebar/component.yml +2 -0
- data/app/components/solidus_admin/sidebar/item/component.html.erb +26 -0
- data/app/components/solidus_admin/sidebar/item/component.rb +27 -0
- data/app/components/solidus_admin/skip_link/component.rb +24 -0
- data/app/components/solidus_admin/skip_link/component.yml +2 -0
- data/app/components/solidus_admin/ui/badge/component.rb +34 -0
- data/app/components/solidus_admin/ui/button/component.rb +101 -0
- data/app/components/solidus_admin/ui/forms/checkbox/component.rb +42 -0
- data/app/components/solidus_admin/ui/forms/field/component.html.erb +28 -0
- data/app/components/solidus_admin/ui/forms/field/component.rb +72 -0
- data/app/components/solidus_admin/ui/forms/input/component.js +16 -0
- data/app/components/solidus_admin/ui/forms/input/component.rb +99 -0
- data/app/components/solidus_admin/ui/forms/switch/component.rb +47 -0
- data/app/components/solidus_admin/ui/icon/component.rb +25 -0
- data/app/components/solidus_admin/ui/icon/names.txt +2494 -0
- data/app/components/solidus_admin/ui/panel/component.html.erb +36 -0
- data/app/components/solidus_admin/ui/panel/component.js +14 -0
- data/app/components/solidus_admin/ui/panel/component.rb +19 -0
- data/app/components/solidus_admin/ui/panel/component.yml +4 -0
- data/app/components/solidus_admin/ui/tab/component.rb +43 -0
- data/app/components/solidus_admin/ui/table/component.html.erb +170 -0
- data/app/components/solidus_admin/ui/table/component.js +118 -0
- data/app/components/solidus_admin/ui/table/component.rb +150 -0
- data/app/components/solidus_admin/ui/table/component.yml +11 -0
- data/app/components/solidus_admin/ui/table/pagination/component.html.erb +28 -0
- data/app/components/solidus_admin/ui/table/pagination/component.rb +14 -0
- data/app/components/solidus_admin/ui/table/pagination/component.yml +3 -0
- data/app/components/solidus_admin/ui/toast/component.html.erb +26 -0
- data/app/components/solidus_admin/ui/toast/component.js +17 -0
- data/app/components/solidus_admin/ui/toast/component.rb +18 -0
- data/app/components/solidus_admin/ui/toast/component.yml +4 -0
- data/app/components/solidus_admin/ui/toggletip/component.html.erb +53 -0
- data/app/components/solidus_admin/ui/toggletip/component.js +26 -0
- data/app/components/solidus_admin/ui/toggletip/component.rb +98 -0
- data/app/components/solidus_admin/ui/toggletip/component.yml +2 -0
- data/app/controllers/solidus_admin/accounts_controller.rb +11 -0
- data/app/controllers/solidus_admin/authentication_adapters/backend.rb +26 -0
- data/app/controllers/solidus_admin/base_controller.rb +21 -0
- data/app/controllers/solidus_admin/controller_helpers/authentication.rb +31 -0
- data/app/controllers/solidus_admin/controller_helpers/authorization.rb +29 -0
- data/app/controllers/solidus_admin/controller_helpers/locale.rb +32 -0
- data/app/controllers/solidus_admin/orders_controller.rb +21 -0
- data/app/controllers/solidus_admin/products_controller.rb +93 -0
- data/app/helpers/solidus_admin/components_helper.rb +9 -0
- data/app/helpers/solidus_admin/layout_helper.rb +18 -0
- data/app/javascript/solidus_admin/application.js +2 -0
- data/app/javascript/solidus_admin/controllers/application.js +9 -0
- data/app/javascript/solidus_admin/controllers/components.js +35 -0
- data/app/javascript/solidus_admin/controllers/hello_controller.js +7 -0
- data/app/javascript/solidus_admin/controllers/index.js +14 -0
- data/app/javascript/solidus_admin/utils.js +8 -0
- data/app/views/layouts/solidus_admin/application.html.erb +30 -0
- data/app/views/layouts/solidus_admin/preview.html.erb +10 -0
- data/app/views/solidus_admin/.keep +0 -0
- data/bin/rails +13 -0
- data/config/importmap.rb +13 -0
- data/config/locales/main_nav.en.yml +13 -0
- data/config/locales/orders.en.yml +4 -0
- data/config/locales/products.en.yml +10 -0
- data/config/routes.rb +13 -0
- data/config/solidus_admin/tailwind.config.js.erb +95 -0
- data/docs/customizing_main_navigation.md +42 -0
- data/docs/customizing_tailwind.md +78 -0
- data/docs/customizing_view_components.md +153 -0
- data/lib/generators/solidus_admin/component/USAGE +13 -0
- data/lib/generators/solidus_admin/component/component_generator.rb +130 -0
- data/lib/generators/solidus_admin/component/templates/component.html.erb.tt +3 -0
- data/lib/generators/solidus_admin/component/templates/component.js.tt +14 -0
- data/lib/generators/solidus_admin/component/templates/component.rb.tt +14 -0
- data/lib/generators/solidus_admin/component/templates/component.yml.tt +4 -0
- data/lib/generators/solidus_admin/component/templates/component_preview.rb.tt +15 -0
- data/lib/generators/solidus_admin/component/templates/component_preview_overview.html.erb +7 -0
- data/lib/generators/solidus_admin/component/templates/component_spec.rb.tt +16 -0
- data/lib/generators/solidus_admin/install/install_generator.rb +44 -0
- data/lib/generators/solidus_admin/install/templates/config/initializers/solidus_admin.rb +44 -0
- data/lib/solidus_admin/configuration.rb +217 -0
- data/lib/solidus_admin/engine.rb +67 -0
- data/lib/solidus_admin/importmap.rb +26 -0
- data/lib/solidus_admin/main_nav_item.rb +97 -0
- data/lib/solidus_admin/preview.rb +81 -0
- data/lib/solidus_admin/tailwindcss.rb +58 -0
- data/lib/solidus_admin/version.rb +5 -0
- data/lib/solidus_admin.rb +15 -0
- data/lib/tasks/importmap.rake +10 -0
- data/lib/tasks/tailwindcss.rake +55 -0
- data/solidus_admin.gemspec +35 -0
- metadata +255 -18
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="
|
|
3
|
+
bg-white
|
|
4
|
+
rounded-lg
|
|
5
|
+
shadow-sm
|
|
6
|
+
border
|
|
7
|
+
border-gray-100
|
|
8
|
+
flex-col
|
|
9
|
+
justify-start
|
|
10
|
+
items-start
|
|
11
|
+
gap-6
|
|
12
|
+
inline-flex
|
|
13
|
+
w-full
|
|
14
|
+
py-6
|
|
15
|
+
"
|
|
16
|
+
data-controller="<%= stimulus_id %>"
|
|
17
|
+
>
|
|
18
|
+
<% if @title %>
|
|
19
|
+
<h2 class="mt-0 px-6 w-full">
|
|
20
|
+
<span class="body-title"><%= @title %></span>
|
|
21
|
+
<%= render component('ui/toggletip').new(text: @title_hint, position: :left) if @title_hint %>
|
|
22
|
+
</h2>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<% if content&.present? %>
|
|
26
|
+
<div class="px-6 w-full flex flex-col gap-6">
|
|
27
|
+
<%= content %>
|
|
28
|
+
</div>
|
|
29
|
+
<% end %>
|
|
30
|
+
|
|
31
|
+
<% if action? %>
|
|
32
|
+
<div class="flex gap-2 items-center border-t border-gray-100 px-6 pt-6 w-full">
|
|
33
|
+
<%= action %>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ['output']
|
|
5
|
+
|
|
6
|
+
typed(event) {
|
|
7
|
+
this.text = event.currentTarget.value
|
|
8
|
+
this.render()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
this.outputTarget.innerText = this.text
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::UI::Panel::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
renders_one :action, ->(name:, href:, icon: 'add-box-fill', **args) {
|
|
5
|
+
link_to(
|
|
6
|
+
icon_tag(icon, class: 'w-[1.4em] h-[1.4em]') + name,
|
|
7
|
+
href,
|
|
8
|
+
**args,
|
|
9
|
+
class: 'flex gap-1 hover:underline'
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# @param title [String] the title of the panel
|
|
14
|
+
# @param title_hint [String] the title hint of the panel
|
|
15
|
+
def initialize(title: nil, title_hint: nil)
|
|
16
|
+
@title = title
|
|
17
|
+
@title_hint = title_hint
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::UI::Tab::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
SIZES = {
|
|
5
|
+
s: %w[h-7 px-1.5 body-small-bold],
|
|
6
|
+
m: %w[h-9 px-3 body-small-bold],
|
|
7
|
+
l: %w[h-12 px-4 body-text-bold],
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
def initialize(text:, size: :m, current: false, disabled: false, **attributes)
|
|
11
|
+
@text = text
|
|
12
|
+
@size = size
|
|
13
|
+
@attributes = attributes
|
|
14
|
+
|
|
15
|
+
@attributes[:'aria-current'] = current
|
|
16
|
+
@attributes[:'aria-disabled'] = disabled
|
|
17
|
+
@attributes[:class] = [
|
|
18
|
+
%w[
|
|
19
|
+
rounded justify-start items-center inline-flex py-1.5 cursor-pointer
|
|
20
|
+
bg-transparent text-gray-500
|
|
21
|
+
|
|
22
|
+
hover:bg-gray-75 hover:text-gray-700
|
|
23
|
+
focus:bg-gray-25 focus:text-gray-700
|
|
24
|
+
|
|
25
|
+
active:bg-gray-50 active:text-black
|
|
26
|
+
aria-current:bg-gray-50 aria-current:text-black
|
|
27
|
+
|
|
28
|
+
disabled:bg-gray-100 disabled:text-gray-400
|
|
29
|
+
aria-disabled:bg-gray-100 aria-disabled:text-gray-400
|
|
30
|
+
],
|
|
31
|
+
SIZES.fetch(@size.to_sym),
|
|
32
|
+
@attributes.delete(:class),
|
|
33
|
+
].join(" ")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call
|
|
37
|
+
content_tag(
|
|
38
|
+
:a,
|
|
39
|
+
@text,
|
|
40
|
+
**@attributes
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="
|
|
3
|
+
rounded-lg
|
|
4
|
+
border
|
|
5
|
+
border-gray-100
|
|
6
|
+
overflow-hidden
|
|
7
|
+
"
|
|
8
|
+
data-controller="<%= stimulus_id %>"
|
|
9
|
+
data-<%= stimulus_id %>-selected-row-class="bg-gray-15"
|
|
10
|
+
>
|
|
11
|
+
<% toolbar_classes = "h-14 p-2 bg-white border-b border-gray-100 justify-start items-center gap-2 visible:flex hidden:hidden" %>
|
|
12
|
+
|
|
13
|
+
<div role="search">
|
|
14
|
+
<div class="<%= toolbar_classes %>" data-<%= stimulus_id %>-target="searchToolbar">
|
|
15
|
+
<%= form_with(
|
|
16
|
+
url: @search_url,
|
|
17
|
+
method: :get,
|
|
18
|
+
html: {
|
|
19
|
+
id: search_form_id,
|
|
20
|
+
class: 'flex-grow',
|
|
21
|
+
"data-turbo-frame": table_frame_id,
|
|
22
|
+
"data-turbo-action": "replace",
|
|
23
|
+
"data-#{stimulus_id}-target": "searchForm",
|
|
24
|
+
"data-action": "reset->#{stimulus_id}#search",
|
|
25
|
+
},
|
|
26
|
+
) do |form| %>
|
|
27
|
+
<label class="items-center gap-1 p-0 inline-flex w-full justify-start relative">
|
|
28
|
+
<%= render component("ui/icon").new(name: 'search-line', class: "w-[1.4em] h-[1.4em] fill-gray-500 absolute ml-3") %>
|
|
29
|
+
<input
|
|
30
|
+
name="q[<%= @search_key %>]"
|
|
31
|
+
value="<%= params.dig(:q, @search_key) %>"
|
|
32
|
+
type="search"
|
|
33
|
+
placeholder="<%= t('.search_placeholder', resources: resource_plural_name) %>"
|
|
34
|
+
class="peer w-full placeholder:text-gray-400 py-1.5 px-10 bg-white rounded border border-gray-300 search-cancel:appearance-none"
|
|
35
|
+
data-<%= stimulus_id %>-target="searchField"
|
|
36
|
+
data-action="<%= stimulus_id %>#search"
|
|
37
|
+
aria-label="<%= t('.search_placeholder', resources: resource_plural_name) %>"
|
|
38
|
+
>
|
|
39
|
+
<button
|
|
40
|
+
class="absolute right-0 mr-3 peer-placeholder-shown:hidden"
|
|
41
|
+
data-action="<%= stimulus_id %>#clearSearch"
|
|
42
|
+
aria-label="<%= t('.clear') %>"
|
|
43
|
+
>
|
|
44
|
+
<%= render component("ui/icon").new(name: 'close-circle-fill', class: "w-[1.4em] h-[1.4em] fill-gray-500") %>
|
|
45
|
+
</button>
|
|
46
|
+
</label>
|
|
47
|
+
<% end %>
|
|
48
|
+
|
|
49
|
+
<div class="ml-4">
|
|
50
|
+
<%= render component("ui/button").new(
|
|
51
|
+
text: t('.cancel'),
|
|
52
|
+
scheme: :ghost,
|
|
53
|
+
"data-action": "#{stimulus_id}#cancelSearch",
|
|
54
|
+
) %>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<% if @filters.any? %>
|
|
59
|
+
<div class="<%= toolbar_classes %>" data-<%= stimulus_id %>-target="filterToolbar">
|
|
60
|
+
<div class="font-semibold text-gray-700 text-sm px-2"><%= t('.refine_search') %>:</div>
|
|
61
|
+
<% @filters.each do |filter| %>
|
|
62
|
+
<label class="flex gap-2 px-2">
|
|
63
|
+
<%= render component('ui/forms/checkbox').new(
|
|
64
|
+
name: filter[:name],
|
|
65
|
+
value: filter[:value],
|
|
66
|
+
size: :s,
|
|
67
|
+
form: search_form_id,
|
|
68
|
+
'data-action': "#{stimulus_id}#search",
|
|
69
|
+
) %>
|
|
70
|
+
<span class="text-gray-700 leading-none text-sm self-center"><%= filter[:label] %></span>
|
|
71
|
+
</label>
|
|
72
|
+
<% end %>
|
|
73
|
+
</div>
|
|
74
|
+
<% end %>
|
|
75
|
+
|
|
76
|
+
<div class="<%= toolbar_classes %>" data-<%= stimulus_id %>-target="scopesToolbar">
|
|
77
|
+
<div class="flex-grow">
|
|
78
|
+
<%= render component("ui/tab").new(text: "All", current: true, href: "") %>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<%= render component("ui/button").new(
|
|
82
|
+
'aria-label': t('.filter'),
|
|
83
|
+
icon: "filter-3-line",
|
|
84
|
+
scheme: :secondary,
|
|
85
|
+
"data-action": "#{stimulus_id}#showSearch",
|
|
86
|
+
) %>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="<%= toolbar_classes %>" data-<%= stimulus_id %>-target="batchToolbar" role="toolbar" aria-label="<%= t(".batch_actions") %>">
|
|
91
|
+
<%= form_tag '', id: batch_actions_form_id %>
|
|
92
|
+
<% @batch_actions.each do |batch_action| %>
|
|
93
|
+
<%= render_batch_action_button(batch_action) %>
|
|
94
|
+
<% end %>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<%= turbo_frame_tag table_frame_id, target: "_top" do %>
|
|
98
|
+
<table class="table-fixed w-full border-collapse">
|
|
99
|
+
<colgroup>
|
|
100
|
+
<% @columns.each do |column| %>
|
|
101
|
+
<col class="<%= column.class_name %>">
|
|
102
|
+
<% end %>
|
|
103
|
+
</colgroup>
|
|
104
|
+
|
|
105
|
+
<thead
|
|
106
|
+
class="bg-gray-15 text-gray-700 text-left text-small"
|
|
107
|
+
data-<%= stimulus_id %>-target="defaultHeader"
|
|
108
|
+
>
|
|
109
|
+
<tr>
|
|
110
|
+
<% @columns.each do |column| %>
|
|
111
|
+
<%= render_header_cell(column.header) %>
|
|
112
|
+
<% end %>
|
|
113
|
+
</tr>
|
|
114
|
+
</thead>
|
|
115
|
+
|
|
116
|
+
<% if @batch_actions %>
|
|
117
|
+
<thead
|
|
118
|
+
data-<%= stimulus_id %>-target="batchHeader"
|
|
119
|
+
class="bg-white color-black text-xs leading-none text-left"
|
|
120
|
+
hidden
|
|
121
|
+
>
|
|
122
|
+
<tr>
|
|
123
|
+
<%= render_header_cell(selectable_column.header) %>
|
|
124
|
+
<%= render_header_cell(content_tag(:div, safe_join([
|
|
125
|
+
content_tag(:span, "0", "data-#{stimulus_id}-target": "selectedRowsCount"),
|
|
126
|
+
" #{t('.rows_selected')}.",
|
|
127
|
+
])), colspan: @columns.count - 1) %>
|
|
128
|
+
</div>
|
|
129
|
+
</thead>
|
|
130
|
+
<% end %>
|
|
131
|
+
|
|
132
|
+
<tbody class="bg-white text-3.5 line-[150%] text-black">
|
|
133
|
+
<% @rows.each do |row| %>
|
|
134
|
+
<tr class="<%= row_class_for(row) %>">
|
|
135
|
+
<% @columns.each do |column| %>
|
|
136
|
+
<%= render_data_cell(column.data, row) %>
|
|
137
|
+
<% end %>
|
|
138
|
+
</tr>
|
|
139
|
+
<% end %>
|
|
140
|
+
|
|
141
|
+
<% if @rows.empty? && @model_class %>
|
|
142
|
+
<tr>
|
|
143
|
+
<td
|
|
144
|
+
colspan="<%= @columns.size %>"
|
|
145
|
+
class="text-center py-4 text-3.5 line-[150%] text-black bg-white"
|
|
146
|
+
>
|
|
147
|
+
<%= t('.no_resources_found', resources: resource_plural_name) %>
|
|
148
|
+
</td>
|
|
149
|
+
</tr>
|
|
150
|
+
<% end %>
|
|
151
|
+
</tbody>
|
|
152
|
+
|
|
153
|
+
<% if @prev_page_link || @next_page_link %>
|
|
154
|
+
<tfoot>
|
|
155
|
+
<tr>
|
|
156
|
+
<td colspan="<%= @columns.size %>" class="py-4 bg-white">
|
|
157
|
+
<div class="flex justify-center">
|
|
158
|
+
<%= render component('ui/table/pagination').new(
|
|
159
|
+
prev_link: @prev_page_link,
|
|
160
|
+
next_link: @next_page_link
|
|
161
|
+
) %>
|
|
162
|
+
</div>
|
|
163
|
+
</td>
|
|
164
|
+
</tr>
|
|
165
|
+
</tfoot>
|
|
166
|
+
<% end %>
|
|
167
|
+
|
|
168
|
+
</table>
|
|
169
|
+
<% end %>
|
|
170
|
+
</div>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { debounce } from "solidus_admin/utils"
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static targets = [
|
|
6
|
+
"checkbox",
|
|
7
|
+
"headerCheckbox",
|
|
8
|
+
"batchToolbar",
|
|
9
|
+
"scopesToolbar",
|
|
10
|
+
"searchToolbar",
|
|
11
|
+
"searchField",
|
|
12
|
+
"searchForm",
|
|
13
|
+
"filterToolbar",
|
|
14
|
+
"defaultHeader",
|
|
15
|
+
"batchHeader",
|
|
16
|
+
"selectedRowsCount",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
static classes = ["selectedRow"]
|
|
20
|
+
static values = {
|
|
21
|
+
mode: { type: String, default: "scopes" },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
initialize() {
|
|
25
|
+
// Debounced search function.
|
|
26
|
+
// This method submits the search form after a delay of 200ms.
|
|
27
|
+
// If the function is called again within this delay, the previous call is cleared,
|
|
28
|
+
// effectively ensuring the form is only submitted 200ms after the last call (e.g., user stops typing).
|
|
29
|
+
this.search = debounce(this.search.bind(this), 200)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
connect() {
|
|
33
|
+
if (this.searchFieldTarget.value !== "") this.modeValue = "search"
|
|
34
|
+
|
|
35
|
+
this.render()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
showSearch(event) {
|
|
39
|
+
this.modeValue = "search"
|
|
40
|
+
this.render()
|
|
41
|
+
this.searchFieldTarget.focus()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
search() {
|
|
45
|
+
this.searchFormTarget.requestSubmit()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
clearSearch() {
|
|
49
|
+
this.searchFieldTarget.value = ''
|
|
50
|
+
this.search()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
cancelSearch() {
|
|
54
|
+
this.clearSearch()
|
|
55
|
+
|
|
56
|
+
this.modeValue = "scopes"
|
|
57
|
+
this.render()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
selectRow(event) {
|
|
61
|
+
if (this.checkboxTargets.some((checkbox) => checkbox.checked)) {
|
|
62
|
+
this.modeValue = "batch"
|
|
63
|
+
} else if (this.searchFieldTarget.value !== '') {
|
|
64
|
+
this.modeValue = "search"
|
|
65
|
+
} else {
|
|
66
|
+
this.modeValue = "scopes"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.render()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
selectAllRows(event) {
|
|
73
|
+
if (this.modeValue = event.target.checked) {
|
|
74
|
+
this.modeValue = "batch"
|
|
75
|
+
} else if (this.searchFieldTarget.value !== '') {
|
|
76
|
+
this.modeValue = "search"
|
|
77
|
+
} else {
|
|
78
|
+
this.modeValue = "scopes"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.checkboxTargets.forEach((checkbox) => (checkbox.checked = event.target.checked))
|
|
82
|
+
|
|
83
|
+
this.render()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
render() {
|
|
87
|
+
const selectedRows = this.checkboxTargets.filter((checkbox) => checkbox.checked)
|
|
88
|
+
|
|
89
|
+
this.searchToolbarTarget.toggleAttribute("hidden", this.modeValue !== "search")
|
|
90
|
+
|
|
91
|
+
if (this.hasFilterToolbarTarget) {
|
|
92
|
+
this.filterToolbarTarget.toggleAttribute("hidden", this.modeValue !== "search")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.batchToolbarTarget.toggleAttribute("hidden", this.modeValue !== "batch")
|
|
96
|
+
this.batchHeaderTarget.toggleAttribute("hidden", this.modeValue !== "batch")
|
|
97
|
+
this.defaultHeaderTarget.toggleAttribute("hidden", this.modeValue === "batch")
|
|
98
|
+
|
|
99
|
+
this.scopesToolbarTarget.toggleAttribute("hidden", this.modeValue !== "scopes")
|
|
100
|
+
|
|
101
|
+
// Update the rows background color
|
|
102
|
+
this.checkboxTargets.filter((checkbox) =>
|
|
103
|
+
checkbox.closest("tr").classList.toggle(this.selectedRowClass, checkbox.checked),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// Update the selected rows count
|
|
107
|
+
this.selectedRowsCountTarget.textContent = `${selectedRows.length}`
|
|
108
|
+
|
|
109
|
+
// Update the header checkboxes
|
|
110
|
+
this.headerCheckboxTargets.forEach((checkbox) => {
|
|
111
|
+
checkbox.indeterminate = false
|
|
112
|
+
checkbox.checked = false
|
|
113
|
+
|
|
114
|
+
if (selectedRows.length === this.checkboxTargets.length) checkbox.checked = true
|
|
115
|
+
else if (selectedRows.length > 0) checkbox.indeterminate = true
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
# @param id [String] A unique identifier for the table component.
|
|
5
|
+
# @param model_class [ActiveModel::Translation] The model class used for translations.
|
|
6
|
+
# @param rows [Array] The collection of objects that will be passed to columns for display.
|
|
7
|
+
# @param fade_row_proc [Proc, nil] A proc determining if a row should have a faded appearance.
|
|
8
|
+
# @param search_key [Symbol] The key for searching.
|
|
9
|
+
# @param search_url [String] The base URL for searching.
|
|
10
|
+
#
|
|
11
|
+
# @param columns [Array<Hash>] The array of column definitions.
|
|
12
|
+
# @option columns [Symbol|Proc|#to_s] :header The column header.
|
|
13
|
+
# @option columns [Symbol|Proc|#to_s] :data The data accessor for the column.
|
|
14
|
+
# @option columns [String] :class_name (optional) The class name for the column.
|
|
15
|
+
#
|
|
16
|
+
# @param batch_actions [Array<Hash>] The array of batch action definitions.
|
|
17
|
+
# @option batch_actions [String] :display_name The batch action display name.
|
|
18
|
+
# @option batch_actions [String] :icon The batch action icon.
|
|
19
|
+
# @option batch_actions [String] :action The batch action path.
|
|
20
|
+
# @option batch_actions [String] :method The batch action HTTP method for the provided path.
|
|
21
|
+
#
|
|
22
|
+
#
|
|
23
|
+
# @param filters [Array<Hash>] The array of filter definitions.
|
|
24
|
+
# @option filters [String] :name The filter's name.
|
|
25
|
+
# @option filters [Any] :value The filter's value.
|
|
26
|
+
# @option filters [String] :label The filter's label.
|
|
27
|
+
#
|
|
28
|
+
# @param prev_page_link [String, nil] The link to the previous page.
|
|
29
|
+
# @param next_page_link [String, nil] The link to the next page.
|
|
30
|
+
def initialize(
|
|
31
|
+
id:,
|
|
32
|
+
model_class:,
|
|
33
|
+
rows:,
|
|
34
|
+
search_key:,
|
|
35
|
+
search_url:,
|
|
36
|
+
fade_row_proc: nil,
|
|
37
|
+
columns: [],
|
|
38
|
+
batch_actions: [],
|
|
39
|
+
filters: [],
|
|
40
|
+
prev_page_link: nil,
|
|
41
|
+
next_page_link: nil
|
|
42
|
+
)
|
|
43
|
+
@columns = columns.map { Column.new(**_1) }
|
|
44
|
+
@batch_actions = batch_actions.map { BatchAction.new(**_1) }
|
|
45
|
+
@filters = filters.map { Filter.new(**_1) }
|
|
46
|
+
@id = id
|
|
47
|
+
@model_class = model_class
|
|
48
|
+
@rows = rows
|
|
49
|
+
@fade_row_proc = fade_row_proc
|
|
50
|
+
@search_key = search_key
|
|
51
|
+
@search_url = search_url
|
|
52
|
+
@prev_page_link = prev_page_link
|
|
53
|
+
@next_page_link = next_page_link
|
|
54
|
+
|
|
55
|
+
@columns.unshift selectable_column if batch_actions.present?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def resource_plural_name
|
|
59
|
+
@model_class.model_name.human.pluralize
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def selectable_column
|
|
63
|
+
@selectable_column ||= Column.new(
|
|
64
|
+
header: -> {
|
|
65
|
+
component("ui/forms/checkbox").new(
|
|
66
|
+
form: batch_actions_form_id,
|
|
67
|
+
"data-action": "#{stimulus_id}#selectAllRows",
|
|
68
|
+
"data-#{stimulus_id}-target": "headerCheckbox",
|
|
69
|
+
"aria-label": t('.select_all'),
|
|
70
|
+
)
|
|
71
|
+
},
|
|
72
|
+
data: ->(data) {
|
|
73
|
+
component("ui/forms/checkbox").new(
|
|
74
|
+
name: "id[]",
|
|
75
|
+
form: batch_actions_form_id,
|
|
76
|
+
value: data.id,
|
|
77
|
+
"data-action": "#{stimulus_id}#selectRow",
|
|
78
|
+
"data-#{stimulus_id}-target": "checkbox",
|
|
79
|
+
"aria-label": t('.select_row'),
|
|
80
|
+
)
|
|
81
|
+
},
|
|
82
|
+
class_name: 'w-[52px]',
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def batch_actions_form_id
|
|
87
|
+
@batch_actions_form_id ||= "#{stimulus_id}--batch-actions-#{@id}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def table_frame_id
|
|
91
|
+
@table_frame_id ||= "#{stimulus_id}--table-frame-#{@id}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def search_form_id
|
|
95
|
+
@search_form_id ||= "#{stimulus_id}--search-form-#{@id}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def render_batch_action_button(batch_action)
|
|
99
|
+
render component("ui/button").new(
|
|
100
|
+
name: request_forgery_protection_token,
|
|
101
|
+
value: form_authenticity_token(form_options: {
|
|
102
|
+
action: batch_action.action,
|
|
103
|
+
method: batch_action.method,
|
|
104
|
+
}),
|
|
105
|
+
formaction: batch_action.action,
|
|
106
|
+
formmethod: batch_action.method,
|
|
107
|
+
form: batch_actions_form_id,
|
|
108
|
+
type: :submit,
|
|
109
|
+
icon: batch_action.icon,
|
|
110
|
+
text: batch_action.display_name,
|
|
111
|
+
scheme: :secondary,
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def render_header_cell(cell, **attrs)
|
|
116
|
+
cell = cell.call if cell.respond_to?(:call)
|
|
117
|
+
cell = @model_class.human_attribute_name(cell) if cell.is_a?(Symbol)
|
|
118
|
+
cell = cell.render_in(self) if cell.respond_to?(:render_in)
|
|
119
|
+
|
|
120
|
+
content_tag(:th, cell, class: %{
|
|
121
|
+
border-b
|
|
122
|
+
border-gray-100
|
|
123
|
+
px-4
|
|
124
|
+
h-9
|
|
125
|
+
font-semibold
|
|
126
|
+
vertical-align-middle
|
|
127
|
+
leading-none
|
|
128
|
+
}, **attrs)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def render_data_cell(cell, data)
|
|
132
|
+
cell = cell.call(data) if cell.respond_to?(:call)
|
|
133
|
+
cell = data.public_send(cell) if cell.is_a?(Symbol)
|
|
134
|
+
cell = cell.render_in(self) if cell.respond_to?(:render_in)
|
|
135
|
+
|
|
136
|
+
content_tag(:td, content_tag(:div, cell, class: "flex items-center gap-1.5"), class: "py-2 px-4 h-10 vertical-align-middle leading-none")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def row_class_for(row)
|
|
140
|
+
classes = ['border-b', 'border-gray-100']
|
|
141
|
+
classes << ['bg-gray-15', 'text-gray-700'] if @fade_row_proc&.call(row)
|
|
142
|
+
|
|
143
|
+
classes.join(' ')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
Column = Struct.new(:header, :data, :class_name, keyword_init: true)
|
|
147
|
+
BatchAction = Struct.new(:display_name, :icon, :action, :method, keyword_init: true) # rubocop:disable Lint/StructNewOverride
|
|
148
|
+
Filter = Struct.new(:name, :value, :label, keyword_init: true)
|
|
149
|
+
private_constant :Column, :BatchAction, :Filter
|
|
150
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
en:
|
|
2
|
+
no_resources_found: "No %{resources} found"
|
|
3
|
+
rows_selected: 'selected'
|
|
4
|
+
select_all: 'Select all'
|
|
5
|
+
select_row: 'Select row'
|
|
6
|
+
filter: 'Filter'
|
|
7
|
+
search_placeholder: 'Search all %{resources}'
|
|
8
|
+
refine_search: 'Refine Search'
|
|
9
|
+
batch_actions: Batch actions
|
|
10
|
+
clear: Clear
|
|
11
|
+
cancel: Cancel
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<nav aria-label="pagination" class="flex items-center">
|
|
2
|
+
<%= render component("ui/button").new(
|
|
3
|
+
icon: 'arrow-left-s-line',
|
|
4
|
+
class: 'rounded-tr-none rounded-br-none border-r-0',
|
|
5
|
+
scheme: :secondary,
|
|
6
|
+
size: :s,
|
|
7
|
+
tag: :a,
|
|
8
|
+
href: @prev_link,
|
|
9
|
+
'aria-disabled': @prev_link.blank?,
|
|
10
|
+
rel: 'prev',
|
|
11
|
+
text: content_tag(:span, t('.previous'), class: 'sr-only'),
|
|
12
|
+
"data-turbo-frame": "_self",
|
|
13
|
+
"data-turbo-action": "advance",
|
|
14
|
+
) -%>
|
|
15
|
+
<%= render component("ui/button").new(
|
|
16
|
+
icon: 'arrow-right-s-line',
|
|
17
|
+
class: 'rounded-tl-none rounded-bl-none',
|
|
18
|
+
scheme: :secondary,
|
|
19
|
+
size: :s,
|
|
20
|
+
tag: :a,
|
|
21
|
+
href: @next_link,
|
|
22
|
+
'aria-disabled': @next_link.blank?,
|
|
23
|
+
rel: 'next',
|
|
24
|
+
text: content_tag(:span, t('.next'), class: 'sr-only'),
|
|
25
|
+
"data-turbo-frame": "_self",
|
|
26
|
+
"data-turbo-action": "advance",
|
|
27
|
+
) %>
|
|
28
|
+
</nav>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::UI::Table::Pagination::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
# @param prev_link [String] The link to the previous page.
|
|
5
|
+
# @param next_link [String] The link to the next page.
|
|
6
|
+
def initialize(prev_link: nil, next_link: nil)
|
|
7
|
+
@prev_link = prev_link
|
|
8
|
+
@next_link = next_link
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def render?
|
|
12
|
+
@prev_link.present? || @next_link.present?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="
|
|
3
|
+
rounded inline-block px-3 py-2
|
|
4
|
+
<%= SCHEMES.fetch(@scheme.to_sym).join(' ') %>
|
|
5
|
+
"
|
|
6
|
+
data-controller="<%= stimulus_id %>"
|
|
7
|
+
data-<%= stimulus_id %>-closing-class="transform opacity-0 transition duration-500"
|
|
8
|
+
data-<%= stimulus_id %>-transition-value="500"
|
|
9
|
+
role="dialog"
|
|
10
|
+
aria-label="<%= t(".#{@scheme}_label") %>"
|
|
11
|
+
aria-live="polite"
|
|
12
|
+
>
|
|
13
|
+
<%= icon_tag(@icon, class: 'inline-block w-[1.125rem] h-[1.125rem] mr-3 fill-current') if @icon %>
|
|
14
|
+
|
|
15
|
+
<p class="inline-block body-tiny-bold"><%= @text %></p>
|
|
16
|
+
|
|
17
|
+
<button
|
|
18
|
+
class="inline-block ml-3 align-text-bottom"
|
|
19
|
+
title="<%= t('.close_text') %>"
|
|
20
|
+
data-action="<%= stimulus_id %>#close"
|
|
21
|
+
aria-label="<%= t('.close_text') %>"
|
|
22
|
+
data-<%= stimulus_id %>-target="closeButton"
|
|
23
|
+
>
|
|
24
|
+
<%= icon_tag('close-line', class: "w-[1.125rem] h-[1.125rem] fill-current") %>
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ['closeButton']
|
|
5
|
+
static classes = ['closing']
|
|
6
|
+
static values = { transition: Number }
|
|
7
|
+
|
|
8
|
+
connect () {
|
|
9
|
+
// Give focus to the close button
|
|
10
|
+
this.closeButtonTarget.focus();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
close () {
|
|
14
|
+
this.element.classList.add(...this.closingClasses);
|
|
15
|
+
setTimeout(() => this.element.remove(), this.transitionValue)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::UI::Toast::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
SCHEMES = {
|
|
5
|
+
default: %w[
|
|
6
|
+
bg-gray-800 text-white
|
|
7
|
+
],
|
|
8
|
+
error: %w[
|
|
9
|
+
bg-red-500 text-white
|
|
10
|
+
],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def initialize(text:, icon: nil, scheme: :default)
|
|
14
|
+
@text = text
|
|
15
|
+
@icon = icon
|
|
16
|
+
@scheme = scheme.to_sym
|
|
17
|
+
end
|
|
18
|
+
end
|