okonomi_ui_kit 0.1.3 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 046343b54f5e77b5bfed849724f58fe4ef7248379def23b4bde6b3f177929cba
4
- data.tar.gz: 38452296f2f644bf5fef70a262d88c6ec4e9927fdad13233401cb2d4ede544be
3
+ metadata.gz: 72d7f384eb55f293ac296765d5f4c8aa7d41abef10e71e37b268f21ca1700947
4
+ data.tar.gz: 9d20c03d219a277fe26327c1c503c82e3ef4c38fc0bb0ba13e502c2e0e043b87
5
5
  SHA512:
6
- metadata.gz: ca836822748ed4a8ae72cbac7c4192545927198b98763f0c90f9157a451077ebbdd11c45f125f387a94afd4a08f49ff56b511b20bab9636a6c22d43621fed21d
7
- data.tar.gz: 22a80940b55c4bbdb9a083cb0017999780a15a1ad57498c39a782fb0af93d561419dc2c124fd3ad270f1fdc88fddfe5839d009df40a6de8929c1840472dde858
6
+ metadata.gz: 86e22f8b7cfbe2273980fe52fdf7664e3fa7e223a0dcb9da0c833db3c54aca108bf69e5393d2ee8a77f946935c51810809353a0020e8a478e7159bc9cdb0be03
7
+ data.tar.gz: 534b0a48e874565213ad9f6af731f954394db21350d37c69d2fd97f38cb2c13614bb97c82ef345903f383212ad089c7508651f91cff916b5462ba5b027977fe8
@@ -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,198 @@
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
21
-
22
- def email_field(method, options = {})
23
- css = input_field_classes(method, options)
24
- super(method, options.merge(class: css))
25
- end
5
+ def ui
6
+ @template.ui
7
+ end
26
8
 
27
- def url_field(method, options = {})
28
- css = input_field_classes(method, options)
29
- super(method, options.merge(class: css))
30
- end
9
+ def field_set(options = {}, &block)
10
+ @template.render('okonomi/forms/tailwind/field_set', options:, form: self, &block)
11
+ end
31
12
 
32
- def password_field(method, options = {})
33
- css = input_field_classes(method, options)
34
- super(method, options.merge(class: css))
35
- end
13
+ def field(field_id = nil, options = {}, &block)
14
+ @template.render('okonomi/forms/tailwind/field', field_id:, options:, form: self, &block)
15
+ end
36
16
 
37
- def number_field(method, options = {})
38
- css = input_field_classes(method, options)
39
- super(method, options.merge(class: css))
40
- end
17
+ def upload_field(method, options = {})
18
+ @template.render('okonomi/forms/tailwind/upload_field', method:, options:, form: self)
19
+ end
41
20
 
42
- def telephone_field(method, options = {})
43
- css = input_field_classes(method, options)
44
- super(method, options.merge(class: css))
45
- 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
46
25
 
47
- def search_field(method, options = {})
48
- css = input_field_classes(method, options)
49
- super(method, options.merge(class: css))
50
- end
26
+ def email_field(method, options = {})
27
+ css = input_field_classes(method, :email, options)
28
+ super(method, options.merge(class: css))
29
+ end
51
30
 
52
- def date_field(method, options = {})
53
- css = input_field_classes(method, options)
54
- super(method, options.merge(class: css))
55
- end
31
+ def url_field(method, options = {})
32
+ css = input_field_classes(method, :url, options)
33
+ super(method, options.merge(class: css))
34
+ end
56
35
 
57
- def datetime_local_field(method, options = {})
58
- css = input_field_classes(method, options)
59
- super(method, options.merge(class: css))
60
- end
36
+ def password_field(method, options = {})
37
+ css = input_field_classes(method, :password, options)
38
+ super(method, options.merge(class: css))
39
+ end
61
40
 
62
- def time_field(method, options = {})
63
- css = input_field_classes(method, options)
64
- super(method, options.merge(class: css))
65
- end
41
+ def number_field(method, options = {})
42
+ css = input_field_classes(method, :number, options)
43
+ super(method, options.merge(class: css))
44
+ end
66
45
 
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
46
+ def telephone_field(method, options = {})
47
+ css = input_field_classes(method, :telephone_field, options)
48
+ super(method, options.merge(class: css))
49
+ end
71
50
 
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
51
+ def search_field(method, options = {})
52
+ css = input_field_classes(method, :search, options)
53
+ super(method, options.merge(class: css))
54
+ end
82
55
 
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
56
+ def date_field(method, options = {})
57
+ css = input_field_classes(method, :date, options)
58
+ super(method, options.merge(class: css))
59
+ end
93
60
 
94
- def select_class_default
95
- [select_class_base, select_class_default_state].join(' ')
96
- 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
97
65
 
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
66
+ def time_field(method, options = {})
67
+ css = input_field_classes(method, :time, options)
68
+ super(method, options.merge(class: css))
69
+ end
101
70
 
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
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
105
75
 
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
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
110
98
 
111
- def submit(value = nil, options = {})
112
- variant = options.delete(:variant) || 'contained'
113
- color = options.delete(:color) || 'primary'
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
114
121
 
115
- base_classes = @template.ui.button_class(variant:, color:)
116
- super(value, merge_class(options, base_classes))
117
- end
122
+ def label(method, text = nil, options = {}, &block)
123
+ base_classes = ui.get_theme.dig(:components, :label, :root)
124
+ super(method, text, merge_class(options, base_classes), &block)
125
+ end
118
126
 
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
127
+ def submit(value = nil, options = {})
128
+ variant = options.delete(:variant) || 'contained'
129
+ color = options.delete(:color) || 'primary'
122
130
 
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)
131
+ base_classes = ui.button_class(variant:, color:)
132
+ super(value, merge_class(options, base_classes))
134
133
  end
135
- end
136
134
 
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
135
+ def check_box_with_label(method, options = {}, checked_value = true, unchecked_value = false)
136
+ @template.content_tag(:div, class: ui.get_theme.dig(:components, :checkbox, :wrapper)) do
137
+ @template.concat check_box(
138
+ method,
139
+ {
140
+ class: ui.get_theme.dig(:components, :checkbox, :input, :root)
141
+ }.merge(options || {}),
142
+ checked_value,
143
+ unchecked_value
144
+ )
145
+ @template.concat @template.render('okonomi/forms/tailwind/checkbox_label', method:, options:, form: self)
146
+ end
147
+ end
160
148
 
161
- private
149
+ def show_if(field:, equals:, &block)
150
+ field_id = "#{object_name}_#{field}"
151
+ @template.tag.div(
152
+ class: "hidden",
153
+ data: {
154
+ controller: "form-field-visibility",
155
+ "form-field-visibility-field-id-value": field_id,
156
+ "form-field-visibility-equals-value": equals
157
+ },
158
+ &block
159
+ )
160
+ end
162
161
 
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'
162
+ private
163
+
164
+ def input_field_classes(method, type, options, include_disabled: true)
165
+ css_classes = [
166
+ ui.get_theme.dig(:components, :input, :types, type, :root) || ui.get_theme.dig(:components, :input, :types, :text, :root),
167
+ when_errors(
168
+ method,
169
+ ui.get_theme.dig(:components, :input, :types, type, :error) || ui.get_theme.dig(:components, :input, :types, :text, :error),
170
+ ui.get_theme.dig(:components, :input, :types, type, :valid) || ui.get_theme.dig(:components, :input, :types, :text, :valid)
171
+ ),
172
+ options[:class]
173
+ ]
174
+
175
+ if include_disabled
176
+ css_classes << (
177
+ ui.get_theme.dig(:components, :input, :types, type, :disabled) || ui.get_theme.dig(:components, :input, :types, :text, :disabled)
178
+ )
179
+ end
180
+
181
+ css_classes.compact.join(' ').split(' ').uniq
172
182
  end
173
-
174
- css_classes.compact.join(' ').split(' ').uniq
175
- end
176
183
 
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
184
+ def when_errors(method, value, default_value = nil)
185
+ key = method.to_s.gsub('_id', '').to_sym
186
+ if object.errors.include?(key) || object.errors.include?(method)
187
+ value
188
+ else
189
+ default_value
190
+ end
183
191
  end
184
- end
185
192
 
186
- def merge_class(options, new_class)
187
- options[:class] = [options[:class], new_class].compact.join(" ")
188
- options
189
- end
193
+ def merge_class(options, new_class)
194
+ options[:class] = [options[:class], new_class].compact.join(" ")
195
+ options
196
+ end
190
197
  end
191
198
  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.4"
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.4
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