infotable 1.0.1 → 1.2.0
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 +4 -4
- data/README.md +14 -2
- data/app/assets/javascripts/infotable/controllers/drawer_controller.js +68 -42
- data/app/assets/javascripts/infotable/controllers/table_controller.js +127 -23
- data/app/controllers/infotable/tables_controller.rb +17 -7
- data/app/helpers/infotable/tables_helper.rb +64 -2
- data/app/views/infotable/tables/_active_filters.html.erb +24 -0
- data/app/views/infotable/tables/_column_visibility.html.erb +11 -11
- data/app/views/infotable/tables/_filter_drawer.html.erb +4 -4
- data/app/views/infotable/tables/_next_page_frame.html.erb +5 -5
- data/app/views/infotable/tables/_pagination.html.erb +12 -12
- data/app/views/infotable/tables/_rail.html.erb +56 -0
- data/app/views/infotable/tables/_side_panel.html.erb +22 -10
- data/app/views/infotable/tables/_table.html.erb +43 -26
- data/app/views/infotable/tables/_table_rows.html.erb +3 -3
- data/app/views/infotable/tables/filters/_boolean.html.erb +2 -2
- data/app/views/infotable/tables/filters/_date.html.erb +2 -30
- data/app/views/infotable/tables/filters/_number.html.erb +2 -32
- data/app/views/infotable/tables/filters/_range.html.erb +66 -0
- data/app/views/infotable/tables/filters/_select.html.erb +4 -4
- data/app/views/infotable/tables/filters/_text.html.erb +3 -3
- data/app/views/infotable/tables/show.html.erb +22 -13
- data/config/locales/en.yml +23 -0
- data/config/locales/pl.yml +23 -0
- data/config/routes.rb +1 -0
- data/lib/infotable/base.rb +66 -0
- data/lib/infotable/configuration.rb +3 -1
- data/lib/infotable/formatters/action_formatter.rb +7 -7
- data/lib/infotable/formatters/avatar_formatter.rb +2 -2
- data/lib/infotable/formatters/badge_formatter.rb +9 -9
- data/lib/infotable/formatters/boolean_formatter.rb +2 -2
- data/lib/infotable/formatters/link_formatter.rb +1 -1
- data/lib/infotable/formatters/percent_formatter.rb +1 -1
- data/lib/infotable/version.rb +1 -1
- metadata +4 -1
|
@@ -1,32 +1,2 @@
|
|
|
1
|
-
<%# Number filter
|
|
2
|
-
|
|
3
|
-
<%# From input %>
|
|
4
|
-
<div class="relative">
|
|
5
|
-
<input type="number"
|
|
6
|
-
name="filters[<%= column[:name] %>][from]"
|
|
7
|
-
id="<%= infotable_filter_id(table_name, column, 'from') %>"
|
|
8
|
-
placeholder="<%= t('infotable.filters.min') %>"
|
|
9
|
-
step="any"
|
|
10
|
-
class="block w-full rounded-lg border-0 bg-white pr-8 pl-3 py-2 text-base sm:text-sm text-neutral-900 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] disabled:cursor-not-allowed disabled:bg-neutral-100">
|
|
11
|
-
<button type="button"
|
|
12
|
-
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
13
|
-
data-action="click->infotable--drawer#clearSingleFilter"
|
|
14
|
-
data-filter-name="<%= column[:name] %>"
|
|
15
|
-
data-filter-type="number"
|
|
16
|
-
title="<%= t('infotable.filters.clear_filter') %>">
|
|
17
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
18
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
19
|
-
</svg>
|
|
20
|
-
</button>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<%# To input %>
|
|
24
|
-
<div class="relative">
|
|
25
|
-
<input type="number"
|
|
26
|
-
name="filters[<%= column[:name] %>][to]"
|
|
27
|
-
id="<%= infotable_filter_id(table_name, column, 'to') %>"
|
|
28
|
-
placeholder="<%= t('infotable.filters.max') %>"
|
|
29
|
-
step="any"
|
|
30
|
-
class="block w-full rounded-lg border-0 bg-white pr-3 pl-3 py-2 text-base sm:text-sm text-neutral-900 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] disabled:cursor-not-allowed disabled:bg-neutral-100">
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
1
|
+
<%# Number filter: range or comparison, see _range %>
|
|
2
|
+
<%= render "infotable/tables/filters/range", column: column, table_name: table_name, kind: :number %>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<%# Shared by the number and date filters.
|
|
2
|
+
Locals: column, table_name, kind (:number or :date)
|
|
3
|
+
The operator select picks the shape of the filter:
|
|
4
|
+
"" from/to inputs (either may stay blank)
|
|
5
|
+
single ops one :value input
|
|
6
|
+
empty ops no input %>
|
|
7
|
+
<%
|
|
8
|
+
kind = kind.to_sym
|
|
9
|
+
input_type = kind == :date ? "date" : "number"
|
|
10
|
+
ops = kind == :date ? %w[on before after] : %w[eq neq gt gte lt lte]
|
|
11
|
+
input_class = "block w-full rounded-lg border-0 bg-white dark:bg-gray-900 pr-3 pl-3 py-2 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] disabled:cursor-not-allowed disabled:bg-neutral-100"
|
|
12
|
+
select_class = "#{input_class} appearance-none pr-10 cursor-pointer bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]"
|
|
13
|
+
step = kind == :number ? "any" : nil
|
|
14
|
+
%>
|
|
15
|
+
<div class="space-y-2" data-range-container id="<%= infotable_filter_id(table_name, column, 'container') %>">
|
|
16
|
+
<div class="flex items-center gap-2">
|
|
17
|
+
<select name="filters[<%= column[:name] %>][op]"
|
|
18
|
+
data-range-op
|
|
19
|
+
data-action="change->infotable--drawer#syncFilterOps"
|
|
20
|
+
id="<%= infotable_filter_id(table_name, column, 'op') %>"
|
|
21
|
+
class="<%= select_class %>">
|
|
22
|
+
<option value=""><%= t("infotable.filters.range_ops.between") %></option>
|
|
23
|
+
<% ops.each do |op| %>
|
|
24
|
+
<option value="<%= op %>"><%= t("infotable.filters.#{kind}_ops.#{op}") %></option>
|
|
25
|
+
<% end %>
|
|
26
|
+
<option value="is_empty"><%= t("infotable.filters.text_ops.is_empty") %></option>
|
|
27
|
+
<option value="is_not_empty"><%= t("infotable.filters.text_ops.is_not_empty") %></option>
|
|
28
|
+
</select>
|
|
29
|
+
<button type="button"
|
|
30
|
+
class="shrink-0 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
31
|
+
data-action="click->infotable--drawer#clearSingleFilter"
|
|
32
|
+
data-filter-name="<%= column[:name] %>"
|
|
33
|
+
data-filter-type="<%= kind %>"
|
|
34
|
+
title="<%= t('infotable.filters.clear_filter') %>">
|
|
35
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
36
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
37
|
+
</svg>
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="grid grid-cols-2 gap-2" data-range-fields="range">
|
|
42
|
+
<input type="<%= input_type %>"
|
|
43
|
+
name="filters[<%= column[:name] %>][from]"
|
|
44
|
+
id="<%= infotable_filter_id(table_name, column, 'from') %>"
|
|
45
|
+
placeholder="<%= t(kind == :date ? 'infotable.filters.from' : 'infotable.filters.min') %>"
|
|
46
|
+
aria-label="<%= t('infotable.filters.from') %>"
|
|
47
|
+
<% if step %>step="<%= step %>"<% end %>
|
|
48
|
+
class="<%= input_class %>">
|
|
49
|
+
<input type="<%= input_type %>"
|
|
50
|
+
name="filters[<%= column[:name] %>][to]"
|
|
51
|
+
id="<%= infotable_filter_id(table_name, column, 'to') %>"
|
|
52
|
+
placeholder="<%= t(kind == :date ? 'infotable.filters.to' : 'infotable.filters.max') %>"
|
|
53
|
+
aria-label="<%= t('infotable.filters.to') %>"
|
|
54
|
+
<% if step %>step="<%= step %>"<% end %>
|
|
55
|
+
class="<%= input_class %>">
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div data-range-fields="single" style="display: none">
|
|
59
|
+
<input type="<%= input_type %>"
|
|
60
|
+
name="filters[<%= column[:name] %>][value]"
|
|
61
|
+
id="<%= infotable_filter_id(table_name, column, 'value') %>"
|
|
62
|
+
placeholder="<%= t('infotable.filters.enter_value') %>"
|
|
63
|
+
<% if step %>step="<%= step %>"<% end %>
|
|
64
|
+
class="<%= input_class %>">
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
id="<%= infotable_filter_id(table_name, column, 'value') %>"
|
|
15
15
|
multiple
|
|
16
16
|
size="6"
|
|
17
|
-
class="block w-full appearance-none rounded-lg border-0 bg-white pl-3 py-2 pr-8 text-base sm:text-sm text-neutral-900 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75"
|
|
17
|
+
class="block w-full appearance-none rounded-lg border-0 bg-white dark:bg-gray-900 pl-3 py-2 pr-8 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75"
|
|
18
18
|
style="min-height: 120px;">
|
|
19
19
|
<% options.each do |option| %>
|
|
20
20
|
<% if option.is_a?(Array) %>
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<% end %>
|
|
26
26
|
</select>
|
|
27
27
|
<button type="button"
|
|
28
|
-
class="absolute right-2 top-2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
28
|
+
class="absolute right-2 top-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
29
29
|
data-action="click->infotable--drawer#clearSingleFilter"
|
|
30
30
|
data-filter-name="<%= column[:name] %>"
|
|
31
31
|
data-filter-type="select"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
<div class="relative">
|
|
42
42
|
<select name="filters[<%= column[:name] %>][value]"
|
|
43
43
|
id="<%= infotable_filter_id(table_name, column, 'value') %>"
|
|
44
|
-
class="block w-full appearance-none rounded-lg border-0 bg-white pr-10 pl-3 py-2 text-base sm:text-sm text-neutral-900 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75 bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]">
|
|
44
|
+
class="block w-full appearance-none rounded-lg border-0 bg-white dark:bg-gray-900 pr-10 pl-3 py-2 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75 bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]">
|
|
45
45
|
<option value=""><%= t('infotable.filters.all') %></option>
|
|
46
46
|
<% options.each do |option| %>
|
|
47
47
|
<% if option.is_a?(Array) %>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
<% end %>
|
|
53
53
|
</select>
|
|
54
54
|
<button type="button"
|
|
55
|
-
class="absolute right-8 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
55
|
+
class="absolute right-8 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
56
56
|
data-action="click->infotable--drawer#clearSingleFilter"
|
|
57
57
|
data-filter-name="<%= column[:name] %>"
|
|
58
58
|
data-filter-type="select"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
data-filter-op
|
|
7
7
|
data-action="change->infotable--drawer#syncFilterOps"
|
|
8
8
|
id="<%= infotable_filter_id(table_name, column, 'op') %>"
|
|
9
|
-
class="block w-full appearance-none rounded-lg border-0 bg-white pr-10 pl-3 py-2 text-base sm:text-sm text-neutral-900 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75 bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]">
|
|
9
|
+
class="block w-full appearance-none rounded-lg border-0 bg-white dark:bg-gray-900 pr-10 pl-3 py-2 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75 bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]">
|
|
10
10
|
<option value="contains"><%= t('infotable.filters.text_ops.contains') %></option>
|
|
11
11
|
<option value="exact"><%= t('infotable.filters.text_ops.exact') %></option>
|
|
12
12
|
<option value="not_contain"><%= t('infotable.filters.text_ops.not_contain') %></option>
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
name="filters[<%= column[:name] %>][value]"
|
|
24
24
|
id="<%= infotable_filter_id(table_name, column, 'value') %>"
|
|
25
25
|
placeholder="<%= t('infotable.filters.enter_value') %>"
|
|
26
|
-
class="block w-full rounded-lg border-0 bg-white pr-8 pl-3 py-2 text-base sm:text-sm text-neutral-900 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] disabled:cursor-not-allowed disabled:bg-neutral-100">
|
|
26
|
+
class="block w-full rounded-lg border-0 bg-white dark:bg-gray-900 pr-8 pl-3 py-2 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] disabled:cursor-not-allowed disabled:bg-neutral-100">
|
|
27
27
|
<button type="button"
|
|
28
|
-
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
28
|
+
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
29
29
|
data-action="click->infotable--drawer#clearSingleFilter"
|
|
30
30
|
data-filter-name="<%= column[:name] %>"
|
|
31
31
|
data-filter-type="text"
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<%# This view renders inside the turbo frame %>
|
|
2
2
|
<%# Turbo requires the frame element to be present in the response %>
|
|
3
3
|
<turbo-frame id="<%= params[:table_name] %>_content">
|
|
4
|
-
|
|
4
|
+
<% active_filters = infotable_active_filters(@table_class, @filters, @search) %>
|
|
5
|
+
<%= render "infotable/tables/active_filters", chips: active_filters, table_name: params[:table_name] %>
|
|
6
|
+
<div<% if @pagy && !@table_class.countless? %> data-total-count="<%= @pagy.count %>"<% end %> class="relative overflow-y-auto overflow-x-auto scroll-smooth [scrollbar-width:thin] [scrollbar-color:#cbd5e1_#f1f5f9] [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar-track]:bg-slate-100 [&::-webkit-scrollbar-thumb]:bg-slate-300 [&::-webkit-scrollbar-thumb]:rounded [&::-webkit-scrollbar-thumb:hover]:bg-slate-400 dark:[scrollbar-color:#475569_#1e293b] dark:[&::-webkit-scrollbar-track]:bg-slate-800 dark:[&::-webkit-scrollbar-thumb]:bg-slate-600"
|
|
5
7
|
style="min-height: <%= @table_class.min_height %>; max-height: <%= @table_class.max_height %>;">
|
|
6
8
|
|
|
7
9
|
<% if @records.any? %>
|
|
8
10
|
<%# Table %>
|
|
9
|
-
<table class="min-w-full divide-y divide-gray-200">
|
|
10
|
-
<thead class="bg-gray-50 border-b-2 border-gray-200">
|
|
11
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
12
|
+
<thead class="bg-gray-50 dark:bg-gray-800 border-b-2 border-gray-200 dark:border-gray-700">
|
|
11
13
|
<tr>
|
|
12
14
|
<%
|
|
13
15
|
row_size = @table_class.row_size_value
|
|
@@ -18,12 +20,12 @@
|
|
|
18
20
|
end
|
|
19
21
|
%>
|
|
20
22
|
<% if @table_class.checkbox_enabled? %>
|
|
21
|
-
<th scope="col" class="<%= header_padding %> w-10 bg-gray-50 sticky top-0 z-20">
|
|
23
|
+
<th scope="col" class="<%= header_padding %> w-10 bg-gray-50 dark:bg-gray-800 sticky top-0 z-20">
|
|
22
24
|
<input type="checkbox"
|
|
23
25
|
aria-label="<%= t('infotable.batch.select_all') %>"
|
|
24
26
|
data-infotable--table-target="selectAll"
|
|
25
27
|
data-action="change->infotable--table#toggleAll"
|
|
26
|
-
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
|
28
|
+
class="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-500">
|
|
27
29
|
</th>
|
|
28
30
|
<% end %>
|
|
29
31
|
<% @table_class.columns.each do |column| %>
|
|
@@ -32,7 +34,7 @@
|
|
|
32
34
|
data-default-visible="<%= column[:visible] %>"
|
|
33
35
|
<%= tag.attributes(data: { default_pinned: column[:pinned] }) if column[:pinned] %>
|
|
34
36
|
style="width: <%= column[:width] %>; max-width: <%= column[:width] %>; overflow: hidden;"
|
|
35
|
-
class="<%= header_padding %> text-left text-xs font-semibold text-gray-900 tracking-wider select-none bg-gray-50 sticky top-0 z-20">
|
|
37
|
+
class="<%= header_padding %> text-left text-xs font-semibold text-gray-900 dark:text-gray-100 tracking-wider select-none bg-gray-50 dark:bg-gray-800 sticky top-0 z-20">
|
|
36
38
|
<% if column[:sortable] %>
|
|
37
39
|
<% sort_index = current_sorts.index { |name, _| name == column[:name].to_s } %>
|
|
38
40
|
<% sort_dir = sort_index ? current_sorts[sort_index].last : nil %>
|
|
@@ -56,16 +58,16 @@
|
|
|
56
58
|
<span class="flex-none rounded">
|
|
57
59
|
<% if sort_index %>
|
|
58
60
|
<% if sort_dir == 'asc' %>
|
|
59
|
-
<svg class="h-4 w-4 text-indigo-600" fill="currentColor" viewBox="0 0 20 20">
|
|
61
|
+
<svg class="h-4 w-4 text-indigo-600 dark:text-indigo-400" fill="currentColor" viewBox="0 0 20 20">
|
|
60
62
|
<path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd"/>
|
|
61
63
|
</svg>
|
|
62
64
|
<% else %>
|
|
63
|
-
<svg class="h-4 w-4 text-indigo-600" fill="currentColor" viewBox="0 0 20 20">
|
|
65
|
+
<svg class="h-4 w-4 text-indigo-600 dark:text-indigo-400" fill="currentColor" viewBox="0 0 20 20">
|
|
64
66
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
65
67
|
</svg>
|
|
66
68
|
<% end %>
|
|
67
69
|
<% if current_sorts.size > 1 %>
|
|
68
|
-
<span class="text-[10px] font-bold text-indigo-600"><%= sort_index + 1 %></span>
|
|
70
|
+
<span class="text-[10px] font-bold text-indigo-600 dark:text-indigo-400"><%= sort_index + 1 %></span>
|
|
69
71
|
<% end %>
|
|
70
72
|
<% else %>
|
|
71
73
|
<svg class="h-4 w-4 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity" fill="currentColor" viewBox="0 0 20 20">
|
|
@@ -81,7 +83,7 @@
|
|
|
81
83
|
<% end %>
|
|
82
84
|
</tr>
|
|
83
85
|
</thead>
|
|
84
|
-
<tbody id="<%= params[:table_name] %>_tbody" class="bg-white">
|
|
86
|
+
<tbody id="<%= params[:table_name] %>_tbody" class="bg-white dark:bg-gray-900">
|
|
85
87
|
<%= render partial: "infotable/tables/table_rows", locals: { records: @records, table_class: @table_class } %>
|
|
86
88
|
</tbody>
|
|
87
89
|
</table>
|
|
@@ -96,8 +98,15 @@
|
|
|
96
98
|
<svg class="mx-auto h-16 w-16 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
97
99
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/>
|
|
98
100
|
</svg>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
<% if active_filters.any? %>
|
|
102
|
+
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100"><%= t('infotable.table.empty_state.filtered_title') %></h3>
|
|
103
|
+
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400"><%= t('infotable.table.empty_state.filtered_description') %></p>
|
|
104
|
+
<button type="button" data-action="click->infotable--table#clearAllFilters" data-empty-clear-filters
|
|
105
|
+
class="mt-4 inline-flex items-center rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500"><%= t('infotable.filters.active.clear_all') %></button>
|
|
106
|
+
<% else %>
|
|
107
|
+
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100"><%= t('infotable.table.empty_state.title') %></h3>
|
|
108
|
+
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400"><%= t('infotable.table.empty_state.description') %></p>
|
|
109
|
+
<% end %>
|
|
101
110
|
</div>
|
|
102
111
|
<% end %>
|
|
103
112
|
|
|
@@ -105,7 +114,7 @@
|
|
|
105
114
|
|
|
106
115
|
<%# Pagination - Only visible when infinite scroll is disabled %>
|
|
107
116
|
<% unless @infinite_scroll_enabled %>
|
|
108
|
-
<div class="border-t border-gray-200 bg-white px-6 py-4">
|
|
117
|
+
<div class="border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-6 py-4">
|
|
109
118
|
<%= render "infotable/tables/pagination", pagy: @pagy, table_name: params[:table_name] %>
|
|
110
119
|
</div>
|
|
111
120
|
<% end %>
|
data/config/locales/en.yml
CHANGED
|
@@ -5,6 +5,8 @@ en:
|
|
|
5
5
|
export_csv: "Export CSV"
|
|
6
6
|
filters: "Filters"
|
|
7
7
|
exporting: "Exporting..."
|
|
8
|
+
refresh: "Refresh"
|
|
9
|
+
columns: "Columns"
|
|
8
10
|
sort_hint: "Click to sort, Shift+click to add a column to the sort"
|
|
9
11
|
loading:
|
|
10
12
|
initial: "Loading data..."
|
|
@@ -13,6 +15,8 @@ en:
|
|
|
13
15
|
empty_state:
|
|
14
16
|
title: "No data found"
|
|
15
17
|
description: "Get started by adding some records to see them here."
|
|
18
|
+
filtered_title: "No rows match your filters"
|
|
19
|
+
filtered_description: "Remove a filter or search for something else."
|
|
16
20
|
|
|
17
21
|
drawer:
|
|
18
22
|
label: "Filters and columns"
|
|
@@ -39,6 +43,10 @@ en:
|
|
|
39
43
|
clear_all: "Clear All"
|
|
40
44
|
apply_filters: "Apply Filters"
|
|
41
45
|
clear_filter: "Clear this filter"
|
|
46
|
+
active:
|
|
47
|
+
search: "Search: \u201c%{query}\u201d"
|
|
48
|
+
remove: "Remove filter %{filter}"
|
|
49
|
+
clear_all: "Clear all filters"
|
|
42
50
|
from: "From"
|
|
43
51
|
to: "To"
|
|
44
52
|
min: "Min"
|
|
@@ -60,6 +68,19 @@ en:
|
|
|
60
68
|
ends_with: "Ends with"
|
|
61
69
|
is_empty: "Is empty"
|
|
62
70
|
is_not_empty: "Is not empty"
|
|
71
|
+
range_ops:
|
|
72
|
+
between: "Between"
|
|
73
|
+
number_ops:
|
|
74
|
+
eq: "Equals"
|
|
75
|
+
neq: "Does not equal"
|
|
76
|
+
gt: "Greater than"
|
|
77
|
+
gte: "Greater than or equal"
|
|
78
|
+
lt: "Less than"
|
|
79
|
+
lte: "Less than or equal"
|
|
80
|
+
date_ops:
|
|
81
|
+
"on": "On"
|
|
82
|
+
before: "Before"
|
|
83
|
+
after: "After"
|
|
63
84
|
|
|
64
85
|
formatters:
|
|
65
86
|
ago: "%{time} ago"
|
|
@@ -73,6 +94,8 @@ en:
|
|
|
73
94
|
clear: "Clear selection"
|
|
74
95
|
select_all: "Select all rows"
|
|
75
96
|
select_row: "Select row"
|
|
97
|
+
select_all_matching: "Select all %{count} matching"
|
|
98
|
+
truncated: "Only the first %{max} rows are selected"
|
|
76
99
|
|
|
77
100
|
search:
|
|
78
101
|
placeholder: "Search..."
|
data/config/locales/pl.yml
CHANGED
|
@@ -5,6 +5,8 @@ pl:
|
|
|
5
5
|
export_csv: "Eksportuj CSV"
|
|
6
6
|
filters: "Filtry"
|
|
7
7
|
exporting: "Eksportowanie..."
|
|
8
|
+
refresh: "Odśwież"
|
|
9
|
+
columns: "Kolumny"
|
|
8
10
|
sort_hint: "Kliknij, aby sortować; Shift+klik dodaje kolumnę do sortowania"
|
|
9
11
|
loading:
|
|
10
12
|
initial: "Ładowanie danych..."
|
|
@@ -13,6 +15,8 @@ pl:
|
|
|
13
15
|
empty_state:
|
|
14
16
|
title: "Nie znaleziono danych"
|
|
15
17
|
description: "Rozpocznij od dodania rekordów, aby zobaczyć je tutaj."
|
|
18
|
+
filtered_title: "Żaden wiersz nie pasuje do filtrów"
|
|
19
|
+
filtered_description: "Usuń filtr lub wyszukaj coś innego."
|
|
16
20
|
|
|
17
21
|
drawer:
|
|
18
22
|
label: "Filtry i kolumny"
|
|
@@ -39,6 +43,10 @@ pl:
|
|
|
39
43
|
clear_all: "Wyczyść wszystkie"
|
|
40
44
|
apply_filters: "Zastosuj filtry"
|
|
41
45
|
clear_filter: "Wyczyść ten filtr"
|
|
46
|
+
active:
|
|
47
|
+
search: "Szukaj: \u201e%{query}\u201d"
|
|
48
|
+
remove: "Usuń filtr %{filter}"
|
|
49
|
+
clear_all: "Wyczyść wszystkie filtry"
|
|
42
50
|
from: "Od"
|
|
43
51
|
to: "Do"
|
|
44
52
|
min: "Min"
|
|
@@ -60,6 +68,19 @@ pl:
|
|
|
60
68
|
ends_with: "Kończy się na"
|
|
61
69
|
is_empty: "Jest puste"
|
|
62
70
|
is_not_empty: "Nie jest puste"
|
|
71
|
+
range_ops:
|
|
72
|
+
between: "Pomiędzy"
|
|
73
|
+
number_ops:
|
|
74
|
+
eq: "Równa się"
|
|
75
|
+
neq: "Różne od"
|
|
76
|
+
gt: "Większe niż"
|
|
77
|
+
gte: "Większe lub równe"
|
|
78
|
+
lt: "Mniejsze niż"
|
|
79
|
+
lte: "Mniejsze lub równe"
|
|
80
|
+
date_ops:
|
|
81
|
+
"on": "W dniu"
|
|
82
|
+
before: "Przed"
|
|
83
|
+
after: "Po"
|
|
63
84
|
|
|
64
85
|
formatters:
|
|
65
86
|
ago: "%{time} temu"
|
|
@@ -72,6 +93,8 @@ pl:
|
|
|
72
93
|
selected: "Zaznaczono: %{count}"
|
|
73
94
|
clear: "Wyczyść zaznaczenie"
|
|
74
95
|
select_all: "Zaznacz wszystkie wiersze"
|
|
96
|
+
select_all_matching: "Zaznacz wszystkie pasujące (%{count})"
|
|
97
|
+
truncated: "Zaznaczono tylko pierwsze %{max} wierszy"
|
|
75
98
|
select_row: "Zaznacz wiersz"
|
|
76
99
|
|
|
77
100
|
search:
|
data/config/routes.rb
CHANGED
|
@@ -3,4 +3,5 @@ Infotable::Engine.routes.draw do
|
|
|
3
3
|
get "tables/:table_name", to: "tables#show", as: :table
|
|
4
4
|
get "tables/:table_name/data", to: "tables#data", as: :table_data
|
|
5
5
|
get "tables/:table_name/export", to: "tables#export", as: :table_export
|
|
6
|
+
get "tables/:table_name/ids", to: "tables#ids", as: :table_ids
|
|
6
7
|
end
|
data/lib/infotable/base.rb
CHANGED
|
@@ -116,6 +116,21 @@ module Infotable
|
|
|
116
116
|
record.public_send(@checkbox_settings[:id_method])
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
# Ids of every row the query matches (the "select all matching" button), at most `limit`.
|
|
120
|
+
# Plucks when the id method is a plain column of an ungrouped query, loads records otherwise.
|
|
121
|
+
def checkbox_ids(query, limit = nil)
|
|
122
|
+
method = @checkbox_settings[:id_method]
|
|
123
|
+
scope = query.reorder(nil)
|
|
124
|
+
scope = scope.limit(limit) if limit
|
|
125
|
+
model = query.model
|
|
126
|
+
|
|
127
|
+
if model.column_names.include?(method.to_s) && scope.group_values.empty? && scope.having_clause.empty?
|
|
128
|
+
scope.pluck(model.arel_table[method])
|
|
129
|
+
else
|
|
130
|
+
scope.map { |record| checkbox_value(record) }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
119
134
|
# Per-table access check. Block receives the controller; falsy => 403.
|
|
120
135
|
# authorize { |controller| controller.current_user&.admin? }
|
|
121
136
|
def authorize(&block)
|
|
@@ -157,6 +172,7 @@ module Infotable
|
|
|
157
172
|
searchable: options.fetch(:searchable, false),
|
|
158
173
|
filterable: options.fetch(:filterable, false),
|
|
159
174
|
visible: options.fetch(:visible, true),
|
|
175
|
+
exportable: options.fetch(:exportable, !%i[action actions].include?(type)), # false keeps the column out of CSV exports
|
|
160
176
|
pinned: options[:pinned], # :left, :right, or nil
|
|
161
177
|
virtual: options.fetch(:virtual, false), # Virtual/computed columns (aggregates, subqueries)
|
|
162
178
|
virtual_sql: options[:virtual_sql], # SQL expression for aggregate columns (used in HAVING clause)
|
|
@@ -322,6 +338,26 @@ module Infotable
|
|
|
322
338
|
@filters_button_settings[:visible] = visible
|
|
323
339
|
end
|
|
324
340
|
|
|
341
|
+
# Where the controls live. :header (default) is the bar above the table with title, search,
|
|
342
|
+
# export and filters buttons. :rail hides that bar and puts filters, columns, export and
|
|
343
|
+
# refresh on a slim vertical rail at the table's right edge; search moves into the drawer.
|
|
344
|
+
# @example
|
|
345
|
+
# toolbar :rail
|
|
346
|
+
def toolbar(mode)
|
|
347
|
+
unless [ :header, :rail ].include?(mode)
|
|
348
|
+
raise ArgumentError, "Invalid toolbar: #{mode}. Must be one of header, rail"
|
|
349
|
+
end
|
|
350
|
+
@toolbar_setting = mode
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def toolbar_mode
|
|
354
|
+
@toolbar_setting || :header
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def rail_toolbar?
|
|
358
|
+
toolbar_mode == :rail
|
|
359
|
+
end
|
|
360
|
+
|
|
325
361
|
# Check if filters button is visible
|
|
326
362
|
def filters_button_visible?
|
|
327
363
|
@filters_button_settings ||= { visible: true }
|
|
@@ -444,6 +480,36 @@ module Infotable
|
|
|
444
480
|
columns.select { |col| col[:visible] }
|
|
445
481
|
end
|
|
446
482
|
|
|
483
|
+
# Columns a CSV export may contain (action columns and `exportable: false` are left out)
|
|
484
|
+
def exportable_columns
|
|
485
|
+
columns.select { |col| col[:exportable] }
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# Which columns the CSV export contains and in which order, when the user has not chosen:
|
|
489
|
+
# export_columns :name, :email, :created_at
|
|
490
|
+
# Without it the export uses the visible, exportable columns in table order. With it, the
|
|
491
|
+
# list is also the pool the user can pick from (visible_columns can only narrow and reorder it).
|
|
492
|
+
def export_columns(*names)
|
|
493
|
+
@export_column_names = names.flatten.map(&:to_sym)
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def export_column_names
|
|
497
|
+
@export_column_names
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# The columns to export, as column definitions in export order. `requested` is the list of
|
|
501
|
+
# names the browser sends (the user's visible columns in their on-screen order).
|
|
502
|
+
def columns_for_export(requested = nil)
|
|
503
|
+
pool = exportable_columns
|
|
504
|
+
pool = export_column_names.map { |name| pool.find { |col| col[:name] == name } || raise(ArgumentError, "export_columns: unknown or non-exportable column #{name.inspect} in #{self.name}") } if export_column_names
|
|
505
|
+
|
|
506
|
+
by_name = pool.index_by { |col| col[:name].to_s }
|
|
507
|
+
picked = Array(requested).map { |name| by_name[name.to_s] }.compact.uniq
|
|
508
|
+
return picked if picked.any?
|
|
509
|
+
|
|
510
|
+
export_column_names ? pool : pool.select { |col| col[:visible] }
|
|
511
|
+
end
|
|
512
|
+
|
|
447
513
|
# Get sortable columns
|
|
448
514
|
def sortable_columns
|
|
449
515
|
columns.select { |col| col[:sortable] }
|
|
@@ -14,7 +14,8 @@ module Infotable
|
|
|
14
14
|
:empty_value,
|
|
15
15
|
:parent_controller,
|
|
16
16
|
:max_filter_options,
|
|
17
|
-
:url_state
|
|
17
|
+
:url_state,
|
|
18
|
+
:max_select_all
|
|
18
19
|
|
|
19
20
|
def initialize
|
|
20
21
|
@default_per_page = 25
|
|
@@ -30,6 +31,7 @@ module Infotable
|
|
|
30
31
|
@nil_value = "-" # Default placeholder for nil values
|
|
31
32
|
@empty_value = "-" # Default placeholder for empty strings
|
|
32
33
|
@url_state = true # mirror table state in the address bar
|
|
34
|
+
@max_select_all = 10_000 # "select all matching" collects at most this many ids (nil = no limit)
|
|
33
35
|
@max_filter_options = 500 # select filters render at most this many <option>s (nil = no limit)
|
|
34
36
|
@parent_controller = "ActionController::Base" # Set to "ApplicationController" to inherit host auth/filters
|
|
35
37
|
end
|
|
@@ -72,7 +72,7 @@ module Infotable
|
|
|
72
72
|
action[:label] || action[:icon] || view_context.t("infotable.formatters.action"),
|
|
73
73
|
resolve_url(action[:url], record),
|
|
74
74
|
role: "menuitem",
|
|
75
|
-
class: "block px-3 py-1.5 text-sm whitespace-nowrap #{link_classes(action[:color])} hover:bg-gray-50",
|
|
75
|
+
class: "block px-3 py-1.5 text-sm whitespace-nowrap #{link_classes(action[:color])} hover:bg-gray-50 dark:hover:bg-gray-800",
|
|
76
76
|
data: link_data(action[:data] || {}, action[:method] || :get, action[:confirm])
|
|
77
77
|
)
|
|
78
78
|
end
|
|
@@ -81,10 +81,10 @@ module Infotable
|
|
|
81
81
|
# <details> gives open/close and keyboard support without JS
|
|
82
82
|
content_tag(:details, class: "relative inline-block text-left") do
|
|
83
83
|
summary = content_tag(:summary, "\u22EF".html_safe,
|
|
84
|
-
class: "list-none cursor-pointer inline-flex items-center px-2 py-1 rounded-md border border-gray-300 bg-white text-gray-600 hover:bg-gray-50",
|
|
84
|
+
class: "list-none cursor-pointer inline-flex items-center px-2 py-1 rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800",
|
|
85
85
|
"aria-label": view_context.t("infotable.formatters.action"), "aria-haspopup": "menu")
|
|
86
86
|
menu = content_tag(:div, role: "menu",
|
|
87
|
-
class: "absolute right-0 z-30 mt-1 min-w-36 rounded-md border border-gray-200 bg-white py-1 shadow-lg") do
|
|
87
|
+
class: "absolute right-0 z-30 mt-1 min-w-36 rounded-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 py-1 shadow-lg") do
|
|
88
88
|
safe_join(actions.map { |action| render_menu_item(action, record) })
|
|
89
89
|
end
|
|
90
90
|
summary + menu
|
|
@@ -110,7 +110,7 @@ module Infotable
|
|
|
110
110
|
when "success"
|
|
111
111
|
"border-green-600 bg-green-600 text-white hover:bg-green-700"
|
|
112
112
|
else
|
|
113
|
-
"border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
|
|
113
|
+
"border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
"#{base} #{color_classes}"
|
|
@@ -119,11 +119,11 @@ module Infotable
|
|
|
119
119
|
def link_classes(color = nil)
|
|
120
120
|
case color&.to_s
|
|
121
121
|
when "danger"
|
|
122
|
-
"text-red-600 hover:text-red-900"
|
|
122
|
+
"text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
|
|
123
123
|
when "success"
|
|
124
|
-
"text-green-600 hover:text-green-900"
|
|
124
|
+
"text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300"
|
|
125
125
|
else
|
|
126
|
-
"text-indigo-600 hover:text-indigo-900"
|
|
126
|
+
"text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-200"
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
|
|
@@ -10,8 +10,8 @@ module Infotable
|
|
|
10
10
|
class AvatarFormatter < BaseFormatter
|
|
11
11
|
SIZES = { sm: "h-6 w-6 text-xs", md: "h-8 w-8 text-sm", lg: "h-10 w-10 text-base" }.freeze
|
|
12
12
|
COLORS = [
|
|
13
|
-
"bg-indigo-100 text-indigo-700", "bg-green-100 text-green-700", "bg-yellow-100 text-yellow-700",
|
|
14
|
-
"bg-pink-100 text-pink-700", "bg-blue-100 text-blue-700", "bg-purple-100 text-purple-700"
|
|
13
|
+
"bg-indigo-100 dark:bg-indigo-900/40 text-indigo-700 dark:text-indigo-300", "bg-green-100 dark:bg-green-900/40 text-green-700 dark:text-green-300", "bg-yellow-100 dark:bg-yellow-900/40 text-yellow-700 dark:text-yellow-300",
|
|
14
|
+
"bg-pink-100 dark:bg-pink-900/40 text-pink-700 dark:text-pink-300", "bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300", "bg-purple-100 dark:bg-purple-900/40 text-purple-700 dark:text-purple-300"
|
|
15
15
|
].freeze
|
|
16
16
|
|
|
17
17
|
def format(value, record, options = {})
|
|
@@ -54,23 +54,23 @@ module Infotable
|
|
|
54
54
|
|
|
55
55
|
color_classes = case color.to_s
|
|
56
56
|
when "green"
|
|
57
|
-
"bg-green-100 text-green-800"
|
|
57
|
+
"bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-300"
|
|
58
58
|
when "red"
|
|
59
|
-
"bg-red-100 text-red-800"
|
|
59
|
+
"bg-red-100 dark:bg-red-900/40 text-red-800 dark:text-red-300"
|
|
60
60
|
when "yellow"
|
|
61
|
-
"bg-yellow-100 text-yellow-800"
|
|
61
|
+
"bg-yellow-100 dark:bg-yellow-900/40 text-yellow-800 dark:text-yellow-300"
|
|
62
62
|
when "blue"
|
|
63
|
-
"bg-blue-100 text-blue-800"
|
|
63
|
+
"bg-blue-100 dark:bg-blue-900/40 text-blue-800 dark:text-blue-300"
|
|
64
64
|
when "indigo"
|
|
65
|
-
"bg-indigo-100 text-indigo-800"
|
|
65
|
+
"bg-indigo-100 dark:bg-indigo-900/40 text-indigo-800 dark:text-indigo-300"
|
|
66
66
|
when "purple"
|
|
67
|
-
"bg-purple-100 text-purple-800"
|
|
67
|
+
"bg-purple-100 dark:bg-purple-900/40 text-purple-800 dark:text-purple-300"
|
|
68
68
|
when "pink"
|
|
69
|
-
"bg-pink-100 text-pink-800"
|
|
69
|
+
"bg-pink-100 dark:bg-pink-900/40 text-pink-800 dark:text-pink-300"
|
|
70
70
|
when "cyan"
|
|
71
|
-
"bg-cyan-100 text-cyan-800"
|
|
71
|
+
"bg-cyan-100 dark:bg-cyan-900/40 text-cyan-800 dark:text-cyan-300"
|
|
72
72
|
else # gray
|
|
73
|
-
"bg-gray-100 text-gray-800"
|
|
73
|
+
"bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200"
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
"#{base} #{size_classes} #{color_classes}"
|
|
@@ -20,9 +20,9 @@ module Infotable
|
|
|
20
20
|
|
|
21
21
|
def format_as_badge(value, true_text, false_text)
|
|
22
22
|
if value
|
|
23
|
-
content_tag(:span, true_text, class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800")
|
|
23
|
+
content_tag(:span, true_text, class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-300")
|
|
24
24
|
else
|
|
25
|
-
content_tag(:span, false_text, class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800")
|
|
25
|
+
content_tag(:span, false_text, class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200")
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -10,7 +10,7 @@ module Infotable
|
|
|
10
10
|
# Without url: the value itself is the URL (handy for website columns); mailto:/tel: work too.
|
|
11
11
|
class LinkFormatter < BaseFormatter
|
|
12
12
|
ALLOWED_SCHEMES = %w[http https mailto tel].freeze
|
|
13
|
-
DEFAULT_CLASSES = "text-indigo-600 hover:text-indigo-800 hover:underline".freeze
|
|
13
|
+
DEFAULT_CLASSES = "text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-200 hover:underline".freeze
|
|
14
14
|
|
|
15
15
|
def format(value, record, options = {})
|
|
16
16
|
label = resolve(options[:label], record) || value
|
|
@@ -19,7 +19,7 @@ module Infotable
|
|
|
19
19
|
|
|
20
20
|
width = number.clamp(0, 100).round
|
|
21
21
|
bar = content_tag(:div, content_tag(:div, "", class: "h-full rounded-full bg-indigo-500", style: "width: #{width}%"),
|
|
22
|
-
class: "h-1.5 w-16 rounded-full bg-gray-200 flex-none", role: "presentation")
|
|
22
|
+
class: "h-1.5 w-16 rounded-full bg-gray-200 dark:bg-gray-700 flex-none", role: "presentation")
|
|
23
23
|
content_tag(:div, safe_join([ content_tag(:span, text, class: "tabular-nums"), bar ]), class: "flex items-center gap-2")
|
|
24
24
|
end
|
|
25
25
|
end
|
data/lib/infotable/version.rb
CHANGED