quicksilver_ui 0.1.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 +7 -0
- data/app/assets/tailwind/alert.css +35 -0
- data/app/assets/tailwind/badge.css +27 -0
- data/app/assets/tailwind/button.css +35 -0
- data/app/assets/tailwind/form.css +35 -0
- data/app/assets/tailwind/link.css +23 -0
- data/app/assets/tailwind/modal.css +43 -0
- data/app/assets/tailwind/quicksilver_ui/engine.css +7 -0
- data/app/assets/tailwind/typography.css +112 -0
- data/app/helpers/app_form_builder.rb +94 -0
- data/app/helpers/app_form_helper.rb +7 -0
- data/app/javascript/controllers/autogrow_controller.js +19 -0
- data/app/javascript/controllers/dismissable_controller.js +35 -0
- data/app/javascript/controllers/dropdown_controller.js +59 -0
- data/app/javascript/controllers/modal_controller.js +45 -0
- data/app/javascript/controllers/tabs_controller.js +62 -0
- data/app/javascript/mixins/use_floating_ui.js +104 -0
- data/app/views/form/base_tag.rb +42 -0
- data/app/views/form/checkbox.rb +62 -0
- data/app/views/form/date_field.rb +11 -0
- data/app/views/form/email_field.rb +7 -0
- data/app/views/form/error.rb +15 -0
- data/app/views/form/file_field.rb +12 -0
- data/app/views/form/group.rb +97 -0
- data/app/views/form/hint.rb +15 -0
- data/app/views/form/input.rb +11 -0
- data/app/views/form/label.rb +19 -0
- data/app/views/form/password_field.rb +7 -0
- data/app/views/form/phone_field.rb +7 -0
- data/app/views/form/radio_button.rb +37 -0
- data/app/views/form/search_field.rb +7 -0
- data/app/views/form/select.rb +46 -0
- data/app/views/form/text_field.rb +7 -0
- data/app/views/form/textarea.rb +27 -0
- data/app/views/form/toggle.rb +35 -0
- data/app/views/ui/accordion.rb +67 -0
- data/app/views/ui/alert.rb +84 -0
- data/app/views/ui/avatar.rb +57 -0
- data/app/views/ui/badge.rb +35 -0
- data/app/views/ui/base.rb +29 -0
- data/app/views/ui/dropdown/item.rb +49 -0
- data/app/views/ui/dropdown.rb +111 -0
- data/app/views/ui/icon.rb +46 -0
- data/app/views/ui/modal.rb +96 -0
- data/app/views/ui/toast.rb +90 -0
- data/lib/generators/quicksilver_ui/affordance/affordance_generator.rb +102 -0
- data/lib/generators/quicksilver_ui/component/all_generator.rb +32 -0
- data/lib/generators/quicksilver_ui/component/component_generator.rb +194 -0
- data/lib/generators/quicksilver_ui/form/all_generator.rb +32 -0
- data/lib/generators/quicksilver_ui/form/form_generator.rb +164 -0
- data/lib/generators/quicksilver_ui/form/templates/app_form_builder.rb +39 -0
- data/lib/generators/quicksilver_ui/form/templates/app_form_helper.rb +7 -0
- data/lib/generators/quicksilver_ui/install/install_generator.rb +42 -0
- data/lib/generators/quicksilver_ui/install/templates/base.rb +29 -0
- data/lib/generators/quicksilver_ui/install/templates/initializer.rb +16 -0
- data/lib/quicksilver_ui/dependencies.rb +191 -0
- data/lib/quicksilver_ui/engine.rb +18 -0
- data/lib/quicksilver_ui/version.rb +5 -0
- data/lib/quicksilver_ui.rb +37 -0
- metadata +98 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 98070a613f6cd27f56a14c30d26f7a769f92e3434dcb38ec980d6af9f99ead6b
|
|
4
|
+
data.tar.gz: b54c5beab648c18a73c6a916bacf70c5391c88ec0c1cd483ca84f8ea431b82c8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 634ea19a66852a9bd502eaeaea72edf39fab4c6c1d6b989b51e32d56e0562b8ebfc92dd8d7b226760556730c0c9788b5d290cf1764383f02fdb3c5528fc4cd89
|
|
7
|
+
data.tar.gz: a7ae72ff22c80a1ce740901c6accbe471e05ed59e15cc469ea60b6208d7ad30aed554a246fbc8cc2af3eaa5419f3ee9a80a6fe3782d9d4a188aac47604ae66a5
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.ui-alert {
|
|
2
|
+
@apply min-w-96 max-w-2xl ring ring-gray-700 bg-white p-3 text-gray-900 inline-flex justify-start items-start gap-3;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.ui-alert-neutral {
|
|
6
|
+
@apply ring-gray-700 bg-gray-100;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ui-alert-brand {
|
|
10
|
+
@apply text-primary-950 ring-primary-200 bg-primary-100;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ui-alert-success {
|
|
14
|
+
@apply text-emerald-950 ring-emerald-200 bg-emerald-100;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ui-alert-warning {
|
|
18
|
+
@apply text-amber-950 ring-amber-200 bg-amber-100;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ui-alert-danger {
|
|
22
|
+
@apply text-red-950 ring-red-200 bg-red-100;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ui-alert-info {
|
|
26
|
+
@apply text-blue-950 ring-blue-200 bg-blue-100;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ui-alert-sm {
|
|
30
|
+
@apply p-1.5 gap-1.5;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.ui-alert-md {
|
|
34
|
+
@apply p-3 gap-3;
|
|
35
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.ui-badge {
|
|
2
|
+
@apply inline-flex justify-start items-center min-w-fit max-w-fit gap-1 px-2 py-0.5 text-xs bg-gray-50 rounded-sm ring ring-gray-600;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.ui-badge-neutral {
|
|
6
|
+
@apply text-gray-900 bg-gray-50 ring-gray-600;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ui-badge-brand {
|
|
10
|
+
@apply text-primary-900 bg-primary-100 ring-primary-200;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ui-badge-danger {
|
|
14
|
+
@apply text-red-700 bg-red-50 ring-red-700;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ui-badge-sm {
|
|
18
|
+
@apply px-2 py-0.5 text-xs;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ui-badge-md {
|
|
22
|
+
@apply px-2.5 py-0.5 text-sm font-medium leading-5;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ui-badge-lg {
|
|
26
|
+
@apply px-3 py-1 text-sm font-medium leading-5;
|
|
27
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.ui-button {
|
|
2
|
+
@apply max-w-fit block px-2 py-1 no-underline text-sm font-medium text-gray-950 border border-gray-900 hover:bg-gray-900 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.ui-button-primary {
|
|
6
|
+
@apply text-white border-primary-500 bg-primary-500 hover:bg-primary-700 hover:border-primary-700 focus-visible:outline-primary-500;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ui-button-secondary {
|
|
10
|
+
@apply text-white border-secondary-700 bg-secondary-700 hover:bg-secondary-900 hover:border-secondary-900 focus-visible:outline-secondary-500;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ui-button-warning {
|
|
14
|
+
@apply text-amber-950 bg-amber-200 hover:bg-amber-300 active:bg-amber-400 border border-amber-300 hover:border-amber-500 focus-visible:outline-amber-500;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ui-button-danger {
|
|
18
|
+
@apply text-red-500 bg-white active:bg-red-400 border border-red-500 hover:bg-red-500 hover:text-red-50 focus-visible:outline-red-500;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ui-button-lg {
|
|
22
|
+
@apply text-base py-2 px-4;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ui-button-md {
|
|
26
|
+
@apply text-sm py-1 px-4;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ui-button-sm {
|
|
30
|
+
@apply text-xs py-1 px-3;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.ui-button-xs {
|
|
34
|
+
@apply text-xs py-0.5 px-2;
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.ui-form-control {
|
|
2
|
+
@apply ring ring-gray-800 px-2 py-1 rounded-sm max-w-fit focus:outline-2 focus:outline-gray-900;
|
|
3
|
+
|
|
4
|
+
[data-invalid] & {
|
|
5
|
+
@apply text-red-500 ring-red-500 focus:outline-red-500;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ui-form-checkbox {
|
|
10
|
+
@apply size-4 appearance-none border border-gray-800 bg-white checked:bg-gray-900 indeterminate:border-gray-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900 focus:outline-gray-900 disabled:border-gray-500 hover:disabled:border-gray-500 disabled:bg-gray-400 disabled:checked:bg-gray-400;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ui-form-radio {
|
|
14
|
+
@apply size-4 appearance-none border border-gray-800 bg-white checked:bg-gray-900 indeterminate:border-gray-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900 focus:outline-gray-900 disabled:border-gray-500 hover:disabled:border-gray-500 disabled:bg-gray-400 disabled:checked:bg-gray-400;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ui-form-select {
|
|
18
|
+
@apply ring ring-gray-800 pl-2 pr-6 py-1 rounded-sm max-w-fit focus:outline-2 focus:outline-gray-900;
|
|
19
|
+
|
|
20
|
+
[data-invalid] & {
|
|
21
|
+
@apply text-red-500 ring-red-500 focus:outline-red-500;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ui-form-label {
|
|
26
|
+
@apply font-medium text-sm text-gray-900;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ui-form-hint {
|
|
30
|
+
@apply text-sm text-gray-800;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.ui-form-error {
|
|
34
|
+
@apply text-sm font-medium text-red-600;
|
|
35
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.ui-link {
|
|
2
|
+
@apply underline text-sm text-gray-900 hover:text-primary-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.ui-link-danger {
|
|
6
|
+
@apply text-tertiary-600 hover:text-tertiary-700 active:text-tertiary-800 focus-visible:outline-tertiary-600;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ui-link-primary {
|
|
10
|
+
@apply text-primary-600 hover:text-primary-700 active:text-primary-800 focus-visible:outline-primary-500;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ui-link-lg {
|
|
14
|
+
@apply text-base;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ui-link-md {
|
|
18
|
+
@apply text-sm;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ui-link-sm {
|
|
22
|
+
@apply text-xs;
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
.ui-modal {
|
|
2
|
+
@apply absolute top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2 min-w-sm max-w-xl w-full shadow-xl ring ring-gray-300 bg-white backdrop:bg-gray-800/50;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
dialog.ui-modal:not([open]) {
|
|
6
|
+
@apply hidden;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ui-modal-body {
|
|
10
|
+
@apply p-4;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ui-modal-footer {
|
|
14
|
+
@apply border-t border-gray-300 p-6;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@keyframes ui-fade-in {
|
|
18
|
+
0% {
|
|
19
|
+
opacity: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
100% {
|
|
23
|
+
opacity: 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@keyframes ui-fade-out {
|
|
28
|
+
0% {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
100% {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
dialog[data-modal-target="dialog"][open] {
|
|
38
|
+
animation: ui-fade-in 200ms forwards;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
dialog[data-modal-target="dialog"][closing] {
|
|
42
|
+
animation: ui-fade-out 200ms forwards;
|
|
43
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
@import "../typography.css" layer(affordances);
|
|
2
|
+
@import "../button.css" layer(affordances);
|
|
3
|
+
@import "../link.css" layer(affordances);
|
|
4
|
+
@import "../alert.css" layer(affordances);
|
|
5
|
+
@import "../form.css" layer(affordances);
|
|
6
|
+
@import "../badge.css" layer(affordances);
|
|
7
|
+
@import "../modal.css" layer(affordances);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* Typography */
|
|
2
|
+
.ui-display-2xl {
|
|
3
|
+
@apply text-gray-900 text-5xl font-light leading-relaxed tracking-tight;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.ui-display-xl {
|
|
7
|
+
@apply text-gray-900 text-4xl font-light leading-relaxed tracking-tight;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.ui-display-lg {
|
|
11
|
+
@apply text-gray-900 text-3xl font-light leading-relaxed tracking-tight;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.ui-display-md {
|
|
15
|
+
@apply text-gray-900 text-2xl font-light leading-relaxed tracking-tight;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ui-display-sm {
|
|
19
|
+
@apply text-gray-900 text-xl font-light leading-relaxed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ui-display-xs {
|
|
23
|
+
@apply text-gray-900 text-lg font-light leading-relaxed;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Heading Typography */
|
|
27
|
+
.ui-heading-2xl {
|
|
28
|
+
@apply text-gray-900 text-4xl font-semibold leading-tight;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.ui-heading-xl {
|
|
32
|
+
@apply text-gray-900 text-3xl font-semibold leading-snug;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.ui-heading-lg {
|
|
36
|
+
@apply text-gray-900 text-2xl font-semibold leading-snug;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.ui-heading-md {
|
|
40
|
+
@apply text-gray-900 text-xl font-semibold leading-snug;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ui-heading-sm {
|
|
44
|
+
@apply text-gray-900 text-lg font-semibold leading-snug;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ui-heading-xs {
|
|
48
|
+
@apply text-gray-900 text-base font-semibold leading-normal;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Body Typography */
|
|
52
|
+
.ui-text-xl {
|
|
53
|
+
@apply text-gray-900 text-xl leading-7;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ui-text-lg {
|
|
57
|
+
@apply text-gray-900 text-lg leading-7;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ui-text-md {
|
|
61
|
+
@apply text-gray-900 text-base leading-6;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.ui-text-sm {
|
|
65
|
+
@apply text-gray-900 text-sm leading-5;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ui-text-xs {
|
|
69
|
+
@apply text-gray-900 text-xs leading-4;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Body Typography - Medium Weight Variants */
|
|
73
|
+
.ui-text-xl-medium {
|
|
74
|
+
@apply text-gray-900 text-xl font-medium leading-7;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ui-text-lg-medium {
|
|
78
|
+
@apply text-gray-900 text-lg font-medium leading-7;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.ui-text-md-medium {
|
|
82
|
+
@apply text-gray-900 text-base font-medium leading-6;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.ui-text-sm-medium {
|
|
86
|
+
@apply text-gray-900 text-sm font-medium leading-5;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.ui-text-xs-medium {
|
|
90
|
+
@apply text-gray-900 text-xs font-medium leading-4;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Body Typography - Semibold Weight Variants */
|
|
94
|
+
.ui-text-xl-semibold {
|
|
95
|
+
@apply text-gray-900 text-xl font-semibold leading-7;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ui-text-lg-semibold {
|
|
99
|
+
@apply text-gray-900 text-lg font-semibold leading-7;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ui-text-md-semibold {
|
|
103
|
+
@apply text-gray-900 text-base font-semibold leading-6;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ui-text-sm-semibold {
|
|
107
|
+
@apply text-gray-900 text-sm font-semibold leading-5;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.ui-text-xs-semibold {
|
|
111
|
+
@apply text-gray-900 text-xs font-semibold leading-4;
|
|
112
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
class AppFormBuilder < ActionView::Helpers::FormBuilder
|
|
2
|
+
delegate :render, to: :@template
|
|
3
|
+
|
|
4
|
+
def self.with_blank_error_proc(&block)
|
|
5
|
+
old_error_proc = ActionView::Base.field_error_proc
|
|
6
|
+
begin
|
|
7
|
+
ActionView::Base.field_error_proc = proc do |tag, instance|
|
|
8
|
+
tag
|
|
9
|
+
end
|
|
10
|
+
block.call
|
|
11
|
+
ensure
|
|
12
|
+
ActionView::Base.field_error_proc = old_error_proc
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def group(method, options = {})
|
|
17
|
+
render Form::Group.new(form: self, method:, type: options[:type] || :text, **options.except(:type))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def text_field(method, options = {})
|
|
21
|
+
render Form::TextField.new(form: self, method:, **options)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def date_field(method, options = {})
|
|
25
|
+
render Form::DateField.new(form: self, method:, **options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def email_field(method, options = {})
|
|
29
|
+
render Form::EmailField.new(form: self, method:, **options)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def phone_field(method, options = {})
|
|
33
|
+
render Form::PhoneField.new(form: self, method:, **options)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def file_field(method, options = {})
|
|
37
|
+
render Form::FileField.new(form: self, method:, **options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def password_field(method, options = {})
|
|
41
|
+
render Form::PasswordField.new(form: self, method:, **options)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def search_field(method, options = {})
|
|
45
|
+
render Form::SearchField.new(form: self, method:, **options)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def select(method, choices = nil, options = {}, html_options = {})
|
|
49
|
+
render Form::Select.new(
|
|
50
|
+
form: self,
|
|
51
|
+
method:,
|
|
52
|
+
choices:,
|
|
53
|
+
selected: options[:selected],
|
|
54
|
+
include_blank: options.fetch(:include_blank, true),
|
|
55
|
+
prompt: options[:prompt],
|
|
56
|
+
disabled: options[:disabled],
|
|
57
|
+
include_hidden: options.fetch(:include_hidden, true),
|
|
58
|
+
html_options:
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def textarea(method, options = {})
|
|
63
|
+
render Form::Textarea.new(form: self, method:, **options)
|
|
64
|
+
end
|
|
65
|
+
alias_method :text_area, :textarea
|
|
66
|
+
|
|
67
|
+
def label(method, text = nil, options = {}, &block)
|
|
68
|
+
render Form::Label.new(form: self, method:, text:, **options)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def hint(method, text = nil, options = {}, &block)
|
|
72
|
+
render Form::Hint.new(form: self, method:, text:, **options)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def error(method, text = nil, options = {}, &block)
|
|
76
|
+
render Form::Error.new(form: self, method:, text:, **options)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def submit(value = nil, options = {})
|
|
80
|
+
super(value, options.with_defaults(class: "ui-button ui-button-primary"))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def checkbox(method, options = {})
|
|
84
|
+
render Form::Checkbox.new(form: self, method:, **options)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def toggle(method, options = {})
|
|
88
|
+
render Form::Toggle.new(form: self, method:, **options)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def radio_button(method, tag_value, options = {})
|
|
92
|
+
render Form::RadioButton.new(form: self, tag_value:, method:, **options)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { useResize } from "stimulus-use"
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
connect() {
|
|
6
|
+
useResize(this, { element: this.element })
|
|
7
|
+
this.resize()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
input() {
|
|
11
|
+
this.resize()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
resize() {
|
|
15
|
+
this.element.style.overflow = "hidden"
|
|
16
|
+
this.element.style.height = "auto"
|
|
17
|
+
this.element.style.height = `${this.element.scrollHeight}px`
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { useTransition } from "stimulus-use"
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
autoDismissTime: Number,
|
|
7
|
+
hiddenClass: { type: String, default: "hidden" }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
initialize() {
|
|
11
|
+
if (this.hasAutoDismissTimeValue) {
|
|
12
|
+
setTimeout(this.dismiss.bind(this), this.autoDismissTimeValue)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
connect() {
|
|
17
|
+
useTransition(this, {
|
|
18
|
+
element: this.element,
|
|
19
|
+
enterActive: "transition ease-out duration-300",
|
|
20
|
+
enterFrom: "transform opacity-0 scale-95",
|
|
21
|
+
enterTo: "transform opacity-100 scale-100",
|
|
22
|
+
leaveActive: "transition ease-in duration-300",
|
|
23
|
+
leaveFrom: "transform opacity-100 scale-100",
|
|
24
|
+
leaveTo: "transform opacity-0 scale-95",
|
|
25
|
+
hiddenClass: this.hiddenClassValue,
|
|
26
|
+
transitioned: true
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
dismiss() {
|
|
31
|
+
this.leave().then(() => {
|
|
32
|
+
this.element.remove()
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { useTransition } from "stimulus-use"
|
|
3
|
+
import { useFloatingUI } from "mixins/use_floating_ui"
|
|
4
|
+
import { offset, flip, shift } from "@floating-ui/dom"
|
|
5
|
+
|
|
6
|
+
export default class extends Controller {
|
|
7
|
+
static targets = ["trigger", "menu"]
|
|
8
|
+
static values = {
|
|
9
|
+
placement: { type: String, default: "bottom-start" },
|
|
10
|
+
shiftPadding: { type: Number, default: 8 },
|
|
11
|
+
offset: { type: Number, default: 4 }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
connect() {
|
|
15
|
+
useTransition(this, {
|
|
16
|
+
element: this.menuTarget,
|
|
17
|
+
hiddenClass: "hidden",
|
|
18
|
+
})
|
|
19
|
+
this.actualTrigger = this.hasTriggerTarget ? this.triggerTarget : this.element
|
|
20
|
+
|
|
21
|
+
useFloatingUI(this, this.actualTrigger, this.menuTarget, this.positioningOptions)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
toggle() {
|
|
25
|
+
if (this.menuTarget.classList.contains("hidden")) {
|
|
26
|
+
this.show()
|
|
27
|
+
} else {
|
|
28
|
+
this.hide()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
show() {
|
|
33
|
+
this.enter()
|
|
34
|
+
this.showWithPositioning()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
hide(event) {
|
|
38
|
+
// If event is provided, check if click is outside dropdown
|
|
39
|
+
if (event && this.element.contains(event.target)) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.leave()
|
|
44
|
+
this.hideWithPositioning()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Private
|
|
48
|
+
|
|
49
|
+
get positioningOptions() {
|
|
50
|
+
return {
|
|
51
|
+
placement: this.placementValue,
|
|
52
|
+
middleware: [
|
|
53
|
+
offset(this.offsetValue),
|
|
54
|
+
flip(),
|
|
55
|
+
shift({ padding: this.shiftPaddingValue })
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["dialog"]
|
|
5
|
+
static values = { open: Boolean }
|
|
6
|
+
|
|
7
|
+
initialize() {
|
|
8
|
+
this.forceClose = this.forceClose.bind(this)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
document.addEventListener("turbo:before-render", this.forceClose)
|
|
13
|
+
|
|
14
|
+
if (this.openValue) {
|
|
15
|
+
this.open()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
disconnect() {
|
|
20
|
+
document.removeEventListener("turbo:before-render", this.forceClose)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
open() {
|
|
24
|
+
this.dialogTarget.showModal()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
close() {
|
|
28
|
+
this.dialogTarget.setAttribute("closing", "")
|
|
29
|
+
|
|
30
|
+
Promise.all(this.dialogTarget.getAnimations().map((animation) => animation.finished)).then(() => {
|
|
31
|
+
this.dialogTarget.removeAttribute("closing")
|
|
32
|
+
this.dialogTarget.close()
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
backdropClose(event) {
|
|
37
|
+
if (event.target === this.dialogTarget) {
|
|
38
|
+
this.close()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
forceClose() {
|
|
43
|
+
this.dialogTarget.close()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["tab", "panel"]
|
|
5
|
+
static values = { active: { type: String, default: "preview" } }
|
|
6
|
+
|
|
7
|
+
connect() {
|
|
8
|
+
this.show()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
switch(event) {
|
|
12
|
+
this.activeValue = event.params.tab
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
keydown(event) {
|
|
16
|
+
const tabs = this.tabTargets
|
|
17
|
+
const index = tabs.indexOf(event.currentTarget)
|
|
18
|
+
let newIndex
|
|
19
|
+
|
|
20
|
+
switch (event.key) {
|
|
21
|
+
case "ArrowRight":
|
|
22
|
+
newIndex = (index + 1) % tabs.length
|
|
23
|
+
break
|
|
24
|
+
case "ArrowLeft":
|
|
25
|
+
newIndex = (index - 1 + tabs.length) % tabs.length
|
|
26
|
+
break
|
|
27
|
+
case "Home":
|
|
28
|
+
newIndex = 0
|
|
29
|
+
break
|
|
30
|
+
case "End":
|
|
31
|
+
newIndex = tabs.length - 1
|
|
32
|
+
break
|
|
33
|
+
default:
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
event.preventDefault()
|
|
38
|
+
this.activeValue = tabs[newIndex].dataset.tabsTabParam
|
|
39
|
+
tabs[newIndex].focus()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
activeValueChanged() {
|
|
43
|
+
this.show()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
show() {
|
|
47
|
+
this.tabTargets.forEach(tab => {
|
|
48
|
+
const isActive = tab.dataset.tabsTabParam === this.activeValue
|
|
49
|
+
tab.classList.toggle("bg-gray-900", isActive)
|
|
50
|
+
tab.classList.toggle("border-gray-900", isActive)
|
|
51
|
+
tab.classList.toggle("text-white", isActive)
|
|
52
|
+
tab.classList.toggle("border-transparent", !isActive)
|
|
53
|
+
tab.setAttribute("aria-selected", isActive)
|
|
54
|
+
tab.setAttribute("tabindex", isActive ? "0" : "-1")
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
this.panelTargets.forEach(panel => {
|
|
58
|
+
const isActive = panel.dataset.tab === this.activeValue
|
|
59
|
+
panel.classList.toggle("hidden", !isActive)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|