okonomi_ui_kit 0.1.3 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 046343b54f5e77b5bfed849724f58fe4ef7248379def23b4bde6b3f177929cba
4
- data.tar.gz: 38452296f2f644bf5fef70a262d88c6ec4e9927fdad13233401cb2d4ede544be
3
+ metadata.gz: f6dcaf7cc3b2377bc772e7a10b1aa077bf440743ef6c13e999eb9b5372c42ca7
4
+ data.tar.gz: f5c7b796716c8c823029a03fc76ad293356ee58d2e9af5b18b8d299100402b6d
5
5
  SHA512:
6
- metadata.gz: ca836822748ed4a8ae72cbac7c4192545927198b98763f0c90f9157a451077ebbdd11c45f125f387a94afd4a08f49ff56b511b20bab9636a6c22d43621fed21d
7
- data.tar.gz: 22a80940b55c4bbdb9a083cb0017999780a15a1ad57498c39a782fb0af93d561419dc2c124fd3ad270f1fdc88fddfe5839d009df40a6de8929c1840472dde858
6
+ metadata.gz: 34f34becaab28a8ad277861856eceb3ee874544c869ae52ca5c4a2aa35ad0f34d21a5484f5d4a1eb9fd5b45d6056ee996fa69fc90b3f038bc0a06999adf70d0b
7
+ data.tar.gz: 9bbad8f7dbb9748765976e84a494250b3695c167b5578b7e9077bc963df622c6990b512372a2b7bc1fb7056d7bfc1f1afdcccf3a759262ce45aa459bd7b1a249
@@ -128,6 +128,7 @@
128
128
  --default-font-family: var(--font-sans);
129
129
  --default-mono-font-family: var(--font-mono);
130
130
  --color-default-50: var(--color-gray-50);
131
+ --color-default-300: var(--color-gray-300);
131
132
  --color-default-500: var(--color-gray-500);
132
133
  --color-default-600: var(--color-gray-600);
133
134
  --color-default-700: var(--color-gray-700);
@@ -144,7 +145,6 @@
144
145
  --color-success-700: var(--color-green-700);
145
146
  --color-danger-50: var(--color-red-50);
146
147
  --color-danger-100: var(--color-red-100);
147
- --color-danger-300: var(--color-red-300);
148
148
  --color-danger-400: var(--color-red-400);
149
149
  --color-danger-600: var(--color-red-600);
150
150
  --color-danger-700: var(--color-red-700);
@@ -615,6 +615,9 @@
615
615
  .cursor-pointer {
616
616
  cursor: pointer;
617
617
  }
618
+ .appearance-none {
619
+ appearance: none;
620
+ }
618
621
  .grid-cols-1 {
619
622
  grid-template-columns: repeat(1, minmax(0, 1fr));
620
623
  }
@@ -753,6 +756,12 @@
753
756
  border-color: var(--color-gray-300);
754
757
  }
755
758
  }
759
+ .self-center {
760
+ align-self: center;
761
+ }
762
+ .justify-self-end {
763
+ justify-self: flex-end;
764
+ }
756
765
  .truncate {
757
766
  overflow: hidden;
758
767
  text-overflow: ellipsis;
@@ -1138,6 +1147,9 @@
1138
1147
  .text-blue-900 {
1139
1148
  color: var(--color-blue-900);
1140
1149
  }
1150
+ .text-danger-400 {
1151
+ color: var(--color-danger-400);
1152
+ }
1141
1153
  .text-danger-600 {
1142
1154
  color: var(--color-danger-600);
1143
1155
  }
@@ -1262,9 +1274,6 @@
1262
1274
  --tw-ring-color: color-mix(in oklab, var(--color-black) 5%, transparent);
1263
1275
  }
1264
1276
  }
1265
- .ring-danger-300 {
1266
- --tw-ring-color: var(--color-danger-300);
1267
- }
1268
1277
  .ring-danger-400 {
1269
1278
  --tw-ring-color: var(--color-danger-400);
1270
1279
  }
@@ -1287,11 +1296,8 @@
1287
1296
  outline-style: var(--tw-outline-style);
1288
1297
  outline-width: 1px;
1289
1298
  }
1290
- .-outline-offset-1 {
1291
- outline-offset: calc(1px * -1);
1292
- }
1293
- .outline-gray-300 {
1294
- outline-color: var(--color-gray-300);
1299
+ .outline-default-300 {
1300
+ outline-color: var(--color-default-300);
1295
1301
  }
1296
1302
  .filter {
1297
1303
  filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
@@ -1331,9 +1337,6 @@
1331
1337
  --tw-ease: var(--ease-out);
1332
1338
  transition-timing-function: var(--ease-out);
1333
1339
  }
1334
- .ring-inset {
1335
- --tw-ring-inset: inset;
1336
- }
1337
1340
  .group-hover\:border-primary-600 {
1338
1341
  &:is(:where(.group):hover *) {
1339
1342
  @media (hover: hover) {
@@ -1368,17 +1371,6 @@
1368
1371
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1369
1372
  }
1370
1373
  }
1371
- .focus-within\:ring-2 {
1372
- &:focus-within {
1373
- --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1374
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1375
- }
1376
- }
1377
- .focus-within\:ring-danger-400 {
1378
- &:focus-within {
1379
- --tw-ring-color: var(--color-danger-400);
1380
- }
1381
- }
1382
1374
  .focus-within\:ring-gray-400 {
1383
1375
  &:focus-within {
1384
1376
  --tw-ring-color: var(--color-gray-400);
@@ -1662,22 +1654,6 @@
1662
1654
  }
1663
1655
  }
1664
1656
  }
1665
- .focus\:outline-2 {
1666
- &:focus {
1667
- outline-style: var(--tw-outline-style);
1668
- outline-width: 2px;
1669
- }
1670
- }
1671
- .focus\:-outline-offset-2 {
1672
- &:focus {
1673
- outline-offset: calc(2px * -1);
1674
- }
1675
- }
1676
- .focus\:outline-primary-600 {
1677
- &:focus {
1678
- outline-color: var(--color-primary-600);
1679
- }
1680
- }
1681
1657
  .focus\:outline-none {
1682
1658
  &:focus {
1683
1659
  --tw-outline-style: none;
@@ -1751,6 +1727,12 @@
1751
1727
  display: none;
1752
1728
  }
1753
1729
  }
1730
+ .sm\:size-4 {
1731
+ @media (width >= 40rem) {
1732
+ width: calc(var(--spacing) * 4);
1733
+ height: calc(var(--spacing) * 4);
1734
+ }
1735
+ }
1754
1736
  .sm\:grid-cols-3 {
1755
1737
  @media (width >= 40rem) {
1756
1738
  grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -1,191 +1,209 @@
1
1
  module OkonomiUiKit
2
2
  class FormBuilder < ActionView::Helpers::FormBuilder
3
- delegate :tag, :content_tag, :safe_join, to: :@template
3
+ delegate :tag, :content_tag, :safe_join, to: :@template
4
4
 
5
- def field_set(options = {}, &block)
6
- @template.render('okonomi/forms/tailwind/field_set', options:, form: self, &block)
7
- end
8
-
9
- def field(field_id = nil, options = {}, &block)
10
- @template.render('okonomi/forms/tailwind/field', field_id:, options:, form: self, &block)
11
- end
12
-
13
- def upload_field(method, options = {})
14
- @template.render('okonomi/forms/tailwind/upload_field', method:, options:, form: self)
15
- end
16
-
17
- def text_field(method, options = {})
18
- css = input_field_classes(method, options, focus_ring: 'focus-within:ring-0.5')
19
- super(method, { autocomplete: "off" }.merge(options).merge(class: css))
20
- end
5
+ def ui
6
+ @template.ui
7
+ end
21
8
 
22
- def email_field(method, options = {})
23
- css = input_field_classes(method, options)
24
- super(method, options.merge(class: css))
25
- end
9
+ def field_set(options = {}, &block)
10
+ @template.render('okonomi/forms/tailwind/field_set', options:, form: self, &block)
11
+ end
26
12
 
27
- def url_field(method, options = {})
28
- css = input_field_classes(method, options)
29
- super(method, options.merge(class: css))
30
- end
13
+ def field(field_id = nil, options = {}, &block)
14
+ @template.render('okonomi/forms/tailwind/field', field_id:, options:, form: self, &block)
15
+ end
31
16
 
32
- def password_field(method, options = {})
33
- css = input_field_classes(method, options)
34
- super(method, options.merge(class: css))
35
- end
17
+ def upload_field(method, options = {})
18
+ @template.render('okonomi/forms/tailwind/upload_field', method:, options:, form: self)
19
+ end
36
20
 
37
- def number_field(method, options = {})
38
- css = input_field_classes(method, options)
39
- super(method, options.merge(class: css))
40
- end
21
+ def text_field(method, options = {})
22
+ css = input_field_classes(method, :text, options)
23
+ super(method, { autocomplete: "off" }.merge(options).merge(class: css))
24
+ end
41
25
 
42
- def telephone_field(method, options = {})
43
- css = input_field_classes(method, options)
44
- super(method, options.merge(class: css))
45
- end
26
+ def email_field(method, options = {})
27
+ css = input_field_classes(method, :email, options)
28
+ super(method, options.merge(class: css))
29
+ end
46
30
 
47
- def search_field(method, options = {})
48
- css = input_field_classes(method, options)
49
- super(method, options.merge(class: css))
50
- end
31
+ def url_field(method, options = {})
32
+ css = input_field_classes(method, :url, options)
33
+ super(method, options.merge(class: css))
34
+ end
51
35
 
52
- def date_field(method, options = {})
53
- css = input_field_classes(method, options)
54
- super(method, options.merge(class: css))
55
- end
36
+ def password_field(method, options = {})
37
+ css = input_field_classes(method, :password, options)
38
+ super(method, options.merge(class: css))
39
+ end
56
40
 
57
- def datetime_local_field(method, options = {})
58
- css = input_field_classes(method, options)
59
- super(method, options.merge(class: css))
60
- end
41
+ def number_field(method, options = {})
42
+ css = input_field_classes(method, :number, options)
43
+ super(method, options.merge(class: css))
44
+ end
61
45
 
62
- def time_field(method, options = {})
63
- css = input_field_classes(method, options)
64
- super(method, options.merge(class: css))
65
- end
46
+ def telephone_field(method, options = {})
47
+ css = input_field_classes(method, :telephone_field, options)
48
+ super(method, options.merge(class: css))
49
+ end
66
50
 
67
- def text_area(method, options = {})
68
- css = input_field_classes(method, options, include_disabled: false)
69
- super(method, options.merge(class: css))
70
- end
51
+ def search_field(method, options = {})
52
+ css = input_field_classes(method, :search, options)
53
+ super(method, options.merge(class: css))
54
+ end
71
55
 
72
- def select(method, choices = nil, options = {}, html_options = {}, &block)
73
- css = [
74
- select_class_base,
75
- when_errors(method,
76
- 'bg-danger-100 text-danger-600 ring-1 ring-inset ring-danger-300 focus-within:ring-2 focus-within:ring-danger-400',
77
- select_class_default_state),
78
- html_options[:class]
79
- ].compact.join(' ').split(' ').uniq
80
- super(method, choices, options, html_options.merge(class: css), &block)
81
- end
56
+ def date_field(method, options = {})
57
+ css = input_field_classes(method, :date, options)
58
+ super(method, options.merge(class: css))
59
+ end
82
60
 
83
- def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
84
- css = [
85
- select_class_base,
86
- when_errors(method,
87
- 'bg-danger-100 text-danger-600 ring-1 ring-inset ring-danger-300 focus-within:ring-2 focus-within:ring-danger-400',
88
- select_class_default_state),
89
- html_options[:class]
90
- ].compact.join(' ').split(' ').uniq
91
- super(method, collection, value_method, text_method, options, html_options.merge(class: css))
92
- end
61
+ def datetime_local_field(method, options = {})
62
+ css = input_field_classes(method, :datetime_local, options)
63
+ super(method, options.merge(class: css))
64
+ end
93
65
 
94
- def select_class_default
95
- [select_class_base, select_class_default_state].join(' ')
96
- end
66
+ def time_field(method, options = {})
67
+ css = input_field_classes(method, :time, options)
68
+ super(method, options.merge(class: css))
69
+ end
97
70
 
98
- def select_class_base
99
- "col-start-1 row-start-1 w-full rounded-md bg-white py-2 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 focus:outline-primary-600 sm:text-sm/6"
100
- end
71
+ def text_area(method, options = {})
72
+ css = input_field_classes(method, :textarea, options, include_disabled: false)
73
+ super(method, options.merge(class: css))
74
+ end
101
75
 
102
- def select_class_default_state
103
- 'ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-gray-400'
104
- end
76
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
77
+ css = [
78
+ ui.get_theme.dig(:components, :select, :root),
79
+ when_errors(
80
+ method,
81
+ ui.get_theme.dig(:components, :select, :error),
82
+ ui.get_theme.dig(:components, :select, :valid)
83
+ ),
84
+ html_options[:class]
85
+ ].compact.join(' ').split(' ').uniq
86
+
87
+ select_html = super(method, choices, options, html_options.merge(class: css), &block)
88
+ icon_html = @template.svg_icon(
89
+ ui.get_theme.dig(:components, :select, :icon, :file),
90
+ class: ui.get_theme.dig(:components, :select, :icon, :class)
91
+ )
92
+
93
+ @template.content_tag(:div, class: ui.get_theme.dig(:components, :select, :wrapper)) do
94
+ @template.concat(select_html)
95
+ @template.concat(icon_html)
96
+ end
97
+ end
105
98
 
106
- def label(method, text = nil, options = {}, &block)
107
- base_classes = "block text-sm/6 font-medium text-gray-900"
108
- super(method, text, merge_class(options, base_classes), &block)
109
- end
99
+ def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
100
+ css = [
101
+ ui.get_theme.dig(:components, :select, :root),
102
+ when_errors(
103
+ method,
104
+ ui.get_theme.dig(:components, :select, :error),
105
+ ui.get_theme.dig(:components, :select, :valid)
106
+ ),
107
+ html_options[:class]
108
+ ].compact.join(' ').split(' ').uniq
109
+
110
+ select_html = super(method, collection, value_method, text_method, options, html_options.merge(class: css))
111
+ icon_html = @template.svg_icon(
112
+ ui.get_theme.dig(:components, :select, :icon, :file),
113
+ class: ui.get_theme.dig(:components, :select, :icon, :class)
114
+ )
115
+
116
+ @template.content_tag(:div, class: ui.get_theme.dig(:components, :select, :wrapper)) do
117
+ @template.concat(select_html)
118
+ @template.concat(icon_html)
119
+ end
120
+ end
110
121
 
111
- def submit(value = nil, options = {})
112
- variant = options.delete(:variant) || 'contained'
113
- color = options.delete(:color) || 'primary'
122
+ def multi_select(method, **options)
123
+ @template.render(
124
+ partial: 'okonomi/forms/tailwind/multi_select',
125
+ locals: {
126
+ form: self,
127
+ method: method,
128
+ options: options
129
+ }
130
+ )
131
+ end
114
132
 
115
- base_classes = @template.ui.button_class(variant:, color:)
116
- super(value, merge_class(options, base_classes))
117
- end
133
+ def label(method, text = nil, options = {}, &block)
134
+ base_classes = ui.get_theme.dig(:components, :label, :root)
135
+ super(method, text, merge_class(options, base_classes), &block)
136
+ end
118
137
 
119
- def label_class(label_css = nil)
120
- ["block text-base font-medium leading-6 text-gray-900 whitespace-nowrap", label_css].compact_blank.join(' ')
121
- end
138
+ def submit(value = nil, options = {})
139
+ variant = options.delete(:variant) || 'contained'
140
+ color = options.delete(:color) || 'primary'
122
141
 
123
- def check_box_with_label(method, options = {}, checked_value = true, unchecked_value = false)
124
- @template.content_tag(:div, class: 'flex gap-2 items-center') do
125
- @template.concat check_box(
126
- method,
127
- {
128
- class: 'cursor-pointer h-4 w-4 rounded-sm border-gray-300 text-primary-600 focus:ring-0 focus:ring-primary-600'
129
- }.merge(options || {}),
130
- checked_value,
131
- unchecked_value
132
- )
133
- @template.concat @template.render('okonomi/forms/tailwind/checkbox_label', method:, options:, form: self)
142
+ base_classes = ui.button_class(variant:, color:)
143
+ super(value, merge_class(options, base_classes))
134
144
  end
135
- end
136
145
 
137
- def multi_select(method, **options)
138
- @template.render(
139
- partial: 'okonomi/forms/tailwind/multi_select',
140
- locals: {
141
- form: self,
142
- method: method,
143
- options: options
144
- }
145
- )
146
- end
147
-
148
- def show_if(field:, equals:, &block)
149
- field_id = "#{object_name}_#{field}"
150
- @template.tag.div(
151
- class: "hidden",
152
- data: {
153
- controller: "form-field-visibility",
154
- "form-field-visibility-field-id-value": field_id,
155
- "form-field-visibility-equals-value": equals
156
- },
157
- &block
158
- )
159
- end
146
+ def check_box_with_label(method, options = {}, checked_value = true, unchecked_value = false)
147
+ @template.content_tag(:div, class: ui.get_theme.dig(:components, :checkbox, :wrapper)) do
148
+ @template.concat check_box(
149
+ method,
150
+ {
151
+ class: ui.get_theme.dig(:components, :checkbox, :input, :root)
152
+ }.merge(options || {}),
153
+ checked_value,
154
+ unchecked_value
155
+ )
156
+ @template.concat @template.render('okonomi/forms/tailwind/checkbox_label', method:, options:, form: self)
157
+ end
158
+ end
160
159
 
161
- private
160
+ def show_if(field:, equals:, &block)
161
+ field_id = "#{object_name}_#{field}"
162
+ @template.tag.div(
163
+ class: "hidden",
164
+ data: {
165
+ controller: "form-field-visibility",
166
+ "form-field-visibility-field-id-value": field_id,
167
+ "form-field-visibility-equals-value": equals
168
+ },
169
+ &block
170
+ )
171
+ end
162
172
 
163
- def input_field_classes(method, options, focus_ring: 'focus-within:ring-1', include_disabled: true)
164
- css_classes = [
165
- 'w-full border-0 px-3 py-2 rounded-md ring-1 focus:outline-none',
166
- when_errors(method, 'bg-danger-100 ring-danger-400 focus:ring-danger-600', "text-gray-700 ring-gray-300 #{focus_ring} focus-within:ring-gray-400"),
167
- options[:class]
168
- ]
169
-
170
- if include_disabled
171
- css_classes << 'disabled:bg-gray-50 disabled:cursor-not-allowed'
173
+ private
174
+
175
+ def input_field_classes(method, type, options, include_disabled: true)
176
+ css_classes = [
177
+ ui.get_theme.dig(:components, :input, :types, type, :root) || ui.get_theme.dig(:components, :input, :types, :text, :root),
178
+ when_errors(
179
+ method,
180
+ ui.get_theme.dig(:components, :input, :types, type, :error) || ui.get_theme.dig(:components, :input, :types, :text, :error),
181
+ ui.get_theme.dig(:components, :input, :types, type, :valid) || ui.get_theme.dig(:components, :input, :types, :text, :valid)
182
+ ),
183
+ options[:class]
184
+ ]
185
+
186
+ if include_disabled
187
+ css_classes << (
188
+ ui.get_theme.dig(:components, :input, :types, type, :disabled) || ui.get_theme.dig(:components, :input, :types, :text, :disabled)
189
+ )
190
+ end
191
+
192
+ css_classes.compact.join(' ').split(' ').uniq
172
193
  end
173
-
174
- css_classes.compact.join(' ').split(' ').uniq
175
- end
176
194
 
177
- def when_errors(method, value, default_value = nil)
178
- key = method.to_s.gsub('_id', '').to_sym
179
- if object.errors.include?(key) || object.errors.include?(method)
180
- value
181
- else
182
- default_value
195
+ def when_errors(method, value, default_value = nil)
196
+ key = method.to_s.gsub('_id', '').to_sym
197
+ if object.errors.include?(key) || object.errors.include?(method)
198
+ value
199
+ else
200
+ default_value
201
+ end
183
202
  end
184
- end
185
203
 
186
- def merge_class(options, new_class)
187
- options[:class] = [options[:class], new_class].compact.join(" ")
188
- options
189
- end
204
+ def merge_class(options, new_class)
205
+ options[:class] = [options[:class], new_class].compact.join(" ")
206
+ options
207
+ end
190
208
  end
191
209
  end
@@ -63,6 +63,41 @@ module OkonomiUiKit
63
63
  info: "text-info-600 hover:underline"
64
64
  }
65
65
  }
66
+ },
67
+ input: {
68
+ types: {
69
+ text: {
70
+ root: "w-full border-0 px-3 py-2 rounded-md ring-1 focus:outline-none focus-within:ring-1",
71
+ error: "bg-danger-100 text-danger-400 ring-danger-400 focus:ring-danger-600",
72
+ valid: "text-default-700 ring-gray-300 focus-within:ring-gray-400",
73
+ disabled: "disabled:bg-gray-50 disabled:cursor-not-allowed"
74
+ }
75
+ }
76
+ },
77
+ select: {
78
+ root: "col-start-1 row-start-1 w-full appearance-none rounded-md py-2 pr-8 pl-3 text-base outline-1 focus:outline-none sm:text-sm/6",
79
+ error: "bg-danger-100 text-danger-400 ring-1 ring-danger-400",
80
+ valid: "bg-white outline-default-300 text-default-700",
81
+ wrapper: "grid grid-cols-1",
82
+ icon: {
83
+ file: 'heroicons/solid/chevron-down',
84
+ class: "pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4"
85
+ }
86
+ },
87
+ label: {
88
+ root: "block text-sm/6 font-medium text-default-700"
89
+ },
90
+ checkbox: {
91
+ wrapper: "flex gap-4 items-center",
92
+ input: {
93
+ root: "cursor-pointer size-5 rounded-sm border-gray-300 text-primary-600 focus:ring-0 focus:ring-primary-600"
94
+ },
95
+ label: {
96
+ root: "cursor-pointer font-medium text-gray-700"
97
+ },
98
+ hint: {
99
+ root: "cursor-pointer text-sm text-gray-400"
100
+ }
66
101
  }
67
102
  }
68
103
  }
@@ -0,0 +1,13 @@
1
+ // app/javascript/controllers/file_input_controller.js
2
+ import { Controller } from "@hotwired/stimulus"
3
+
4
+ export default class extends Controller {
5
+ connect() {
6
+ this.input = this.element
7
+ this.label = document.getElementById("file-name")
8
+
9
+ this.input.addEventListener("change", () => {
10
+ this.label.textContent = this.input.files[0]?.name || "No file chosen"
11
+ })
12
+ }
13
+ }
@@ -0,0 +1,31 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["message"]
5
+ static values = { autoDismiss: Number }
6
+
7
+ connect() {
8
+ if (this.hasAutoDismissValue && this.autoDismissValue > 0) {
9
+ setTimeout(() => {
10
+ this.dismiss()
11
+ }, this.autoDismissValue)
12
+ }
13
+ }
14
+
15
+ dismiss() {
16
+ this.element.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'
17
+ this.element.style.opacity = '0'
18
+ this.element.style.transform = 'translateX(100%)'
19
+
20
+ setTimeout(() => {
21
+ if (this.element.parentNode) {
22
+ this.element.remove()
23
+ }
24
+ }, 300)
25
+ }
26
+
27
+ close(event) {
28
+ event.preventDefault()
29
+ this.dismiss()
30
+ }
31
+ }
@@ -0,0 +1,27 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = { fieldId: String, equals: String }
5
+
6
+ connect() {
7
+ console.log("FormFieldVisibilityController connected")
8
+ this.field = document.getElementById(this.fieldIdValue)
9
+ if (this.field) {
10
+ this.toggle()
11
+ this.field.addEventListener("change", this.toggle.bind(this))
12
+ }
13
+ }
14
+
15
+ toggle() {
16
+ let currentValue
17
+
18
+ if (this.field.type === "checkbox") {
19
+ currentValue = this.field.checked ? this.field.value : null
20
+ } else {
21
+ currentValue = this.field.value
22
+ }
23
+
24
+ const shouldShow = currentValue === this.equalsValue
25
+ this.element.classList.toggle("hidden", !shouldShow)
26
+ }
27
+ }
@@ -0,0 +1,55 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["input", "filename", "dropzone", "preview", "destroyInput"]
5
+
6
+ connect() {
7
+ this.dropzoneTarget.addEventListener("dragover", (e) => {
8
+ e.preventDefault()
9
+ this.dropzoneTarget.classList.add("bg-gray-100")
10
+ })
11
+
12
+ this.dropzoneTarget.addEventListener("dragleave", () => {
13
+ this.dropzoneTarget.classList.remove("bg-gray-100")
14
+ })
15
+
16
+ this.dropzoneTarget.addEventListener("drop", (e) => {
17
+ e.preventDefault()
18
+ this.dropzoneTarget.classList.remove("bg-gray-100")
19
+ const files = e.dataTransfer.files
20
+ if (files.length > 0) {
21
+ this.inputTarget.files = files
22
+ this.updatePreview()
23
+ }
24
+ })
25
+ }
26
+
27
+ updatePreview() {
28
+ const file = this.inputTarget.files[0]
29
+ this.filenameTarget.textContent = file ? file.name : "No file selected"
30
+ this.destroyInputTarget.value = "0"
31
+
32
+ if (file && file.type.startsWith("image/")) {
33
+ const reader = new FileReader()
34
+ reader.onload = (e) => {
35
+ this.previewTarget.innerHTML = `<img src="${e.target.result}" class="max-h-32 rounded" />`
36
+ }
37
+ reader.readAsDataURL(file)
38
+ } else if (file) {
39
+ this.previewTarget.innerHTML = `
40
+ <svg class="size-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
41
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16v-4m0 0V7m0 5H3m4 0h4" />
42
+ </svg>`
43
+ }
44
+ }
45
+
46
+ clear() {
47
+ this.inputTarget.value = ""
48
+ this.filenameTarget.textContent = "No file selected"
49
+ this.previewTarget.innerHTML = `
50
+ <svg class="size-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
51
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16v-4m0 0V7m0 5H3m4 0h4" />
52
+ </svg>`
53
+ this.destroyInputTarget.value = "1"
54
+ }
55
+ }
@@ -3,7 +3,7 @@
3
3
  label_options[:for] = options[:id] if options[:id]
4
4
  %>
5
5
  <%= form.label(method, label_options) do %>
6
- <div class="cursor-pointer font-medium text-gray-700">
6
+ <div class="<%= form.ui.get_theme.dig(:components, :checkbox, :label, :root) %>">
7
7
  <% if options[:label] %>
8
8
  <%= options[:label] %>
9
9
  <% else %>
@@ -11,7 +11,7 @@
11
11
  <% end %>
12
12
  </div>
13
13
  <% if options[:hint] %>
14
- <div class="cursor-pointer text-sm text-gray-400">
14
+ <div class="<%= form.ui.get_theme.dig(:components, :checkbox, :hint, :root) %>">
15
15
  <%= t("activerecord.hints.#{form.object.class.name.underscore}.#{method}") %>
16
16
  </div>
17
17
  <% end %>
@@ -1,7 +1,7 @@
1
1
  <div class="w-full flex flex-col gap-2">
2
2
  <div class="flex justify-between items-center" x-data="{ open: false }">
3
3
  <% if field_id %>
4
- <%= form.label field_id, t("activerecord.attributes.#{form.object_name}.#{field_id}"), class: form.label_class(options.dig(:label, :class)) %>
4
+ <%= form.label field_id, t("activerecord.attributes.#{form.object_name}.#{field_id}") %>
5
5
  <% end %>
6
6
  <% if options[:hint] %>
7
7
  <div class="relative">
data/config/importmap.rb CHANGED
@@ -1,2 +1,7 @@
1
+ pin "application", to: "okonomi_ui_kit/application.js", preload: true
2
+ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
3
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
4
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
5
+
1
6
  pin "okonomi_ui_kit", to: "okonomi_ui_kit/application.js", preload: true
2
7
  pin_all_from OkonomiUiKit::Engine.root.join("app/javascript/okonomi_ui_kit/controllers"), under: "controllers", to: "okonomi_ui_kit/controllers"
@@ -29,7 +29,8 @@ module OkonomiUiKit
29
29
  include OkonomiUiKit::PageBuilderHelper
30
30
  include OkonomiUiKit::TableHelper
31
31
  include OkonomiUiKit::UiHelper
32
- # include OkonomiUiKit::FormBuilder
32
+
33
+ ActionView::Base.field_error_proc = ->(html_tag, _instance) { html_tag.html_safe }
33
34
  end
34
35
  end
35
36
  end
@@ -1,3 +1,3 @@
1
1
  module OkonomiUiKit
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: okonomi_ui_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Okonomi GmbH
@@ -1369,6 +1369,10 @@ files:
1369
1369
  - app/helpers/okonomi_ui_kit/ui_helper.rb
1370
1370
  - app/javascript/okonomi_ui_kit/application.js
1371
1371
  - app/javascript/okonomi_ui_kit/controllers/dropdown_controller.js
1372
+ - app/javascript/okonomi_ui_kit/controllers/file_input_controller.js
1373
+ - app/javascript/okonomi_ui_kit/controllers/flash_controller.js
1374
+ - app/javascript/okonomi_ui_kit/controllers/form_field_visibility_controller.js
1375
+ - app/javascript/okonomi_ui_kit/controllers/upload_controller.js
1372
1376
  - app/javascript/okonomi_ui_kit_manifest.js
1373
1377
  - app/jobs/okonomi_ui_kit/application_job.rb
1374
1378
  - app/mailers/okonomi_ui_kit/application_mailer.rb