nitro_kit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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