trek 2.1.0 → 2.3.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/.slim-lint.yml +0 -3
- data/AGENTS.md +8 -1
- data/CHANGELOG.md +75 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +20 -3
- data/Makefile +11 -9
- data/README.md +1 -1
- data/app/assets/icons/trek/audit.svg +1 -0
- data/app/assets/icons/trek/chevron-down.svg +1 -0
- data/app/assets/icons/trek/chevron-up.svg +1 -0
- data/app/assets/icons/trek/external.svg +1 -0
- data/app/assets/icons/trek/print.svg +1 -0
- data/app/assets/icons/trek/theme-auto.svg +1 -0
- data/app/assets/icons/trek/theme-dark.svg +1 -0
- data/app/assets/icons/trek/theme-light.svg +1 -0
- data/app/assets/stylesheets/trek/base.css +13 -0
- data/app/components/trek/button_component/button_component.css +9 -0
- data/app/components/trek/button_component.rb +13 -1
- data/app/components/trek/dashboard/analytics_component/analytics_component.html.slim +2 -0
- data/app/components/trek/dashboard/analytics_component.rb +1 -0
- data/app/components/trek/dashboard/chart_component/chart_component.css +192 -0
- data/app/components/trek/dashboard/chart_component/chart_component.html.slim +6 -0
- data/app/components/trek/dashboard/chart_component/chart_component.js +1 -0
- data/app/components/trek/dashboard/chart_component.rb +27 -0
- data/app/components/trek/dashboard/tile_component/tile_component.css +40 -0
- data/app/components/trek/dashboard/tile_component/tile_component.html.slim +14 -4
- data/app/components/trek/dashboard/tile_component.rb +20 -2
- data/app/components/trek/dialog_component/dialog_component.html.slim +6 -2
- data/app/components/trek/dialog_component/dialog_component.js +62 -2
- data/app/components/trek/dropdown_component/dropdown_component.css +66 -0
- data/app/components/trek/dropdown_component/dropdown_component.html.slim +31 -0
- data/app/components/trek/dropdown_component/dropdown_component.js +114 -0
- data/app/components/trek/dropdown_component.rb +29 -0
- data/app/components/trek/form/actions_component/actions_component.css +6 -7
- data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +10 -12
- data/app/components/trek/form/content_editor_component/content_editor_component.js +4 -0
- data/app/components/trek/form/group_component/group_component.css +34 -0
- data/app/components/trek/header_component/header_component.css +10 -4
- data/app/components/trek/header_component/header_component.html.slim +17 -10
- data/app/components/trek/header_component/header_component.js +29 -5
- data/app/components/trek/header_component.rb +20 -3
- data/app/components/trek/layout_component/layout_component.css +20 -0
- data/app/components/trek/layout_component/layout_component.html.slim +16 -0
- data/app/components/trek/layout_component/layout_component.js +35 -6
- data/app/components/trek/menu_component/menu_component.css +6 -0
- data/app/components/trek/menu_component.rb +29 -4
- data/app/components/trek/toaster/toast_component.rb +1 -1
- data/app/controllers/concerns/trek/authorization.rb +12 -0
- data/app/formatters/prosemirror_to_html/global_id_to_links_formatter.rb +3 -1
- data/app/helpers/trek/page_path_helper.rb +4 -3
- data/app/helpers/trek/theme_helper.rb +16 -1
- data/app/models/concerns/trek/phonable.rb +15 -3
- data/app/models/concerns/trek/sectionable.rb +5 -0
- data/app/models/concerns/trek/users/validations.rb +1 -1
- data/app/models/prosemirror.rb +5 -2
- data/app/uploaders/audio_uploader.rb +11 -0
- data/app/uploaders/record_image_uploader.rb +3 -0
- data/app/views/trek/panels/images/_form.html.slim +1 -1
- data/app/views/trek/panels/images/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/images/new.html.slim +1 -1
- data/app/views/trek/panels/links/_form.html.slim +1 -1
- data/app/views/trek/panels/links/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/links/new.html.slim +1 -1
- data/app/views/trek/panels/prompts/_form.html.slim +1 -1
- data/app/views/trek/panels/prompts/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/prompts/new.html.slim +1 -1
- data/config/locales/trek.en.yml +3 -2
- data/config/locales/trek.fr.yml +1 -0
- data/docs/customize.md +18 -2
- data/docs/deploy.md +7 -2
- data/docs/getting-started.md +1 -1
- data/docs/index.md +1 -1
- data/docs/reference/components.md +15 -0
- data/docs/reference/concerns/phonable.md +27 -1
- data/docs/reference/environment-variables.md +2 -0
- data/docs/reference/forms.md +2 -0
- data/docs/reference/generators/install.md +4 -1
- data/docs/reference/icons.md +1 -1
- data/docs/reference/models/tag.md +1 -1
- data/docs/reference/models/user.md +4 -2
- data/docs/reference/uploaders.md +31 -6
- data/docs/what-is-trek.md +1 -1
- data/lib/generators/trek/install/admin/routes_generator.rb +7 -1
- data/lib/generators/trek/install/appsignal_generator.rb +102 -0
- data/lib/generators/trek/install/model_specs_generator.rb +64 -0
- data/lib/generators/trek/install/postmark_generator.rb +3 -0
- data/lib/generators/trek/install/ruby_generator.rb +1 -1
- data/lib/generators/trek/install/scalingo_generator.rb +1 -1
- data/lib/generators/trek/install/uploaders_generator.rb +2 -0
- data/lib/generators/trek/install_generator.rb +5 -0
- data/lib/generators/trek/taxonomies_generator.rb +7 -0
- data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
- data/lib/generators/trek/templates/config/appsignal.rb.tt +25 -0
- data/lib/generators/trek/templates/controllers/admin/uploads_controller.rb +4 -0
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +4 -1
- data/lib/generators/trek/templates/controllers/concerns/appsignal_context.rb +37 -0
- data/lib/generators/trek/templates/db/seeds/setup/fragments.rb +3 -0
- data/lib/generators/trek/templates/db/seeds/setup/users.rb +6 -0
- data/lib/generators/trek/templates/db/seeds/test/cases/page_tree.rb +5 -0
- data/lib/generators/trek/templates/initializers/appsignal_context.rb +5 -0
- data/lib/generators/trek/templates/locales/scaffold/model.en.yml.tt +1 -1
- data/lib/generators/trek/templates/migrations/fragments.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/menu_nodes.rb.erb +0 -1
- data/lib/generators/trek/templates/migrations/tag_categories.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/taggings.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/users.rb.erb +2 -1
- data/lib/generators/trek/templates/models/tag.rb +6 -1
- data/lib/generators/trek/templates/policies/admin/tag_policy.rb +1 -1
- data/lib/generators/trek/templates/policies/external_link_policy.rb +1 -1
- data/lib/generators/trek/templates/spec/fixtures/files/image.png +0 -0
- data/lib/generators/trek/templates/spec/fixtures/files/sound.mp3 +0 -0
- data/lib/generators/trek/templates/spec/models/page_spec.rb +24 -0
- data/lib/generators/trek/templates/spec/models/user_spec.rb +76 -0
- data/lib/generators/trek/templates/spec/policies/admin/external_link_policy_spec.rb +17 -0
- data/lib/generators/trek/templates/spec/policies/admin/fragment_policy_spec.rb +33 -0
- data/lib/generators/trek/templates/spec/policies/admin/page_policy_spec.rb +39 -0
- data/lib/generators/trek/templates/spec/policies/admin/preview_policy_spec.rb +9 -0
- data/lib/generators/trek/templates/spec/policies/admin/tag_category_policy_spec.rb +31 -0
- data/lib/generators/trek/templates/spec/policies/admin/tag_policy_spec.rb +31 -0
- data/lib/generators/trek/templates/spec/policies/admin/user_policy_spec.rb +58 -0
- data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +50 -0
- data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +47 -0
- data/lib/generators/trek/templates/spec/requests/admin/uploads_spec.rb +22 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb +71 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +45 -0
- data/lib/generators/trek/templates/spec/requests/admin/users_spec.rb +99 -0
- data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +2 -2
- data/lib/generators/trek/templates/views/admin/users/theme.turbo_stream.slim +2 -0
- data/lib/generators/trek/upgrade/v2_generator.rb +3 -0
- data/lib/tasks/trek/attachments.rake +18 -0
- data/lib/trek/cached_attachments.rb +53 -0
- data/lib/trek/saved_attachments_reload.rb +44 -0
- data/lib/trek/version.rb +1 -1
- data/lib/trek.rb +3 -0
- data/package.json +1 -1
- data/postcss.config.js +36 -0
- data/trek.gemspec +5 -0
- metadata +76 -3
- data/app/assets/icons/trek/theme.svg +0 -11
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
border: 0.1rem solid;
|
|
3
|
+
border-radius: 0.8rem;
|
|
4
|
+
font-weight: var(--semibold);
|
|
5
|
+
margin-bottom: 2rem;
|
|
6
|
+
max-width: 83.7rem;
|
|
7
|
+
padding: 2rem;
|
|
8
|
+
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out,
|
|
9
|
+
color 0.2s ease-in-out;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.root:last-child {
|
|
13
|
+
margin-bottom: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.title {
|
|
17
|
+
color: var(--mint-12);
|
|
18
|
+
font-size: 1.6rem;
|
|
19
|
+
font-weight: var(--semibold);
|
|
20
|
+
margin: 0 0 0.5rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.data {
|
|
24
|
+
margin-bottom: 2rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.info {
|
|
28
|
+
color: var(--slate-11);
|
|
29
|
+
font-size: 1.4rem;
|
|
30
|
+
font-weight: var(--medium);
|
|
31
|
+
padding-bottom: 1.6rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.root.color-amber {
|
|
35
|
+
--icon-color: var(--amber-11);
|
|
36
|
+
|
|
37
|
+
background-color: var(--amber-2);
|
|
38
|
+
border-color: var(--amber-5);
|
|
39
|
+
color: var(--amber-12);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.root.color-amber:hover {
|
|
43
|
+
--icon-color: var(--amber-12);
|
|
44
|
+
|
|
45
|
+
background-color: var(--amber-3);
|
|
46
|
+
border-color: var(--amber-6);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.root.color-amber:active {
|
|
50
|
+
background-color: var(--amber-5);
|
|
51
|
+
border-color: var(--amber-7);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.root.color-lime {
|
|
55
|
+
--icon-color: var(--lime-11);
|
|
56
|
+
|
|
57
|
+
background-color: var(--lime-2);
|
|
58
|
+
border-color: var(--lime-5);
|
|
59
|
+
color: var(--lime-12);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.root.color-lime:hover {
|
|
63
|
+
--icon-color: var(--lime-12);
|
|
64
|
+
|
|
65
|
+
background-color: var(--lime-3);
|
|
66
|
+
border-color: var(--lime-6);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.root.color-lime:active {
|
|
70
|
+
background-color: var(--lime-5);
|
|
71
|
+
border-color: var(--lime-7);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.root.color-mint {
|
|
75
|
+
--icon-color: var(--mint-11);
|
|
76
|
+
|
|
77
|
+
background-color: var(--mint-2);
|
|
78
|
+
border-color: var(--mint-5);
|
|
79
|
+
color: var(--mint-12);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.root.color-mint:hover {
|
|
83
|
+
--icon-color: var(--mint-12);
|
|
84
|
+
|
|
85
|
+
background-color: var(--mint-3);
|
|
86
|
+
border-color: var(--mint-6);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.root.color-mint:active {
|
|
90
|
+
background-color: var(--mint-5);
|
|
91
|
+
border-color: var(--mint-7);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.root.color-purple {
|
|
95
|
+
--icon-color: var(--purple-11);
|
|
96
|
+
|
|
97
|
+
background-color: var(--purple-2);
|
|
98
|
+
border-color: var(--purple-5);
|
|
99
|
+
color: var(--purple-12);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.root.color-purple:hover {
|
|
103
|
+
--icon-color: var(--purple-12);
|
|
104
|
+
|
|
105
|
+
background-color: var(--purple-3);
|
|
106
|
+
border-color: var(--purple-6);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.root.color-purple:active {
|
|
110
|
+
background-color: var(--purple-5);
|
|
111
|
+
border-color: var(--purple-7);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.root.color-sky {
|
|
115
|
+
--icon-color: var(--sky-11);
|
|
116
|
+
|
|
117
|
+
background-color: var(--sky-2);
|
|
118
|
+
border-color: var(--sky-5);
|
|
119
|
+
color: var(--sky-12);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.root.color-sky:hover {
|
|
123
|
+
--icon-color: var(--sky-12);
|
|
124
|
+
|
|
125
|
+
background-color: var(--sky-3);
|
|
126
|
+
border-color: var(--sky-6);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.root.color-sky:active {
|
|
130
|
+
background-color: var(--sky-5);
|
|
131
|
+
border-color: var(--sky-7);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.root.color-slate {
|
|
135
|
+
--icon-color: var(--slate-11);
|
|
136
|
+
|
|
137
|
+
background-color: var(--slate-2);
|
|
138
|
+
border-color: var(--slate-5);
|
|
139
|
+
color: var(--slate-12);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.root.color-slate:hover {
|
|
143
|
+
--icon-color: var(--slate-12);
|
|
144
|
+
|
|
145
|
+
background-color: var(--slate-3);
|
|
146
|
+
border-color: var(--slate-6);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.root.color-slate:active {
|
|
150
|
+
background-color: var(--slate-5);
|
|
151
|
+
border-color: var(--slate-7);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.root.color-teal {
|
|
155
|
+
--icon-color: var(--teal-11);
|
|
156
|
+
|
|
157
|
+
background-color: var(--teal-2);
|
|
158
|
+
border-color: var(--teal-5);
|
|
159
|
+
color: var(--teal-12);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.root.color-teal:hover {
|
|
163
|
+
--icon-color: var(--teal-12);
|
|
164
|
+
|
|
165
|
+
background-color: var(--teal-3);
|
|
166
|
+
border-color: var(--teal-6);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.root.color-teal:active {
|
|
170
|
+
background-color: var(--teal-5);
|
|
171
|
+
border-color: var(--teal-7);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.root.color-violet {
|
|
175
|
+
--icon-color: var(--violet-11);
|
|
176
|
+
|
|
177
|
+
background-color: var(--violet-2);
|
|
178
|
+
border-color: var(--violet-5);
|
|
179
|
+
color: var(--violet-12);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.root.color-violet:hover {
|
|
183
|
+
--icon-color: var(--violet-12);
|
|
184
|
+
|
|
185
|
+
background-color: var(--violet-3);
|
|
186
|
+
border-color: var(--violet-6);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.root.color-violet:active {
|
|
190
|
+
background-color: var(--violet-5);
|
|
191
|
+
border-color: var(--violet-7);
|
|
192
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "./chart_component.css";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trek
|
|
4
|
+
class Dashboard::ChartComponent < Trek::Component
|
|
5
|
+
# Chartkick is an optional dependency: host apps that use this component must
|
|
6
|
+
# add `gem "chartkick"` and import "chartkick/chart.js" in their JS bundle.
|
|
7
|
+
include Chartkick::Helper if defined?(Chartkick)
|
|
8
|
+
|
|
9
|
+
option :title
|
|
10
|
+
option :info, optional: true
|
|
11
|
+
option :data
|
|
12
|
+
option :type
|
|
13
|
+
option :options, optional: true, default: proc { {} }
|
|
14
|
+
option :color, default: proc { :slate }
|
|
15
|
+
|
|
16
|
+
def chart
|
|
17
|
+
public_send(type, data, **options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def classes
|
|
21
|
+
class_names(
|
|
22
|
+
root_class,
|
|
23
|
+
class_for("color-#{color}")
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -20,6 +20,14 @@
|
|
|
20
20
|
width: 20rem;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
.root.wide {
|
|
24
|
+
width: calc(40rem + 1.2rem);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.title {
|
|
28
|
+
font-size: 3.6rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
.root:hover .icon {
|
|
24
32
|
transform: scale(1.125);
|
|
25
33
|
}
|
|
@@ -58,6 +66,10 @@
|
|
|
58
66
|
border-color: var(--amber-7);
|
|
59
67
|
}
|
|
60
68
|
|
|
69
|
+
.root.color-amber .title {
|
|
70
|
+
color: var(--amber-11);
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
.root.color-lime {
|
|
62
74
|
--icon-color: var(--lime-11);
|
|
63
75
|
|
|
@@ -78,6 +90,10 @@
|
|
|
78
90
|
border-color: var(--lime-7);
|
|
79
91
|
}
|
|
80
92
|
|
|
93
|
+
.root.color-lime .title {
|
|
94
|
+
color: var(--lime-11);
|
|
95
|
+
}
|
|
96
|
+
|
|
81
97
|
.root.color-mint {
|
|
82
98
|
--icon-color: var(--mint-11);
|
|
83
99
|
|
|
@@ -98,6 +114,10 @@
|
|
|
98
114
|
border-color: var(--mint-7);
|
|
99
115
|
}
|
|
100
116
|
|
|
117
|
+
.root.color-mint .title {
|
|
118
|
+
color: var(--mint-11);
|
|
119
|
+
}
|
|
120
|
+
|
|
101
121
|
.root.color-purple {
|
|
102
122
|
--icon-color: var(--purple-11);
|
|
103
123
|
|
|
@@ -118,6 +138,10 @@
|
|
|
118
138
|
border-color: var(--purple-7);
|
|
119
139
|
}
|
|
120
140
|
|
|
141
|
+
.root.color-purple .title {
|
|
142
|
+
color: var(--purple-11);
|
|
143
|
+
}
|
|
144
|
+
|
|
121
145
|
.root.color-sky {
|
|
122
146
|
--icon-color: var(--sky-11);
|
|
123
147
|
|
|
@@ -138,6 +162,10 @@
|
|
|
138
162
|
border-color: var(--sky-7);
|
|
139
163
|
}
|
|
140
164
|
|
|
165
|
+
.root.color-sky .title {
|
|
166
|
+
color: var(--sky-11);
|
|
167
|
+
}
|
|
168
|
+
|
|
141
169
|
.root.color-slate {
|
|
142
170
|
--icon-color: var(--slate-11);
|
|
143
171
|
|
|
@@ -158,6 +186,10 @@
|
|
|
158
186
|
border-color: var(--slate-7);
|
|
159
187
|
}
|
|
160
188
|
|
|
189
|
+
.root.color-slate .title {
|
|
190
|
+
color: var(--slate-11);
|
|
191
|
+
}
|
|
192
|
+
|
|
161
193
|
.root.color-teal {
|
|
162
194
|
--icon-color: var(--teal-11);
|
|
163
195
|
|
|
@@ -178,6 +210,10 @@
|
|
|
178
210
|
border-color: var(--teal-7);
|
|
179
211
|
}
|
|
180
212
|
|
|
213
|
+
.root.color-teal .title {
|
|
214
|
+
color: var(--teal-11);
|
|
215
|
+
}
|
|
216
|
+
|
|
181
217
|
.root.color-violet {
|
|
182
218
|
--icon-color: var(--violet-11);
|
|
183
219
|
|
|
@@ -197,3 +233,7 @@
|
|
|
197
233
|
background-color: var(--violet-5);
|
|
198
234
|
border-color: var(--violet-7);
|
|
199
235
|
}
|
|
236
|
+
|
|
237
|
+
.root.color-violet .title {
|
|
238
|
+
color: var(--violet-11);
|
|
239
|
+
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
- if href.present?
|
|
2
|
+
= link_to href, class: classes, data: link_data, rel: link_rel, target: target
|
|
3
|
+
- if title
|
|
4
|
+
span(class=class_for("title")) = title
|
|
5
|
+
- if icon
|
|
6
|
+
= render Trek::IconComponent.new(icon, classnames: class_for("icon"))
|
|
7
|
+
span(class=class_for("text")) = formatted_text
|
|
8
|
+
- else
|
|
9
|
+
div(class=classes)
|
|
10
|
+
- if title
|
|
11
|
+
span(class=class_for("title")) = title
|
|
12
|
+
- if icon
|
|
13
|
+
= render Trek::IconComponent.new(icon, classnames: class_for("icon"))
|
|
14
|
+
span(class=class_for("text")) = formatted_text
|
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
module Trek
|
|
4
4
|
class Dashboard::TileComponent < Trek::Component
|
|
5
5
|
option :color, default: proc { :slate }
|
|
6
|
-
|
|
6
|
+
# Extra data attributes for the link, e.g. `turbo_confirm:`.
|
|
7
|
+
option :data, default: proc { {} }
|
|
8
|
+
option :href, optional: true
|
|
7
9
|
option :icon, optional: true
|
|
10
|
+
# An HTTP method other than GET (`:post`, `:delete`…), issued through
|
|
11
|
+
# Turbo like Trek::ButtonComponent's `to:` links: a tile can create a
|
|
12
|
+
# record in one click rather than open a form.
|
|
13
|
+
option :method, optional: true
|
|
8
14
|
option :rel, optional: true
|
|
9
15
|
option :target, optional: true
|
|
10
16
|
option :text
|
|
17
|
+
option :title, optional: true
|
|
11
18
|
option :allowed_to, default: -> { true }
|
|
19
|
+
# Spans two tile columns (including the gap between them).
|
|
20
|
+
option :wide, default: proc { false }
|
|
12
21
|
|
|
13
22
|
def render?
|
|
14
23
|
allowed_to
|
|
@@ -17,10 +26,19 @@ module Trek
|
|
|
17
26
|
def classes
|
|
18
27
|
class_names(
|
|
19
28
|
root_class,
|
|
20
|
-
class_for("color-#{color}")
|
|
29
|
+
class_for("color-#{color}"),
|
|
30
|
+
class_for("wide") => wide
|
|
21
31
|
)
|
|
22
32
|
end
|
|
23
33
|
|
|
34
|
+
def link_data
|
|
35
|
+
data.merge(turbo_method: method).compact
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def link_rel
|
|
39
|
+
rel || ("noopener" if target == "_blank")
|
|
40
|
+
end
|
|
41
|
+
|
|
24
42
|
def formatted_text
|
|
25
43
|
# rubocop:disable Rails/OutputSafety
|
|
26
44
|
text.gsub("\n", "#{tag.br} ")&.html_safe
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
dialog(class=root_class data-controller=identifier)
|
|
2
|
-
h2(
|
|
2
|
+
h2(
|
|
3
|
+
class=class_for("title")
|
|
4
|
+
data=stimulus_target_hash("title")
|
|
5
|
+
) = title || t(".title")
|
|
3
6
|
p(
|
|
4
7
|
class=class_for("text")
|
|
5
8
|
data=stimulus_target_hash("text")
|
|
@@ -22,5 +25,6 @@ dialog(class=root_class data-controller=identifier)
|
|
|
22
25
|
text: t("admin.actions.delete"),
|
|
23
26
|
color: :tomato,
|
|
24
27
|
value: "confirm",
|
|
25
|
-
data: { action: stimulus_action("close") }
|
|
28
|
+
data: { action: stimulus_action("close") }
|
|
29
|
+
.merge(stimulus_target_hash("confirm_button")),
|
|
26
30
|
)
|
|
@@ -3,11 +3,26 @@ import "./dialog_component.css";
|
|
|
3
3
|
import { Controller as BaseController } from "@hotwired/stimulus";
|
|
4
4
|
|
|
5
5
|
export class Controller extends BaseController {
|
|
6
|
-
static targets = ["text"];
|
|
6
|
+
static targets = ["title", "text", "confirmButton"];
|
|
7
7
|
|
|
8
8
|
connect() {
|
|
9
|
+
this.saveDefaults();
|
|
10
|
+
|
|
11
|
+
// For a link with data-turbo-method, Turbo submits an ephemeral form it
|
|
12
|
+
// only copies data-turbo-confirm onto, and calls confirm() without a
|
|
13
|
+
// submitter — remember the clicked element to read the other
|
|
14
|
+
// data-turbo-confirm-* attributes from.
|
|
15
|
+
this.rememberConfirmSource = (event) => {
|
|
16
|
+
this.confirmSource = event.target.closest?.("[data-turbo-confirm]");
|
|
17
|
+
};
|
|
18
|
+
document.addEventListener("click", this.rememberConfirmSource, {
|
|
19
|
+
capture: true,
|
|
20
|
+
});
|
|
21
|
+
|
|
9
22
|
// eslint-disable-next-line no-undef
|
|
10
|
-
Turbo.config.forms.confirm = (message) => {
|
|
23
|
+
Turbo.config.forms.confirm = (message, _formElement, submitter) => {
|
|
24
|
+
this.customize({ ...this.confirmSource?.dataset, ...submitter?.dataset });
|
|
25
|
+
this.confirmSource = null;
|
|
11
26
|
this.setText(message);
|
|
12
27
|
this.open();
|
|
13
28
|
|
|
@@ -23,6 +38,51 @@ export class Controller extends BaseController {
|
|
|
23
38
|
};
|
|
24
39
|
}
|
|
25
40
|
|
|
41
|
+
disconnect() {
|
|
42
|
+
document.removeEventListener("click", this.rememberConfirmSource, {
|
|
43
|
+
capture: true,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
saveDefaults() {
|
|
48
|
+
if (this.hasTitleTarget) {
|
|
49
|
+
this.defaultTitle = this.titleTarget.textContent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.hasConfirmButtonTarget) {
|
|
53
|
+
this.defaultConfirmLabel = this.confirmLabelElement.textContent;
|
|
54
|
+
this.defaultConfirmColor = this.confirmButtonTarget.dataset.accentColor;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
customize({ turboConfirmTitle, turboConfirmButton, turboConfirmColor }) {
|
|
59
|
+
if (this.hasTitleTarget) {
|
|
60
|
+
this.titleTarget.textContent = turboConfirmTitle ?? this.defaultTitle;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.hasConfirmButtonTarget) {
|
|
64
|
+
this.confirmLabelElement.textContent =
|
|
65
|
+
turboConfirmButton ?? this.defaultConfirmLabel;
|
|
66
|
+
this.setConfirmColor(turboConfirmColor ?? this.defaultConfirmColor);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setConfirmColor(color) {
|
|
71
|
+
const button = this.confirmButtonTarget;
|
|
72
|
+
|
|
73
|
+
// Swap the postcss-modules-scoped color-* class along with the accent
|
|
74
|
+
// variables (see generateScopedName in postcss.config.js).
|
|
75
|
+
button.classList.replace(
|
|
76
|
+
`c-trek--button-color-${button.dataset.accentColor}`,
|
|
77
|
+
`c-trek--button-color-${color}`
|
|
78
|
+
);
|
|
79
|
+
button.dataset.accentColor = color;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get confirmLabelElement() {
|
|
83
|
+
return this.confirmButtonTarget.querySelector("span");
|
|
84
|
+
}
|
|
85
|
+
|
|
26
86
|
setText(text) {
|
|
27
87
|
if (this.hasTextTarget) {
|
|
28
88
|
this.textTarget.textContent = text;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* Use component-local class names and add them to HTML via #class_for(name) helper */
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
position: relative;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.trigger {
|
|
9
|
+
align-items: center;
|
|
10
|
+
background: none;
|
|
11
|
+
border: none;
|
|
12
|
+
color: var(--slate-12);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
gap: 0.4rem;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.indicator {
|
|
19
|
+
transition: transform 0.2s ease-in-out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.trigger[aria-expanded="true"] .indicator {
|
|
23
|
+
transform: rotate(180deg);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.menu {
|
|
27
|
+
background-color: var(--slate-2);
|
|
28
|
+
border: 1px solid var(--slate-6);
|
|
29
|
+
border-radius: 0.8rem;
|
|
30
|
+
box-shadow: 0 4px 12px rgb(0 0 0 / 12%);
|
|
31
|
+
list-style: none;
|
|
32
|
+
margin: 0;
|
|
33
|
+
min-width: 14rem;
|
|
34
|
+
padding: 0.4rem;
|
|
35
|
+
position: absolute;
|
|
36
|
+
right: 0;
|
|
37
|
+
top: calc(100% + 0.4rem);
|
|
38
|
+
z-index: 10;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.menu[hidden] {
|
|
42
|
+
display: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.-item-root {
|
|
46
|
+
background: none;
|
|
47
|
+
border: none;
|
|
48
|
+
border-radius: 0.6rem;
|
|
49
|
+
color: var(--slate-12);
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
display: inline-flex;
|
|
52
|
+
font-size: 1.3rem;
|
|
53
|
+
gap: 1rem;
|
|
54
|
+
line-height: 1.23;
|
|
55
|
+
padding: 0.8rem 1.2rem;
|
|
56
|
+
text-align: left;
|
|
57
|
+
text-decoration: none;
|
|
58
|
+
width: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.-item-root:hover,
|
|
62
|
+
.-item-root:focus {
|
|
63
|
+
background-color: var(--accent-3);
|
|
64
|
+
color: var(--accent-11);
|
|
65
|
+
outline: none;
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
div(class=classes data-controller=identifier)
|
|
2
|
+
button(
|
|
3
|
+
type="button"
|
|
4
|
+
class=class_for("trigger")
|
|
5
|
+
aria-haspopup="true"
|
|
6
|
+
aria-expanded="false"
|
|
7
|
+
aria-controls=menu_id
|
|
8
|
+
aria-label=label
|
|
9
|
+
data=stimulus_target_hash("trigger")
|
|
10
|
+
data-action=stimulus_action(click: :toggle, keydown: :on_trigger_keydown)
|
|
11
|
+
)
|
|
12
|
+
= trigger
|
|
13
|
+
- unless hide_chevron
|
|
14
|
+
= render Trek::IconComponent.new( \
|
|
15
|
+
"trek/chevron-down",
|
|
16
|
+
classnames: class_for("indicator"),
|
|
17
|
+
aria_hidden: true,
|
|
18
|
+
)
|
|
19
|
+
ul(
|
|
20
|
+
id=menu_id
|
|
21
|
+
class=class_for("menu")
|
|
22
|
+
role="menu"
|
|
23
|
+
aria-label=label
|
|
24
|
+
hidden=true
|
|
25
|
+
data=stimulus_target_hash("menu")
|
|
26
|
+
data-action=stimulus_action( \
|
|
27
|
+
keydown: :on_menu_keydown,
|
|
28
|
+
click: :on_menu_click,
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
= safe_join(items)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import "./dropdown_component.css";
|
|
2
|
+
|
|
3
|
+
import { Controller as BaseController } from "@hotwired/stimulus";
|
|
4
|
+
|
|
5
|
+
export class Controller extends BaseController {
|
|
6
|
+
static targets = ["trigger", "menu"];
|
|
7
|
+
|
|
8
|
+
connect() {
|
|
9
|
+
this.onDocumentClick = this.onDocumentClick.bind(this);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
disconnect() {
|
|
13
|
+
document.removeEventListener("click", this.onDocumentClick);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get items() {
|
|
17
|
+
return Array.from(this.menuTarget.querySelectorAll('[role="menuitem"]'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get expanded() {
|
|
21
|
+
return this.triggerTarget.getAttribute("aria-expanded") === "true";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
toggle(event) {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
this.expanded ? this.collapse() : this.open();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onTriggerKeydown(event) {
|
|
30
|
+
switch (event.key) {
|
|
31
|
+
case "ArrowDown":
|
|
32
|
+
case "Enter":
|
|
33
|
+
case " ":
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
this.open();
|
|
36
|
+
this.focusItem(0);
|
|
37
|
+
break;
|
|
38
|
+
case "ArrowUp":
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
this.open();
|
|
41
|
+
this.focusItem(this.items.length - 1);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onMenuKeydown(event) {
|
|
47
|
+
const items = this.items;
|
|
48
|
+
const current = items.indexOf(document.activeElement);
|
|
49
|
+
switch (event.key) {
|
|
50
|
+
case "ArrowDown":
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
this.focusItem((current + 1) % items.length);
|
|
53
|
+
break;
|
|
54
|
+
case "ArrowUp":
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
this.focusItem((current - 1 + items.length) % items.length);
|
|
57
|
+
break;
|
|
58
|
+
case "Home":
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
this.focusItem(0);
|
|
61
|
+
break;
|
|
62
|
+
case "End":
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
this.focusItem(items.length - 1);
|
|
65
|
+
break;
|
|
66
|
+
case "Escape":
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
this.collapse();
|
|
69
|
+
this.triggerTarget.focus();
|
|
70
|
+
break;
|
|
71
|
+
case "Enter":
|
|
72
|
+
case " ":
|
|
73
|
+
// Menu items are not native buttons: activate the focused one as a
|
|
74
|
+
// click so it runs its own action, then onMenuClick closes the menu.
|
|
75
|
+
if (current === -1) break;
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
items[current].click();
|
|
78
|
+
break;
|
|
79
|
+
case "Tab":
|
|
80
|
+
this.collapse();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onMenuClick(event) {
|
|
86
|
+
if (event.target.closest('[role="menuitem"]')) {
|
|
87
|
+
this.collapse();
|
|
88
|
+
this.triggerTarget.focus();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
open() {
|
|
93
|
+
if (this.expanded) return;
|
|
94
|
+
this.menuTarget.hidden = false;
|
|
95
|
+
this.triggerTarget.setAttribute("aria-expanded", "true");
|
|
96
|
+
document.addEventListener("click", this.onDocumentClick);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
collapse() {
|
|
100
|
+
if (!this.expanded) return;
|
|
101
|
+
this.menuTarget.hidden = true;
|
|
102
|
+
this.triggerTarget.setAttribute("aria-expanded", "false");
|
|
103
|
+
document.removeEventListener("click", this.onDocumentClick);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
focusItem(index) {
|
|
107
|
+
const item = this.items[index];
|
|
108
|
+
if (item) item.focus();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
onDocumentClick(event) {
|
|
112
|
+
if (!this.element.contains(event.target)) this.collapse();
|
|
113
|
+
}
|
|
114
|
+
}
|