nitro_kit 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +6 -4
  4. data/app/components/nitro_kit/accordion.rb +68 -32
  5. data/app/components/nitro_kit/alert.rb +69 -0
  6. data/app/components/nitro_kit/avatar.rb +52 -0
  7. data/app/components/nitro_kit/badge.rb +46 -19
  8. data/app/components/nitro_kit/button.rb +99 -66
  9. data/app/components/nitro_kit/button_group.rb +18 -13
  10. data/app/components/nitro_kit/card.rb +49 -9
  11. data/app/components/nitro_kit/checkbox.rb +59 -41
  12. data/app/components/nitro_kit/checkbox_group.rb +38 -0
  13. data/app/components/nitro_kit/combobox.rb +138 -0
  14. data/app/components/nitro_kit/component.rb +45 -14
  15. data/app/components/nitro_kit/datepicker.rb +9 -0
  16. data/app/components/nitro_kit/dialog.rb +95 -0
  17. data/app/components/nitro_kit/dropdown.rb +112 -70
  18. data/app/components/nitro_kit/field.rb +221 -56
  19. data/app/components/nitro_kit/field_group.rb +12 -6
  20. data/app/components/nitro_kit/fieldset.rb +42 -7
  21. data/app/components/nitro_kit/form_builder.rb +45 -22
  22. data/app/components/nitro_kit/icon.rb +29 -8
  23. data/app/components/nitro_kit/input.rb +20 -10
  24. data/app/components/nitro_kit/label.rb +18 -5
  25. data/app/components/nitro_kit/pagination.rb +98 -0
  26. data/app/components/nitro_kit/radio_button.rb +28 -27
  27. data/app/components/nitro_kit/radio_button_group.rb +53 -0
  28. data/app/components/nitro_kit/select.rb +72 -0
  29. data/app/components/nitro_kit/switch.rb +49 -39
  30. data/app/components/nitro_kit/table.rb +56 -0
  31. data/app/components/nitro_kit/tabs.rb +98 -0
  32. data/app/components/nitro_kit/textarea.rb +26 -0
  33. data/app/components/nitro_kit/toast.rb +104 -0
  34. data/app/components/nitro_kit/tooltip.rb +53 -0
  35. data/app/helpers/nitro_kit/accordion_helper.rb +2 -0
  36. data/app/helpers/nitro_kit/alert_helper.rb +11 -0
  37. data/app/helpers/nitro_kit/avatar_helper.rb +9 -0
  38. data/app/helpers/nitro_kit/badge_helper.rb +3 -5
  39. data/app/helpers/nitro_kit/button_group_helper.rb +2 -0
  40. data/app/helpers/nitro_kit/button_helper.rb +37 -28
  41. data/app/helpers/nitro_kit/card_helper.rb +2 -0
  42. data/app/helpers/nitro_kit/checkbox_helper.rb +19 -16
  43. data/app/helpers/nitro_kit/combobox_helper.rb +9 -0
  44. data/app/helpers/nitro_kit/datepicker_helper.rb +9 -0
  45. data/app/helpers/nitro_kit/dialog_helper.rb +9 -0
  46. data/app/helpers/nitro_kit/dropdown_helper.rb +3 -1
  47. data/app/helpers/nitro_kit/field_group_helper.rb +9 -0
  48. data/app/helpers/nitro_kit/field_helper.rb +4 -2
  49. data/app/helpers/nitro_kit/fieldset_helper.rb +9 -0
  50. data/app/helpers/nitro_kit/form_helper.rb +13 -0
  51. data/app/helpers/nitro_kit/icon_helper.rb +3 -1
  52. data/app/helpers/nitro_kit/input_helper.rb +35 -0
  53. data/app/helpers/nitro_kit/label_helper.rb +12 -9
  54. data/app/helpers/nitro_kit/pagination_helper.rb +42 -0
  55. data/app/helpers/nitro_kit/radio_button_helper.rb +15 -12
  56. data/app/helpers/nitro_kit/select_helper.rb +24 -0
  57. data/app/helpers/nitro_kit/switch_helper.rb +4 -10
  58. data/app/helpers/nitro_kit/table_helper.rb +9 -0
  59. data/app/helpers/nitro_kit/tabs_helper.rb +9 -0
  60. data/app/helpers/nitro_kit/textarea_helper.rb +9 -0
  61. data/app/helpers/nitro_kit/toast_helper.rb +36 -0
  62. data/app/helpers/nitro_kit/tooltip_helper.rb +9 -0
  63. data/lib/generators/nitro_kit/add_generator.rb +38 -41
  64. data/lib/generators/nitro_kit/install_generator.rb +2 -1
  65. data/lib/nitro_kit/engine.rb +4 -0
  66. data/lib/nitro_kit/schema_builder.rb +90 -16
  67. data/lib/nitro_kit/version.rb +1 -1
  68. data/lib/nitro_kit.rb +39 -1
  69. data/lib/tasks/nitro_kit_tasks.rake +4 -0
  70. metadata +37 -10
  71. data/app/components/nitro_kit/radio_group.rb +0 -35
  72. data/app/helpers/application_helper.rb +0 -109
  73. data/lib/nitro_kit/railtie.rb +0 -8
@@ -1,36 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class Field < Component
3
- FIELD = [
4
- "flex flex-col gap-2 align-start",
5
- "[&:has([data-slot='error'])_[data-slot='label']]:text-destructive",
6
- "[&:has([data-slot='error'])_[data-slot='control']]:border-destructive"
7
- ].freeze
8
-
9
- DESCRIPTION = "text-sm text-muted-foreground"
10
- ERROR = "text-sm text-destructive"
11
-
12
5
  def initialize(
13
- name,
6
+ form = nil,
7
+ field_name = nil,
14
8
  as: :string,
15
9
  label: nil,
16
10
  description: nil,
17
11
  errors: nil,
18
12
  **attrs
19
13
  )
20
- super(**attrs)
21
-
22
- @name = name
14
+ @form = form
15
+ @field_name = field_name.to_s
23
16
  @as = as.to_sym
24
17
 
18
+ @name = attrs[:name] || form&.field_name(field_name)
19
+ @id = attrs[:id] || form&.field_id(field_name)
20
+
21
+ # select
22
+ @options = attrs[:options]
23
+
25
24
  @field_attrs = attrs
26
- @field_label = label || name.to_s.humanize
25
+ @field_label = label || field_name.to_s.humanize
27
26
  @field_description = description
28
27
  @field_error_messages = errors
28
+
29
+ super(
30
+ attrs,
31
+ data: {as: @as},
32
+ class: base_class
33
+ )
29
34
  end
30
35
 
31
36
  attr_reader(
32
- :name,
33
37
  :as,
38
+ :form,
39
+ :name,
40
+ :id,
34
41
  :field_attrs,
35
42
  :field_label,
36
43
  :field_description,
@@ -38,54 +45,35 @@ module NitroKit
38
45
  )
39
46
 
40
47
  def view_template
41
- div(**attrs, class: merge(FIELD, attrs[:class])) do
42
- if !block_given?
43
- default_field
44
- else
48
+ div(**attrs) do
49
+ if block_given?
45
50
  yield
51
+ else
52
+ default_field
46
53
  end
47
54
  end
48
55
  end
49
56
 
50
- alias :original_label :label
57
+ alias :html_label :label
51
58
 
52
59
  def label(text = nil, **attrs)
53
60
  text ||= field_label
54
61
 
55
62
  return unless text
56
63
 
57
- render(
58
- Label.new(
59
- **attrs,
60
- data: data_merge({slot: "label"}, attrs[:data])
61
- )
62
- ) do
64
+ render(Label.new(**mattr(attrs, for: id, data: {slot: "label"}))) do
63
65
  text
64
66
  end
65
67
  end
66
68
 
67
- def description(text = nil, **attrs)
69
+ def description(text = nil, **attrs, &block)
68
70
  text ||= field_description
69
71
 
70
- return unless text
72
+ return unless text || block_given?
71
73
 
72
- div(
73
- data: {slot: "description"},
74
- class: merge(DESCRIPTION, attrs[:class])
75
- ) { text }
76
- end
77
-
78
- alias :original_input :input
79
-
80
- def input(**attrs)
81
- render(
82
- Input.new(
83
- name:,
84
- **field_attrs,
85
- **attrs,
86
- data: {slot: "control", **data_merge(field_attrs[:data], attrs[:data])}
87
- )
88
- )
74
+ div(**mattr(attrs, data: {slot: "description"}, class: description_class)) do
75
+ text_or_block(text, &block)
76
+ end
89
77
  end
90
78
 
91
79
  def errors(error_messages = nil, **attrs)
@@ -93,31 +81,208 @@ module NitroKit
93
81
 
94
82
  return unless error_messages&.any?
95
83
 
96
- ul(
97
- **attrs,
98
- data: {slot: "error"},
99
- class: merge([ERROR, attrs[:class]])
100
- ) do |msg|
84
+ ul(**mattr(attrs, data: {slot: "error"}, class: error_class)) do |msg|
101
85
  error_messages.each do |msg|
102
86
  li { msg }
103
87
  end
104
88
  end
105
89
  end
106
90
 
107
- def control(**override_attrs)
91
+ def control(**attrs)
108
92
  case as
109
93
  when :string
110
- input(**override_attrs)
94
+ input(**attrs)
95
+ when
96
+ :button,
97
+ :color,
98
+ :date,
99
+ :datetime,
100
+ :datetime_local,
101
+ :email,
102
+ :file,
103
+ :hidden,
104
+ :month,
105
+ :number,
106
+ :password,
107
+ :range,
108
+ :search,
109
+ :tel,
110
+ :text,
111
+ :time,
112
+ :url,
113
+ :week
114
+ input(type: as, **attrs)
115
+ when :select
116
+ select(**attrs)
117
+ when :textarea
118
+ textarea(**attrs)
119
+ when :checkbox
120
+ checkbox(**attrs)
121
+ when :combobox
122
+ combobox(**attrs)
123
+ when :radio, :radio_button, :radio_group
124
+ radio_group(**attrs)
125
+ when :switch
126
+ switch(**attrs)
127
+ else
128
+ raise ArgumentError, "Invalid field type `#{as}'"
111
129
  end
112
130
  end
113
131
 
114
132
  private
115
133
 
116
134
  def default_field
117
- label
118
- description
119
- control
120
- errors
135
+ case as
136
+ when :checkbox
137
+ control
138
+ label
139
+ description
140
+ errors
141
+ else
142
+ label
143
+ description
144
+ control
145
+ errors
146
+ end
147
+ end
148
+
149
+ def control_attrs(**attrs)
150
+ mattr(
151
+ attrs,
152
+ name:,
153
+ id:,
154
+ value: value_before_typecast,
155
+ data: {slot: "control"}
156
+ )
157
+ end
158
+
159
+ alias :html_input :input
160
+
161
+ def input(**attrs)
162
+ render(
163
+ Input.new(
164
+ **control_attrs(
165
+ **field_attrs,
166
+ **attrs
167
+ )
168
+ )
169
+ )
170
+ end
171
+
172
+ alias :html_select :select
173
+
174
+ def select(options: nil, **attrs)
175
+ render(
176
+ Select.new(
177
+ options || @options || [],
178
+ **control_attrs(
179
+ **field_attrs,
180
+ **attrs
181
+ )
182
+ )
183
+ )
184
+ end
185
+
186
+ alias :html_textarea :textarea
187
+
188
+ def textarea(**attrs)
189
+ render(
190
+ Textarea.new(
191
+ **control_attrs(
192
+ **field_attrs,
193
+ **attrs
194
+ )
195
+ )
196
+ )
197
+ end
198
+
199
+ def checkbox(**attrs)
200
+ render(
201
+ Checkbox.new(
202
+ **control_attrs(
203
+ **field_attrs,
204
+ **attrs
205
+ )
206
+ )
207
+ )
208
+ end
209
+
210
+ def combobox(**attrs)
211
+ render(
212
+ Combobox.new(
213
+ **control_attrs(
214
+ **field_attrs,
215
+ **attrs
216
+ )
217
+ )
218
+ )
219
+ end
220
+
221
+ def radio_group(options: nil, **attrs)
222
+ render(
223
+ RadioButtonGroup.new(
224
+ options || @options || [],
225
+ **control_attrs(
226
+ **field_attrs,
227
+ **attrs
228
+ )
229
+ )
230
+ )
231
+ end
232
+
233
+ def switch(**attrs)
234
+ # TODO: support use in forms
235
+ render(
236
+ Switch.new(
237
+ **control_attrs(
238
+ **field_attrs,
239
+ **attrs
240
+ )
241
+ )
242
+ )
243
+ end
244
+
245
+ private
246
+
247
+ def base_class
248
+ [
249
+ "grid align-start",
250
+ # Margins
251
+ "[&>[data-slot=label]+[data-slot=control]]:mt-2 [&>[data-slot=label]+[data-slot=description]]:mt-1 [&>[data-slot=description]+[data-slot=control]]:mt-3 [&>[data-slot=control]+[data-slot=description]]:mt-3 [&>[data-slot=control]+[data-slot=error]]:mt-2",
252
+ # When checkbox
253
+ "[&[data-as=checkbox]]:grid-cols-[auto_1fr] data-[as=checkbox]:gap-x-2 [&[data-as=checkbox]_[data-slot=description]]:col-start-2",
254
+ "[&:has([data-slot=error])_[data-slot=control]]:border-destructive"
255
+ ]
256
+ end
257
+
258
+ def description_class
259
+ "text-sm text-muted-foreground"
260
+ end
261
+
262
+ def error_class
263
+ "text-sm text-destructive"
264
+ end
265
+
266
+ def value
267
+ return attrs[:value] if attrs[:value]
268
+ return unless object = form&.object
269
+
270
+ if object.respond_to?(@field_name)
271
+ object.public_send(@field_name)
272
+ end
273
+ end
274
+
275
+ def value_before_typecast
276
+ return attrs[:value] if attrs[:value]
277
+ return unless object = form&.object
278
+
279
+ method_before_type_cast = @field_name + "_before_type_cast"
280
+
281
+ if object.respond_to?(method_before_type_cast)
282
+ object.public_send(method_before_type_cast)
283
+ else
284
+ value
285
+ end
121
286
  end
122
287
  end
123
288
  end
@@ -1,13 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class FieldGroup < Component
3
- FIELD_GROUP_BASE = "space-y-6"
5
+ def initialize(**attrs)
6
+ super(attrs, class: base_class, data: {slot: "control"})
7
+ end
4
8
 
5
9
  def view_template(&block)
6
- div(
7
- **attrs,
8
- class: merge([FIELD_GROUP_BASE, attrs[:class]]),
9
- &block
10
- )
10
+ div(**attrs, &block)
11
+ end
12
+
13
+ private
14
+
15
+ def base_class
16
+ "space-y-6"
11
17
  end
12
18
  end
13
19
  end
@@ -1,16 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class Fieldset < Component
3
- FIELDSET_BASE = "space-y-6"
4
-
5
- def initialize(legend, **attrs)
5
+ def initialize(legend: nil, description: nil, **attrs)
6
6
  @legend = legend
7
- @attrs = attrs
7
+ @description = description
8
+ super(
9
+ attrs,
10
+ class: base_class
11
+ )
12
+ end
13
+
14
+ def view_template
15
+ fieldset(**attrs) do
16
+ legend(@legend) if @legend
17
+ description(@description) if @description
18
+
19
+ yield
20
+ end
21
+ end
22
+
23
+ alias :html_legend :legend
24
+
25
+ def legend(text = nil, **attrs, &block)
26
+ html_legend(**mattr(attrs, class: legend_class)) do
27
+ text_or_block(text, &block)
28
+ end
29
+ end
30
+
31
+ def description(text = nil, **attrs, &block)
32
+ div(**mattr(attrs, class: description_class, data: {slot: "text"})) do
33
+ text_or_block(text, &block)
34
+ end
8
35
  end
9
36
 
10
- attr_reader :legend, :attrs
37
+ private
38
+
39
+ def base_class
40
+ "[&>*+[data-slot=control]]:mt-6 [&>*+[data-slot=text]]:mt-1"
41
+ end
42
+
43
+ def legend_class
44
+ "text-lg font-semibold"
45
+ end
11
46
 
12
- def view_template(&block)
13
- fieldset(**attrs, class: FIELDSET_BASE, &block)
47
+ def description_class
48
+ "text-sm text-muted-foreground"
14
49
  end
15
50
  end
16
51
  end
@@ -1,43 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class FormBuilder < ActionView::Helpers::FormBuilder
3
5
  # Fields
4
6
 
5
- def fieldset(options = {}, &block)
6
- content = @template.capture(&block)
7
- @template.render(NitroKit::Fieldset.new(options)) { content }
7
+ def fieldset(**attrs, &block)
8
+ @template.render(NitroKit::Fieldset.new(**attrs), &block)
8
9
  end
9
10
 
10
- def field_group(options = {}, &block)
11
- content = @template.capture(&block)
12
- @template.render(NitroKit::FieldGroup.new(**options)) { content }
13
- end
11
+ def field(field_name, **attrs, &block)
12
+ label = attrs.fetch(:label, field_name.to_s.humanize)
14
13
 
15
- def field(object_name, **options)
16
- label = options.fetch(:label, object_name.to_s.humanize)
17
- errors = object.errors.include?(object_name) ? object.errors.full_messages_for(object_name) : nil
14
+ errors = object && object.errors.include?(field_name) ? object.errors.full_messages_for(field_name) : nil
18
15
 
19
- @template.render(NitroKit::Field.new(object_name, label:, errors:, **options))
16
+ @template.render(NitroKit::Field.new(self, field_name, label:, errors:, **attrs), &block)
20
17
  end
21
18
 
22
- # Inputs
23
-
24
- def label(object_name, method, content_or_options = nil, options = nil, &block)
19
+ def group(**attrs, &block)
20
+ @template.render(FieldGroup.new(**attrs), &block)
25
21
  end
26
22
 
27
- def checkbox(method, options = {})
28
- @template.checkbox(@object_name, method, objectify_options(options), label: options[:label])
29
- end
23
+ # Inputs
24
+
25
+ %i[
26
+ checkbox
27
+ color_field
28
+ date_field
29
+ datetime_field
30
+ datetime_local_field
31
+ email_field
32
+ file_field
33
+ hidden_field
34
+ month_field
35
+ number_field
36
+ password_field
37
+ phone_field
38
+ radio_button
39
+ range_field
40
+ search_field
41
+ telephone_field
42
+ text_area
43
+ text_field
44
+ time_field
45
+ url_field
46
+ week_field
47
+ ]
48
+ .each do |method|
49
+ define_method(method) do |*args, **attrs, &block|
50
+ @template.send("nk_#{method}", *args, **attrs, &block)
51
+ end
52
+ end
30
53
 
31
- alias_method :check_box, :checkbox
54
+ # Buttons
32
55
 
33
- def submit(value = "Save changes", **options)
56
+ def submit(value = "Save changes", **attrs)
34
57
  content = value || @template.capture(&block)
35
- @template.render(NitroKit::Button.new(variant: :primary, type: :submit, **options)) { content }
58
+ @template.render(NitroKit::Button.new(variant: :primary, type: :submit, **attrs)) { content }
36
59
  end
37
60
 
38
- def button(value = "Save changes", **options)
61
+ def button(value = "Save changes", **attrs)
39
62
  content = value || @template.capture(&block)
40
- @template.render(NitroKit::Button.new(**options)) { content }
63
+ @template.render(NitroKit::Button.new(**attrs)) { content }
41
64
  end
42
65
  end
43
66
  end
@@ -1,23 +1,44 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class Icon < Component
3
5
  include Phlex::Rails::Helpers::ContentTag
4
6
  include LucideRails::RailsHelper
5
7
 
6
- SIZE = {
7
- sm: "size-4",
8
- base: "size-5"
9
- }
10
-
11
- def initialize(name:, size: :base, **attrs)
8
+ def initialize(name, size: :md, **attrs)
12
9
  @name = name
13
10
  @size = size
14
- @attrs = attrs
11
+
12
+ super(
13
+ attrs,
14
+ class: size_class,
15
+ stroke_width: 1.5
16
+ )
15
17
  end
16
18
 
17
19
  attr_reader :name, :size
18
20
 
19
21
  def view_template
20
- lucide_icon(name, **attrs, class: merge([SIZE[size], attrs[:class]]))
22
+ lucide_icon(name, **dasherized_attrs)
23
+ end
24
+
25
+ private
26
+
27
+ def size_class
28
+ case size
29
+ when :sm
30
+ "size-4"
31
+ when :md
32
+ "size-5"
33
+ when :lg
34
+ "size-7"
35
+ else
36
+ raise ArgumentError, "Unknown size `#{size}'"
37
+ end
38
+ end
39
+
40
+ def dasherized_attrs
41
+ attrs.transform_keys { |k| k.to_s.dasherize }
21
42
  end
22
43
  end
23
44
  end
@@ -1,16 +1,26 @@
1
- module NitroKit
2
- INPUT = [
3
- "rounded-md border bg-background border-border text-base px-3 py-2",
4
- "focus:outline-none ring-ring ring-offset-2 ring-offset-background focus-visible:ring-2",
5
- ""
6
- ].freeze
1
+ # frozen_string_literal: true
7
2
 
3
+ module NitroKit
8
4
  class Input < Component
9
- def view_template
10
- input(
11
- **attrs,
12
- class: merge(INPUT, attrs[:class])
5
+ def initialize(**attrs)
6
+ super(
7
+ attrs,
8
+ class: base_class
13
9
  )
14
10
  end
11
+
12
+ def view_template
13
+ input(**attrs)
14
+ end
15
+
16
+ private
17
+
18
+ def base_class
19
+ [
20
+ "block rounded-md border bg-background border-border text-base px-3 py-2 h-10",
21
+ # Focus
22
+ "focus:outline-none ring-ring ring-offset-2 ring-offset-background focus-visible:ring-2"
23
+ ]
24
+ end
15
25
  end
16
26
  end
@@ -1,10 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class Label < Component
3
- def view_template
4
- label(
5
- **attrs,
6
- class: merge(["text-sm font-medium select-none", attrs[:class]])
7
- ) { yield }
5
+ def initialize(text = nil, **attrs)
6
+ @text = text
7
+
8
+ super(
9
+ attrs,
10
+ class: "text-sm font-medium select-none",
11
+ data: {slot: "label"}
12
+ )
13
+ end
14
+
15
+ attr_reader :text
16
+
17
+ def view_template(&block)
18
+ label(**attrs) do
19
+ text_or_block(text, &block)
20
+ end
8
21
  end
9
22
  end
10
23
  end