lesli_view 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/lesli_view/charts/bar.rb +10 -0
- data/lib/lesli_view/charts/general.html.erb +38 -52
- data/lib/lesli_view/charts/general.rb +69 -30
- data/lib/lesli_view/charts/line.rb +16 -0
- data/lib/lesli_view/components/header.html.erb +48 -23
- data/lib/lesli_view/components/header.rb +35 -2
- data/lib/lesli_view/components/tabs.html.erb +59 -26
- data/lib/lesli_view/components/tabs.rb +7 -7
- data/lib/lesli_view/components/timeline.html.erb +28 -25
- data/lib/lesli_view/components/toolbar.css +52 -0
- data/lib/lesli_view/components/toolbar.html.erb +36 -21
- data/lib/lesli_view/components/toolbar.rb +41 -4
- data/lib/lesli_view/elements/avatar.html.erb +3 -3
- data/lib/lesli_view/elements/avatar.rb +8 -8
- data/lib/lesli_view/elements/button.css +357 -0
- data/lib/lesli_view/elements/button.html.erb +15 -12
- data/lib/lesli_view/elements/button.rb +74 -34
- data/lib/lesli_view/elements/empty.html.erb +31 -6
- data/lib/lesli_view/elements/empty.rb +8 -1
- data/lib/lesli_view/elements/table-chart.html.erb +42 -0
- data/lib/lesli_view/elements/table.css +91 -0
- data/lib/lesli_view/elements/table.html.erb +24 -21
- data/lib/lesli_view/elements/table.rb +67 -17
- data/lib/lesli_view/forms/builder.rb +16 -3
- data/lib/lesli_view/forms/builder_horizontal.rb +34 -10
- data/lib/lesli_view/forms/fields.rb +103 -70
- data/lib/lesli_view/forms/fieldset.rb +3 -3
- data/lib/lesli_view/forms/form.css +171 -0
- data/lib/lesli_view/forms/form.scss +2 -54
- data/lib/lesli_view/forms/inputs.rb +27 -15
- data/lib/lesli_view/items/activities.html.erb +4 -6
- data/lib/lesli_view/items/discussion.html.erb +6 -5
- data/lib/lesli_view/items/discussions.html.erb +8 -6
- data/lib/lesli_view/items/task.html.erb +16 -11
- data/lib/lesli_view/items/tasks.html.erb +8 -13
- data/lib/lesli_view/layout/container.html.erb +8 -2
- data/lib/lesli_view/partials/engine_spec.html.erb +28 -41
- data/lib/lesli_view/version.rb +2 -2
- data/lib/lesli_view/widgets/calendar.html.erb +42 -133
- data/lib/lesli_view/widgets/calendar.rb +77 -3
- data/lib/lesli_view/widgets/chart.html.erb +25 -26
- data/lib/lesli_view/widgets/chart.rb +46 -3
- data/lib/lesli_view/widgets/count.html.erb +17 -15
- data/lib/lesli_view/widgets/count.rb +13 -1
- data/lib/lesli_view/widgets/date.html.erb +23 -21
- data/lib/lesli_view/widgets/date.rb +27 -2
- data/lib/lesli_view/widgets/weather.html.erb +40 -38
- data/lib/lesli_view/widgets/weather.rb +41 -2
- data/readme.md +185 -29
- metadata +6 -10
- data/lib/lesli_view/components/header.scss +0 -69
- data/lib/lesli_view/components/tabs.scss +0 -49
- data/lib/lesli_view/components/tabs_spec.rb +0 -0
- data/lib/lesli_view/components/timeline.scss +0 -216
- data/lib/lesli_view/components/toolbar.scss +0 -54
- data/lib/lesli_view/elements/avatar.scss +0 -50
- data/lib/lesli_view/elements/table.scss +0 -153
- data/lib/lesli_view/items/activities.scss +0 -39
- data/lib/lesli_view/items/discussions.scss +0 -47
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 256282d3858c50d538cab6dd29cfe07c2474e61da8b8f1ca131178b9a7418156
|
|
4
|
+
data.tar.gz: f554e466ed571e19aa6f905b99ff821cad077846bd60369d11f15726d96f4011
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf903d1af2c3487d35b4842222377d59d97c1a512833bb234154b7908d926787bd61531870f5f1ea25676de3425503eee46a93ce4c2d13df2427c96f74c43b1e
|
|
7
|
+
data.tar.gz: da6644cdb5dad1e382e03fd19465491068b39bf17eca2de0511ab2039b22525d84e0dce44539a434ae33fe06070a588f7fdbad7c67de0a657087b436cae8d6aa
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
=begin
|
|
2
4
|
|
|
3
5
|
Lesli
|
|
@@ -36,6 +38,14 @@ module LesliView
|
|
|
36
38
|
def type
|
|
37
39
|
"bar"
|
|
38
40
|
end
|
|
41
|
+
|
|
42
|
+
def dataset_defaults
|
|
43
|
+
{
|
|
44
|
+
backgroundColor: "rgba(14, 165, 233, 0.72)",
|
|
45
|
+
borderColor: "rgb(2, 132, 199)",
|
|
46
|
+
borderWidth: 1
|
|
47
|
+
}
|
|
48
|
+
end
|
|
39
49
|
end
|
|
40
50
|
end
|
|
41
51
|
end
|
|
@@ -1,55 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Lesli · Ruby on Rails SaaS Development Framework.
|
|
21
|
-
|
|
22
|
-
Made with ♥ by LesliTech
|
|
23
|
-
Building a better future, one line of code at a time.
|
|
24
|
-
|
|
25
|
-
@contact hello@lesli.tech
|
|
26
|
-
@website https://www.lesli.tech
|
|
27
|
-
@license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
|
|
28
|
-
|
|
29
|
-
// · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
|
|
30
|
-
// ·
|
|
31
|
-
%>
|
|
32
|
-
<script>
|
|
33
|
-
document.addEventListener("turbo:load", () => {
|
|
34
|
-
new LesliChart({
|
|
35
|
-
id: "<%= id %>",
|
|
36
|
-
type: "<%= type %>",
|
|
37
|
-
datasets: <%= raw(datasets.to_json) %>,
|
|
38
|
-
labels: <%= raw(labels.to_json) %>,
|
|
39
|
-
compact: <%= compact ? "true" : "false" %>
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
</script>
|
|
43
|
-
|
|
44
|
-
<div class="lesli-charts lesli-charts-<%= type %> section box m-0 p-2">
|
|
45
|
-
|
|
46
|
-
<% if title.present? %>
|
|
47
|
-
<%= tag.h3(title, class: (compact ? "is-size-7 has-text-centered" : "ml-2")) %>
|
|
1
|
+
<div
|
|
2
|
+
class="lesli-chart lesli-chart-<%= type %> flex h-full min-w-0 flex-col"
|
|
3
|
+
<% if title.present? %>aria-labelledby="<%= title_id %>"<% end %>>
|
|
4
|
+
|
|
5
|
+
<% if title.present? || subtitle.present? %>
|
|
6
|
+
<div class="mb-3">
|
|
7
|
+
<% if title.present? %>
|
|
8
|
+
<h3
|
|
9
|
+
id="<%= title_id %>"
|
|
10
|
+
class="<%= compact ? 'text-center text-xs font-medium text-slate-500' : 'text-base font-semibold text-slate-700' %>">
|
|
11
|
+
<%= title %>
|
|
12
|
+
</h3>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<% if subtitle.present? %>
|
|
16
|
+
<p class="mt-1 text-sm leading-5 text-slate-500"><%= subtitle %></p>
|
|
17
|
+
<% end %>
|
|
18
|
+
</div>
|
|
48
19
|
<% end %>
|
|
49
20
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<div id="<%= id %>" style="min-height:<%= height %>"></div>
|
|
21
|
+
<div
|
|
22
|
+
id="<%= id %>"
|
|
23
|
+
class="min-w-0 flex-1"
|
|
24
|
+
style="min-height: <%= height %>"></div>
|
|
55
25
|
</div>
|
|
26
|
+
|
|
27
|
+
<script data-lesli-chart-config="<%= chart_config.to_json %>">
|
|
28
|
+
(() => {
|
|
29
|
+
const script = document.currentScript;
|
|
30
|
+
const renderChart = () => {
|
|
31
|
+
if (!window.LesliChart || !script) return;
|
|
32
|
+
new window.LesliChart(JSON.parse(script.dataset.lesliChartConfig));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
if (document.readyState === "loading") {
|
|
36
|
+
document.addEventListener("turbo:load", renderChart, { once: true });
|
|
37
|
+
} else {
|
|
38
|
+
renderChart();
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
41
|
+
</script>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
=begin
|
|
2
4
|
|
|
3
5
|
Lesli
|
|
@@ -33,48 +35,62 @@ Building a better future, one line of code at a time.
|
|
|
33
35
|
module LesliView
|
|
34
36
|
module Charts
|
|
35
37
|
class General < ViewComponent::Base
|
|
36
|
-
|
|
37
38
|
attr_reader(
|
|
38
|
-
:id,
|
|
39
|
-
:title,
|
|
40
|
-
:subtitle,
|
|
41
|
-
:labels,
|
|
42
|
-
:datasets,
|
|
43
|
-
:height,
|
|
39
|
+
:id,
|
|
40
|
+
:title,
|
|
41
|
+
:subtitle,
|
|
42
|
+
:labels,
|
|
43
|
+
:datasets,
|
|
44
|
+
:height,
|
|
44
45
|
:compact
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
def initialize(
|
|
48
|
-
id:nil,
|
|
49
|
-
title:nil,
|
|
50
|
-
subtitle:nil,
|
|
51
|
-
labels:nil,
|
|
52
|
-
dataset:nil,
|
|
53
|
-
datasets:nil,
|
|
54
|
-
database_to_dataset:nil,
|
|
55
|
-
database_to_datasets:nil,
|
|
56
|
-
height:"400px",
|
|
57
|
-
compact:false
|
|
49
|
+
id: nil,
|
|
50
|
+
title: nil,
|
|
51
|
+
subtitle: nil,
|
|
52
|
+
labels: nil,
|
|
53
|
+
dataset: nil,
|
|
54
|
+
datasets: nil,
|
|
55
|
+
database_to_dataset: nil,
|
|
56
|
+
database_to_datasets: nil,
|
|
57
|
+
height: "400px",
|
|
58
|
+
compact: false
|
|
58
59
|
)
|
|
59
|
-
@id = id || "lesli-chart-#{
|
|
60
|
+
@id = id.presence || "lesli-chart-#{object_id}"
|
|
60
61
|
@title = title
|
|
61
62
|
@subtitle = subtitle
|
|
62
|
-
@labels = labels
|
|
63
|
-
@
|
|
64
|
-
@
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
@labels = Array(labels)
|
|
64
|
+
@height = normalize_height(height)
|
|
65
|
+
@compact = !!compact
|
|
66
|
+
|
|
67
|
+
source_datasets = datasets
|
|
68
|
+
source_datasets = [{ label: title, data: dataset }] unless dataset.nil?
|
|
69
|
+
source_datasets = convert_database_to_dataset(database_to_dataset) if database_to_dataset
|
|
70
|
+
source_datasets = convert_database_to_datasets(database_to_datasets) if database_to_datasets
|
|
71
|
+
@datasets = normalize_datasets(source_datasets)
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
def type
|
|
73
75
|
nil
|
|
74
76
|
end
|
|
75
77
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
+
def chart_config
|
|
79
|
+
{
|
|
80
|
+
id: id,
|
|
81
|
+
type: type,
|
|
82
|
+
datasets: datasets,
|
|
83
|
+
labels: labels,
|
|
84
|
+
compact: compact
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def title_id
|
|
89
|
+
@title_id ||= "#{id}-title"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def convert_database_to_dataset(data)
|
|
93
|
+
[{ label: title, data: data.map { |record| { x: record["xaxiskey"], y: record["yaxiskey"] } } }]
|
|
78
94
|
end
|
|
79
95
|
|
|
80
96
|
# Transforms a database query result into a structure compatible with Chart.js
|
|
@@ -94,8 +110,7 @@ module LesliView
|
|
|
94
110
|
# 'sum(agent_count) as data'
|
|
95
111
|
# )
|
|
96
112
|
#
|
|
97
|
-
def
|
|
98
|
-
|
|
113
|
+
def convert_database_to_datasets(data)
|
|
99
114
|
# Group records by series name
|
|
100
115
|
# Example: group all "Chrome" rows together
|
|
101
116
|
data
|
|
@@ -118,6 +133,30 @@ module LesliView
|
|
|
118
133
|
}
|
|
119
134
|
end
|
|
120
135
|
end
|
|
136
|
+
|
|
137
|
+
# Preserve the original public conversion helpers.
|
|
138
|
+
alias_method :database_to_dataset, :convert_database_to_dataset
|
|
139
|
+
alias_method :database_to_datasets, :convert_database_to_datasets
|
|
140
|
+
|
|
141
|
+
def dataset_defaults
|
|
142
|
+
{}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def normalize_datasets(value)
|
|
148
|
+
Array(value).map do |dataset|
|
|
149
|
+
attributes = dataset.respond_to?(:to_h) ? dataset.to_h.symbolize_keys : { data: Array(dataset) }
|
|
150
|
+
dataset_defaults.merge(attributes)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def normalize_height(value)
|
|
155
|
+
value = value.to_s
|
|
156
|
+
return value if value.match?(/\A\d+(?:\.\d+)?(?:px|rem|em|vh|vw|%)\z/)
|
|
157
|
+
|
|
158
|
+
"400px"
|
|
159
|
+
end
|
|
121
160
|
end
|
|
122
161
|
end
|
|
123
162
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
=begin
|
|
2
4
|
|
|
3
5
|
Lesli
|
|
@@ -36,6 +38,20 @@ module LesliView
|
|
|
36
38
|
def type
|
|
37
39
|
"line"
|
|
38
40
|
end
|
|
41
|
+
|
|
42
|
+
def dataset_defaults
|
|
43
|
+
{
|
|
44
|
+
backgroundColor: "rgba(14, 165, 233, 0.12)",
|
|
45
|
+
borderColor: "rgb(2, 132, 199)",
|
|
46
|
+
borderWidth: 2,
|
|
47
|
+
fill: true,
|
|
48
|
+
pointBackgroundColor: "rgb(2, 132, 199)",
|
|
49
|
+
pointBorderColor: "rgb(255, 255, 255)",
|
|
50
|
+
pointBorderWidth: 2,
|
|
51
|
+
pointRadius: 3,
|
|
52
|
+
pointHoverRadius: 5
|
|
53
|
+
}
|
|
54
|
+
end
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
57
|
end
|
|
@@ -1,29 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
|
|
2
|
+
<section
|
|
3
|
+
class="mx-auto my-8 grid w-full grid-cols-1 gap-5 md:grid-cols-[minmax(0,1fr)_auto] md:items-start"
|
|
4
|
+
<% if title.present? %>aria-labelledby="<%= title_id %>"<% end %>>
|
|
5
|
+
|
|
6
|
+
<div class="min-w-0">
|
|
7
|
+
<div class="flex items-center gap-4">
|
|
8
|
+
|
|
9
|
+
<% if back? %>
|
|
5
10
|
<%= link_to(
|
|
6
|
-
|
|
7
|
-
class: "
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
back_path,
|
|
12
|
+
class: "inline-flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl border border-sky-600 bg-white text-sky-700 shadow-sm transition hover:bg-sky-50 hover:text-sky-800 focus:outline-none focus:ring-2 focus:ring-sky-500/30",
|
|
13
|
+
aria: { label: I18n.t("actions.back", default: "Go back") },
|
|
14
|
+
data: { turbo_frame: "_top" }
|
|
15
|
+
) do %>
|
|
16
|
+
<span class="material-symbols text-[26px] leading-none" aria-hidden="true">arrow_back</span>
|
|
10
17
|
<% end %>
|
|
11
18
|
<% end %>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
|
|
20
|
+
<div class="min-w-0">
|
|
21
|
+
<% if title.present? %>
|
|
22
|
+
<h1 id="<%= title_id %>" class="text-3xl tracking-tight text-slate-700 sm:text-4xl">
|
|
23
|
+
<%= title %>
|
|
24
|
+
</h1>
|
|
25
|
+
<% end %>
|
|
26
|
+
|
|
27
|
+
<% if subtitle.present? %>
|
|
28
|
+
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-500 sm:text-base">
|
|
29
|
+
<%= subtitle %>
|
|
30
|
+
</p>
|
|
31
|
+
<% end %>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
15
34
|
</div>
|
|
16
|
-
<% if subtitle.present? %>
|
|
17
|
-
<p><%= subtitle %></p>
|
|
18
|
-
<% end %>
|
|
19
|
-
</div>
|
|
20
|
-
<div class="lesli-element-header-items is-flex is-flex-grow-1 is-justify-content-end">
|
|
21
|
-
<% if new_path %>
|
|
22
|
-
<%= render ::LesliView::Elements::Button.new(
|
|
23
|
-
"Nuevo", url:new_path, icon:"add", solid:true
|
|
24
|
-
) %>
|
|
25
|
-
<% end %>
|
|
26
|
-
<%= content %>
|
|
27
35
|
</div>
|
|
28
|
-
</section>
|
|
29
36
|
|
|
37
|
+
<% if actions? %>
|
|
38
|
+
<div
|
|
39
|
+
class="flex flex-wrap items-center gap-3 md:justify-self-end"
|
|
40
|
+
role="group"
|
|
41
|
+
aria-label="<%= I18n.t('actions.label', default: 'Page actions') %>">
|
|
42
|
+
<% if new_path.present? %>
|
|
43
|
+
<%= render ::LesliView::Elements::Button.new(
|
|
44
|
+
new_button_label,
|
|
45
|
+
url: new_path,
|
|
46
|
+
icon: "add",
|
|
47
|
+
solid: true
|
|
48
|
+
) %>
|
|
49
|
+
<% end %>
|
|
50
|
+
|
|
51
|
+
<%= content if content? %>
|
|
52
|
+
</div>
|
|
53
|
+
<% end %>
|
|
54
|
+
</section>
|
|
@@ -35,13 +35,46 @@ Building a better future, one line of code at a time.
|
|
|
35
35
|
module LesliView
|
|
36
36
|
module Components
|
|
37
37
|
class Header < ViewComponent::Base
|
|
38
|
-
attr_reader :title, :subtitle, :back, :new_path
|
|
38
|
+
attr_reader :title, :subtitle, :back, :new_path, :new_label
|
|
39
|
+
|
|
40
|
+
def initialize(title, subtitle = nil, new_path: nil, new_label: nil, back: nil)
|
|
41
|
+
# Support the legacy positional options hash:
|
|
42
|
+
# Header.new("Title", back: path)
|
|
43
|
+
if subtitle.is_a?(Hash)
|
|
44
|
+
legacy_options = subtitle.symbolize_keys
|
|
45
|
+
subtitle = legacy_options[:subtitle]
|
|
46
|
+
new_path ||= legacy_options[:new_path]
|
|
47
|
+
new_label ||= legacy_options[:new_label]
|
|
48
|
+
back ||= legacy_options[:back]
|
|
49
|
+
end
|
|
39
50
|
|
|
40
|
-
def initialize(title, subtitle="", new_path:nil, back:nil)
|
|
41
51
|
@title = title
|
|
42
52
|
@subtitle = subtitle
|
|
43
53
|
@back = back
|
|
44
54
|
@new_path = new_path
|
|
55
|
+
@new_label = new_label
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def back?
|
|
59
|
+
back.present?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def back_path
|
|
63
|
+
return back unless back == true
|
|
64
|
+
|
|
65
|
+
request.referer.presence || "/"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def new_button_label
|
|
69
|
+
new_label.presence || I18n.t("actions.new", default: "New")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def actions?
|
|
73
|
+
new_path.present? || content?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def title_id
|
|
77
|
+
@title_id ||= "lesli-header-title-#{object_id}"
|
|
45
78
|
end
|
|
46
79
|
end
|
|
47
80
|
end
|
|
@@ -1,34 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
<% tabs.each_with_index { |tab, index| tab.set_tab_id(tab.tab_id, index) } %>
|
|
2
|
+
<% initial_tab = active_tab.presence || tabs.first&.tab_id %>
|
|
3
|
+
|
|
4
|
+
<div
|
|
5
|
+
class="lesli-tabs min-w-0<%= " flex items-start gap-6" if vertical %>"
|
|
6
|
+
x-data="{
|
|
7
|
+
activeTab: '<%= j(initial_tab.to_s) %>',
|
|
8
|
+
focusTab(event, offset) {
|
|
9
|
+
const tabs = Array.from(event.currentTarget.closest('[role=tablist]').querySelectorAll('[role=tab]'));
|
|
10
|
+
const currentIndex = tabs.indexOf(event.currentTarget);
|
|
11
|
+
const nextTab = tabs[(currentIndex + offset + tabs.length) % tabs.length];
|
|
12
|
+
|
|
13
|
+
nextTab.focus();
|
|
14
|
+
nextTab.click();
|
|
15
|
+
}
|
|
16
|
+
}">
|
|
17
|
+
|
|
18
|
+
<div class="<%= vertical ? "w-48 shrink-0" : "overflow-x-auto" %>">
|
|
19
|
+
<div
|
|
20
|
+
class="<%= vertical ? "flex w-full flex-col border-r border-slate-200" : "flex min-w-max border-b border-slate-200" %>"
|
|
21
|
+
role="tablist"
|
|
22
|
+
aria-orientation="<%= vertical ? "vertical" : "horizontal" %>">
|
|
23
|
+
|
|
24
|
+
<% tabs.each do |tab| %>
|
|
25
|
+
<button
|
|
26
|
+
id="<%= tab.tab_id %>-tab"
|
|
27
|
+
class="inline-flex min-h-11 cursor-pointer items-center gap-2 whitespace-nowrap px-4 py-2.5 text-sm font-medium transition-colors duration-150 focus:outline-none focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-inset <%= vertical ? "-mr-px w-full border-r-2 text-left" : "-mb-px justify-center border-b-2" %>"
|
|
28
|
+
type="button"
|
|
29
|
+
role="tab"
|
|
30
|
+
aria-controls="<%= tab.tab_id %>"
|
|
31
|
+
:aria-selected="activeTab === '<%= j(tab.tab_id) %>'"
|
|
32
|
+
:tabindex="activeTab === '<%= j(tab.tab_id) %>' ? 0 : -1"
|
|
33
|
+
:class="{
|
|
34
|
+
'border-blue-600 text-blue-700': activeTab === '<%= j(tab.tab_id) %>',
|
|
35
|
+
'border-transparent text-slate-500 hover:text-slate-700': activeTab !== '<%= j(tab.tab_id) %>'
|
|
36
|
+
}"
|
|
37
|
+
@click="activeTab = '<%= j(tab.tab_id) %>'"
|
|
38
|
+
@keydown.right.prevent="focusTab($event, 1)"
|
|
39
|
+
@keydown.down.prevent="focusTab($event, 1)"
|
|
40
|
+
@keydown.left.prevent="focusTab($event, -1)"
|
|
41
|
+
@keydown.up.prevent="focusTab($event, -1)">
|
|
42
|
+
|
|
43
|
+
<% if tab.icon %>
|
|
44
|
+
<span class="material-symbols-outlined text-xl leading-none" aria-hidden="true">
|
|
45
|
+
<%= tab.icon %>
|
|
19
46
|
</span>
|
|
20
|
-
|
|
21
|
-
|
|
47
|
+
<% end %>
|
|
48
|
+
|
|
49
|
+
<span><%= tab.title %></span>
|
|
50
|
+
</button>
|
|
22
51
|
<% end %>
|
|
23
|
-
</
|
|
52
|
+
</div>
|
|
24
53
|
</div>
|
|
25
|
-
|
|
26
|
-
|
|
54
|
+
|
|
55
|
+
<div class="min-w-0 flex-1<%= " pt-5" unless vertical %>">
|
|
56
|
+
<% tabs.each do |tab| %>
|
|
27
57
|
<div
|
|
28
58
|
id="<%= tab.tab_id %>"
|
|
29
|
-
class="
|
|
30
|
-
|
|
31
|
-
|
|
59
|
+
class="min-w-0"
|
|
60
|
+
role="tabpanel"
|
|
61
|
+
aria-labelledby="<%= tab.tab_id %>-tab"
|
|
62
|
+
tabindex="0"
|
|
63
|
+
x-show="activeTab === '<%= j(tab.tab_id) %>'"
|
|
64
|
+
x-transition.opacity.duration.150ms
|
|
32
65
|
x-cloak>
|
|
33
66
|
<%= tab %>
|
|
34
67
|
</div>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
=begin
|
|
2
|
-
<%= render LesliView::
|
|
3
|
-
<% tabs.with_tab(
|
|
2
|
+
<%= render LesliView::Components::Tabs.new(active_tab: "tab1") do |tabs| %>
|
|
3
|
+
<% tabs.with_tab(id: "tab1", title: "Tab 1") do %>
|
|
4
4
|
hola
|
|
5
5
|
<% end %>
|
|
6
|
-
<% tabs.with_tab(
|
|
7
|
-
<% tabs.with_tab(
|
|
8
|
-
<% tabs.with_tab(
|
|
9
|
-
<% tabs.with_tab(
|
|
10
|
-
<% tabs.with_tab(
|
|
6
|
+
<% tabs.with_tab(id: "tab2", title: "Tab 2") %>
|
|
7
|
+
<% tabs.with_tab(id: "tab3", title: "Tab 3") %>
|
|
8
|
+
<% tabs.with_tab(id: "tab4", title: "Tab 4", icon: "save") %>
|
|
9
|
+
<% tabs.with_tab(id: "tab5", title: "Tab 5", icon: "edit") %>
|
|
10
|
+
<% tabs.with_tab(id: "tab6", title: "Tab 6", icon: "delete") %>
|
|
11
11
|
<% end %>
|
|
12
12
|
=end
|
|
13
13
|
|
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
<div class="lesli-timelines">
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
<div class="lesli-timelines py-6">
|
|
2
|
+
<ul class="m-0 w-full list-none p-0">
|
|
3
|
+
<% activities.each do |activity| %>
|
|
4
|
+
<li class="group grid grid-cols-[1.5rem_minmax(0,1fr)] gap-x-4 md:grid-cols-[minmax(8rem,auto)_1.5rem_minmax(0,1fr)] md:gap-x-6">
|
|
5
|
+
<div class="col-start-2 mb-2 min-w-0 text-xs font-bold uppercase tracking-[0.2em] text-slate-500 md:col-start-1 md:row-start-1 md:mb-0 md:pt-1 md:text-right">
|
|
6
|
+
<span><%= activity['date'] %></span>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="relative col-start-1 row-span-2 row-start-1 flex justify-center md:col-start-2">
|
|
10
|
+
<span
|
|
11
|
+
class="material-symbols-outlined relative z-10 bg-white text-xl leading-none text-[var(--lesli-color-primary)]"
|
|
12
|
+
aria-hidden="true">
|
|
13
|
+
<%= icons.dig(activity['icon']&.to_sym) || 'radio_button_checked' %>
|
|
14
|
+
</span>
|
|
15
|
+
<span class="absolute top-7 bottom-0 left-1/2 w-px -translate-x-1/2 bg-slate-300 group-last:hidden" aria-hidden="true"></span>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="col-start-2 row-start-2 min-w-0 pb-10 md:col-start-3 md:row-start-1">
|
|
19
|
+
<h6 class="text-base font-semibold text-slate-800">
|
|
20
|
+
<%= activity['operation'] %>
|
|
21
|
+
</h6>
|
|
22
|
+
<p class="mt-1 text-sm leading-6 text-slate-600">
|
|
23
|
+
<%= activity['description'] %>
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
</li>
|
|
27
|
+
<% end %>
|
|
28
|
+
</ul>
|
|
26
29
|
</div>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* Tailwind styles for LesliView::Components::Toolbar. */
|
|
2
|
+
|
|
3
|
+
@layer components {
|
|
4
|
+
.lesli-toolbar {
|
|
5
|
+
@apply mb-6 flex w-full flex-col gap-2 sm:flex-row sm:items-center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.lesli-toolbar__form {
|
|
9
|
+
@apply flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-center;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.lesli-toolbar__search {
|
|
13
|
+
@apply relative min-w-0 flex-1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.lesli-toolbar__input {
|
|
17
|
+
@apply h-10 w-full rounded-lg border border-slate-300 bg-white py-2 pl-10 pr-10 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 hover:border-slate-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500/10;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.lesli-toolbar__search-icon {
|
|
21
|
+
@apply pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-lg leading-none text-slate-400;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.lesli-toolbar__clear {
|
|
25
|
+
@apply absolute right-1.5 top-1/2 inline-flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-md text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/30;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.lesli-toolbar__clear .material-symbols {
|
|
29
|
+
@apply text-lg leading-none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.lesli-toolbar__filters {
|
|
33
|
+
@apply flex flex-wrap items-center gap-2;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.lesli-toolbar__filters select,
|
|
37
|
+
.lesli-toolbar__filters input {
|
|
38
|
+
@apply h-10 rounded-lg border border-slate-300 bg-white px-3 text-sm text-slate-700 outline-none transition hover:border-slate-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500/10;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.lesli-toolbar__submit {
|
|
42
|
+
@apply inline-flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-lg border border-slate-300 bg-white text-slate-500 transition hover:border-sky-300 hover:bg-sky-50 hover:text-sky-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/20;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.lesli-toolbar__submit .material-symbols {
|
|
46
|
+
@apply text-xl leading-none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.lesli-toolbar__actions {
|
|
50
|
+
@apply flex shrink-0 flex-wrap items-center gap-2;
|
|
51
|
+
}
|
|
52
|
+
}
|