nitro_kit 0.9.0 → 2.0.0.alpha.2

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 (219) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/plugins/marketplace.json +20 -0
  3. data/CHANGELOG.md +172 -0
  4. data/LICENSE +28 -0
  5. data/README.md +22 -15
  6. data/STYLE_GUIDE.md +425 -0
  7. data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +38 -0
  8. data/app/assets/stylesheets/nitro_kit.css +7239 -0
  9. data/app/components/nitro_kit/accordion.rb +125 -65
  10. data/app/components/nitro_kit/alert.rb +80 -43
  11. data/app/components/nitro_kit/app_navigation.rb +307 -0
  12. data/app/components/nitro_kit/app_shell.rb +227 -0
  13. data/app/components/nitro_kit/appearance_bootstrap.rb +146 -0
  14. data/app/components/nitro_kit/appearance_picker.rb +156 -0
  15. data/app/components/nitro_kit/auth_shell.rb +34 -0
  16. data/app/components/nitro_kit/avatar.rb +85 -29
  17. data/app/components/nitro_kit/avatar_stack.rb +155 -9
  18. data/app/components/nitro_kit/badge.rb +43 -81
  19. data/app/components/nitro_kit/button.rb +180 -103
  20. data/app/components/nitro_kit/button_group.rb +63 -11
  21. data/app/components/nitro_kit/button_to.rb +69 -0
  22. data/app/components/nitro_kit/card.rb +42 -45
  23. data/app/components/nitro_kit/checkbox.rb +115 -62
  24. data/app/components/nitro_kit/checkbox_group.rb +131 -22
  25. data/app/components/nitro_kit/choice.rb +55 -0
  26. data/app/components/nitro_kit/combobox.rb +292 -105
  27. data/app/components/nitro_kit/command_palette.rb +412 -0
  28. data/app/components/nitro_kit/component.rb +322 -46
  29. data/app/components/nitro_kit/container.rb +32 -0
  30. data/app/components/nitro_kit/control_group.rb +45 -0
  31. data/app/components/nitro_kit/danger_zone.rb +78 -0
  32. data/app/components/nitro_kit/data_section.rb +106 -0
  33. data/app/components/nitro_kit/details_table.rb +229 -0
  34. data/app/components/nitro_kit/dialog.rb +257 -83
  35. data/app/components/nitro_kit/dropdown.rb +335 -122
  36. data/app/components/nitro_kit/dropzone.rb +326 -0
  37. data/app/components/nitro_kit/empty_state.rb +99 -0
  38. data/app/components/nitro_kit/field.rb +461 -236
  39. data/app/components/nitro_kit/field_group.rb +15 -8
  40. data/app/components/nitro_kit/fieldset.rb +37 -39
  41. data/app/components/nitro_kit/flex.rb +49 -0
  42. data/app/components/nitro_kit/form_builder.rb +313 -72
  43. data/app/components/nitro_kit/form_section.rb +79 -0
  44. data/app/components/nitro_kit/grid.rb +34 -0
  45. data/app/components/nitro_kit/icon.rb +54 -25
  46. data/app/components/nitro_kit/input.rb +97 -10
  47. data/app/components/nitro_kit/label.rb +22 -6
  48. data/app/components/nitro_kit/layout_options.rb +7 -0
  49. data/app/components/nitro_kit/page_header.rb +76 -0
  50. data/app/components/nitro_kit/pagination.rb +355 -69
  51. data/app/components/nitro_kit/pagination_bar.rb +90 -0
  52. data/app/components/nitro_kit/progressive_image.rb +181 -0
  53. data/app/components/nitro_kit/radio_button.rb +77 -55
  54. data/app/components/nitro_kit/radio_button_group.rb +107 -36
  55. data/app/components/nitro_kit/responsive_value.rb +94 -0
  56. data/app/components/nitro_kit/rich_text_area.rb +31 -0
  57. data/app/components/nitro_kit/select.rb +129 -64
  58. data/app/components/nitro_kit/settings_layout.rb +152 -0
  59. data/app/components/nitro_kit/sheet.rb +202 -0
  60. data/app/components/nitro_kit/stat_grid.rb +75 -0
  61. data/app/components/nitro_kit/switch.rb +108 -51
  62. data/app/components/nitro_kit/table.rb +237 -45
  63. data/app/components/nitro_kit/tabs.rb +168 -63
  64. data/app/components/nitro_kit/textarea.rb +81 -10
  65. data/app/components/nitro_kit/toast.rb +211 -80
  66. data/app/components/nitro_kit/toolbar.rb +75 -0
  67. data/app/components/nitro_kit/tooltip.rb +235 -34
  68. data/app/components/nitro_kit/typeset.rb +28 -0
  69. data/app/javascript/controllers/nk/app_shell_controller.js +224 -0
  70. data/app/javascript/controllers/nk/appearance_controller.js +73 -0
  71. data/app/javascript/controllers/nk/avatar_controller.js +15 -0
  72. data/app/javascript/controllers/nk/button_controller.js +56 -0
  73. data/app/javascript/controllers/nk/checkable_controller.js +41 -0
  74. data/app/javascript/controllers/nk/combobox_controller.js +314 -82
  75. data/app/javascript/controllers/nk/command_palette_controller.js +249 -0
  76. data/app/javascript/controllers/nk/dialog_controller.js +14 -10
  77. data/app/javascript/controllers/nk/dropdown_controller.js +106 -58
  78. data/app/javascript/controllers/nk/dropzone/direct_upload.js +61 -0
  79. data/app/javascript/controllers/nk/dropzone/file_rules.js +77 -0
  80. data/app/javascript/controllers/nk/dropzone/form_submit_lock.js +30 -0
  81. data/app/javascript/controllers/nk/dropzone_controller.js +482 -0
  82. data/app/javascript/controllers/nk/overlay_position.js +37 -0
  83. data/app/javascript/controllers/nk/progressive_image_controller.js +116 -0
  84. data/app/javascript/controllers/nk/tabs_controller.js +135 -23
  85. data/app/javascript/controllers/nk/toast_controller.js +112 -45
  86. data/app/javascript/controllers/nk/tooltip_controller.js +12 -47
  87. data/config/importmap.rb +2 -0
  88. data/config/locales/en.yml +98 -0
  89. data/docs/agent_guide.md +139 -0
  90. data/docs/agent_native_spec.md +422 -0
  91. data/docs/component_contracts.md +263 -0
  92. data/docs/customization.md +595 -0
  93. data/docs/hotwire.md +94 -0
  94. data/docs/initialization_prompt.md +36 -0
  95. data/docs/migration_1_to_2.md +318 -0
  96. data/docs/new_app_strategy.md +22 -0
  97. data/docs/patterns/application_foundation.md +136 -0
  98. data/docs/patterns/crud_resource.md +144 -0
  99. data/docs/patterns/destructive_action.md +105 -0
  100. data/docs/patterns/flash_and_toast.md +57 -0
  101. data/docs/patterns/inline_edit.md +59 -0
  102. data/docs/patterns/queryable_collection.md +187 -0
  103. data/docs/patterns/resource_form.md +126 -0
  104. data/docs/rails_conventions.md +95 -0
  105. data/docs/rails_integration.md +514 -0
  106. data/lib/generators/nitro_kit/install_generator.rb +48 -0
  107. data/lib/generators/nitro_kit/upgrade_smoke_tests_generator.rb +67 -0
  108. data/lib/nitro_kit/engine.rb +20 -0
  109. data/lib/nitro_kit/installation.rb +671 -0
  110. data/lib/nitro_kit/migration_inventory.rb +345 -0
  111. data/lib/nitro_kit/upgrade_smoke_test.rb +403 -0
  112. data/lib/nitro_kit/version.rb +1 -1
  113. data/lib/nitro_kit.rb +5 -42
  114. data/lib/rails/commands/nitro_kit/nitro_kit_command.rb +42 -0
  115. data/lib/tasks/nitro_kit_tasks.rake +79 -4
  116. data/plugins/nitro-kit/.codex-plugin/plugin.json +28 -0
  117. data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +67 -0
  118. data/plugins/nitro-kit/skills/nitro-kit-hotwire/agents/openai.yaml +4 -0
  119. data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +60 -0
  120. data/plugins/nitro-kit/skills/nitro-kit-rails/agents/openai.yaml +4 -0
  121. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +62 -0
  122. data/plugins/nitro-kit/skills/nitro-kit-ui/agents/openai.yaml +4 -0
  123. data/src/stylesheets/nitro_kit/components/accordion.css +119 -0
  124. data/src/stylesheets/nitro_kit/components/alert.css +82 -0
  125. data/src/stylesheets/nitro_kit/components/app_navigation.css +156 -0
  126. data/src/stylesheets/nitro_kit/components/app_shell.css +605 -0
  127. data/src/stylesheets/nitro_kit/components/appearance_picker.css +117 -0
  128. data/src/stylesheets/nitro_kit/components/auth_shell.css +8 -0
  129. data/src/stylesheets/nitro_kit/components/avatar.css +81 -0
  130. data/src/stylesheets/nitro_kit/components/avatar_stack.css +80 -0
  131. data/src/stylesheets/nitro_kit/components/badge.css +76 -0
  132. data/src/stylesheets/nitro_kit/components/button.css +283 -0
  133. data/src/stylesheets/nitro_kit/components/button_group.css +53 -0
  134. data/src/stylesheets/nitro_kit/components/button_to.css +6 -0
  135. data/src/stylesheets/nitro_kit/components/card.css +70 -0
  136. data/src/stylesheets/nitro_kit/components/checkbox.css +160 -0
  137. data/src/stylesheets/nitro_kit/components/checkbox_group.css +61 -0
  138. data/src/stylesheets/nitro_kit/components/combobox.css +148 -0
  139. data/src/stylesheets/nitro_kit/components/command_palette.css +255 -0
  140. data/src/stylesheets/nitro_kit/components/container.css +25 -0
  141. data/src/stylesheets/nitro_kit/components/control_group.css +149 -0
  142. data/src/stylesheets/nitro_kit/components/danger_zone.css +54 -0
  143. data/src/stylesheets/nitro_kit/components/data_section.css +40 -0
  144. data/src/stylesheets/nitro_kit/components/details_table.css +53 -0
  145. data/src/stylesheets/nitro_kit/components/dialog.css +80 -0
  146. data/src/stylesheets/nitro_kit/components/dropdown.css +152 -0
  147. data/src/stylesheets/nitro_kit/components/dropzone.css +198 -0
  148. data/src/stylesheets/nitro_kit/components/empty_state.css +53 -0
  149. data/src/stylesheets/nitro_kit/components/field.css +79 -0
  150. data/src/stylesheets/nitro_kit/components/field_group.css +6 -0
  151. data/src/stylesheets/nitro_kit/components/fieldset.css +28 -0
  152. data/src/stylesheets/nitro_kit/components/flex.css +455 -0
  153. data/src/stylesheets/nitro_kit/components/form_section.css +71 -0
  154. data/src/stylesheets/nitro_kit/components/grid.css +308 -0
  155. data/src/stylesheets/nitro_kit/components/icon.css +26 -0
  156. data/src/stylesheets/nitro_kit/components/input.css +80 -0
  157. data/src/stylesheets/nitro_kit/components/label.css +9 -0
  158. data/src/stylesheets/nitro_kit/components/layout.css +440 -0
  159. data/src/stylesheets/nitro_kit/components/page_header.css +61 -0
  160. data/src/stylesheets/nitro_kit/components/pagination.css +119 -0
  161. data/src/stylesheets/nitro_kit/components/pagination_bar.css +41 -0
  162. data/src/stylesheets/nitro_kit/components/palette.css +113 -0
  163. data/src/stylesheets/nitro_kit/components/progressive_image.css +139 -0
  164. data/src/stylesheets/nitro_kit/components/radio_button.css +127 -0
  165. data/src/stylesheets/nitro_kit/components/radio_button_group.css +143 -0
  166. data/src/stylesheets/nitro_kit/components/rich_text_area.css +21 -0
  167. data/src/stylesheets/nitro_kit/components/select.css +70 -0
  168. data/src/stylesheets/nitro_kit/components/settings_layout.css +100 -0
  169. data/src/stylesheets/nitro_kit/components/sheet.css +100 -0
  170. data/src/stylesheets/nitro_kit/components/stat_grid.css +51 -0
  171. data/src/stylesheets/nitro_kit/components/switch.css +152 -0
  172. data/src/stylesheets/nitro_kit/components/table.css +116 -0
  173. data/src/stylesheets/nitro_kit/components/tabs.css +138 -0
  174. data/src/stylesheets/nitro_kit/components/textarea.css +39 -0
  175. data/src/stylesheets/nitro_kit/components/toast.css +131 -0
  176. data/src/stylesheets/nitro_kit/components/toolbar.css +78 -0
  177. data/src/stylesheets/nitro_kit/components/tooltip.css +124 -0
  178. data/src/stylesheets/nitro_kit/components/typeset.css +191 -0
  179. data/src/stylesheets/nitro_kit/layers.css +7 -0
  180. data/src/stylesheets/nitro_kit/reset.css +192 -0
  181. data/src/stylesheets/nitro_kit/tokens.css +198 -0
  182. metadata +182 -51
  183. data/Rakefile +0 -8
  184. data/app/assets/tailwind/application.css +0 -49
  185. data/app/components/nitro_kit/datepicker.rb +0 -9
  186. data/app/helpers/nitro_kit/accordion_helper.rb +0 -9
  187. data/app/helpers/nitro_kit/alert_helper.rb +0 -11
  188. data/app/helpers/nitro_kit/avatar_helper.rb +0 -13
  189. data/app/helpers/nitro_kit/badge_helper.rb +0 -13
  190. data/app/helpers/nitro_kit/button_group_helper.rb +0 -9
  191. data/app/helpers/nitro_kit/button_helper.rb +0 -49
  192. data/app/helpers/nitro_kit/card_helper.rb +0 -9
  193. data/app/helpers/nitro_kit/checkbox_helper.rb +0 -50
  194. data/app/helpers/nitro_kit/combobox_helper.rb +0 -9
  195. data/app/helpers/nitro_kit/datepicker_helper.rb +0 -9
  196. data/app/helpers/nitro_kit/dialog_helper.rb +0 -9
  197. data/app/helpers/nitro_kit/dropdown_helper.rb +0 -9
  198. data/app/helpers/nitro_kit/field_group_helper.rb +0 -9
  199. data/app/helpers/nitro_kit/field_helper.rb +0 -9
  200. data/app/helpers/nitro_kit/fieldset_helper.rb +0 -9
  201. data/app/helpers/nitro_kit/form_helper.rb +0 -13
  202. data/app/helpers/nitro_kit/icon_helper.rb +0 -9
  203. data/app/helpers/nitro_kit/input_helper.rb +0 -37
  204. data/app/helpers/nitro_kit/label_helper.rb +0 -19
  205. data/app/helpers/nitro_kit/pagination_helper.rb +0 -46
  206. data/app/helpers/nitro_kit/radio_button_helper.rb +0 -23
  207. data/app/helpers/nitro_kit/select_helper.rb +0 -24
  208. data/app/helpers/nitro_kit/switch_helper.rb +0 -9
  209. data/app/helpers/nitro_kit/table_helper.rb +0 -9
  210. data/app/helpers/nitro_kit/tabs_helper.rb +0 -9
  211. data/app/helpers/nitro_kit/textarea_helper.rb +0 -9
  212. data/app/helpers/nitro_kit/toast_helper.rb +0 -25
  213. data/app/helpers/nitro_kit/tooltip_helper.rb +0 -9
  214. data/app/javascript/controllers/nk/accordion_controller.js +0 -18
  215. data/app/javascript/controllers/nk/datepicker_controller.js +0 -5
  216. data/app/javascript/controllers/nk/switch_controller.js +0 -32
  217. data/lib/generators/nitro_kit/component_generator.rb +0 -84
  218. data/lib/nitro_kit/schema_builder.rb +0 -129
  219. data/lib/nitro_kit/variants.rb +0 -21
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class Grid < Component
5
+ COLUMNS = (1..12).to_a.freeze
6
+ GAPS = LayoutOptions::GAPS
7
+
8
+ def initialize(
9
+ cols:,
10
+ gap: 4,
11
+ id: nil,
12
+ html: {},
13
+ aria: {},
14
+ data: {},
15
+ desperately_need_a_class: nil
16
+ )
17
+ cols = ResponsiveValue.new(property: :cols, value: cols, allowed: COLUMNS).to_s
18
+ gap = ResponsiveValue.new(property: :gap, value: gap, allowed: GAPS).to_s
19
+
20
+ super(
21
+ component: :grid,
22
+ attributes: { id:, data: { cols:, gap: } }.compact,
23
+ html:,
24
+ aria:,
25
+ data:,
26
+ desperately_need_a_class:
27
+ )
28
+ end
29
+
30
+ def view_template
31
+ div(**root_attributes) { yield if block_given? }
32
+ end
33
+ end
34
+ end
@@ -1,45 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "lucide-rails"
4
+
3
5
  module NitroKit
4
6
  class Icon < Component
5
- register_output_helper :lucide_icon
7
+ SIZES = %i[xs sm md lg xl].freeze
8
+ STROKE_WIDTHS = (0.5..4).freeze
9
+
10
+ def initialize(
11
+ name,
12
+ size: :md,
13
+ label: nil,
14
+ stroke_width: 1.5,
15
+ id: nil,
16
+ html: {},
17
+ aria: {},
18
+ data: {},
19
+ desperately_need_a_class: nil
20
+ )
21
+ @name = name.to_s.tr("_", "-")
22
+ @size = validate_choice!(:size, size, SIZES)
23
+ @label = label
24
+ if !label.nil? && (!label.is_a?(String) || label.strip.empty?)
25
+ raise ArgumentError, "Icon label: must be a non-blank String"
26
+ end
27
+ @stroke_width = validate_stroke_width!(stroke_width)
28
+ @icon = LucideRails::IconProvider.icon(@name)
6
29
 
7
- def initialize(name, size: :md, **attrs)
8
- @name = name
9
- @size = size
30
+ raise ArgumentError, "Unknown icon #{@name.inspect}" unless @icon
31
+
32
+ icon_aria = label ? { label:, hidden: false } : { hidden: true }
10
33
 
11
34
  super(
12
- attrs,
13
- class: size_class,
14
- stroke_width: 1.5
35
+ component: :icon,
36
+ attributes: {
37
+ id:,
38
+ width: 24,
39
+ height: 24,
40
+ viewBox: "0 0 24 24",
41
+ fill: "none",
42
+ stroke: "currentColor",
43
+ stroke_width: @stroke_width,
44
+ stroke_linecap: "round",
45
+ stroke_linejoin: "round",
46
+ focusable: false,
47
+ role: label && "img",
48
+ aria: icon_aria
49
+ }.compact,
50
+ html:,
51
+ aria:,
52
+ data:,
53
+ size:,
54
+ desperately_need_a_class:
15
55
  )
16
56
  end
17
57
 
18
- attr_reader :name, :size
58
+ attr_reader :name, :size, :label, :stroke_width
19
59
 
20
60
  def view_template
21
- lucide_icon(name, **dasherized_attrs)
61
+ svg(**root_attributes) { raw safe(@icon) }
22
62
  end
23
63
 
24
64
  private
25
65
 
26
- def size_class
27
- case size
28
- when :xs
29
- "size-3"
30
- when :sm
31
- "size-4"
32
- when :md
33
- "size-5"
34
- when :lg
35
- "size-7"
36
- else
37
- raise ArgumentError, "Unknown size `#{size}'"
38
- end
39
- end
66
+ def validate_stroke_width!(value)
67
+ return value if value.is_a?(Numeric) && !value.is_a?(Complex) && STROKE_WIDTHS.cover?(value)
40
68
 
41
- def dasherized_attrs
42
- attrs.transform_keys { |k| k.to_s.dasherize }
69
+ raise ArgumentError,
70
+ "Icon stroke_width: must be a number between #{STROKE_WIDTHS.begin} and #{STROKE_WIDTHS.end}; " \
71
+ "received #{value.inspect}"
43
72
  end
44
73
  end
45
74
  end
@@ -2,25 +2,112 @@
2
2
 
3
3
  module NitroKit
4
4
  class Input < Component
5
- def initialize(**attrs)
5
+ TYPES = %i[
6
+ button checkbox color date datetime-local email file hidden month number password radio
7
+ range search tel text time url week
8
+ ].freeze
9
+ OWNED_ATTRIBUTES = %i[
10
+ type id name value placeholder disabled readonly required autocomplete min max step
11
+ minlength maxlength multiple accept pattern inputmode checked
12
+ ].freeze
13
+
14
+ def initialize(
15
+ type: :text,
16
+ id: nil,
17
+ name: nil,
18
+ value: nil,
19
+ placeholder: nil,
20
+ disabled: false,
21
+ readonly: false,
22
+ required: false,
23
+ autocomplete: nil,
24
+ min: nil,
25
+ max: nil,
26
+ step: nil,
27
+ minlength: nil,
28
+ maxlength: nil,
29
+ multiple: false,
30
+ accept: nil,
31
+ pattern: nil,
32
+ inputmode: nil,
33
+ checked: nil,
34
+ html: {},
35
+ aria: {},
36
+ data: {},
37
+ desperately_need_a_class: nil
38
+ )
39
+ normalized_type = type.to_s.tr("_", "-").to_sym
40
+ @type = validate_choice!(:type, normalized_type, TYPES)
41
+ disabled = validate_boolean!(:disabled, disabled)
42
+ readonly = validate_boolean!(:readonly, readonly)
43
+ required = validate_boolean!(:required, required)
44
+ multiple = validate_boolean!(:multiple, multiple)
45
+ checked = validate_boolean!(:checked, checked, allow_nil: true)
46
+ minlength = validate_non_negative_integer!(:minlength, minlength)
47
+ maxlength = validate_non_negative_integer!(:maxlength, maxlength)
48
+ if minlength && maxlength && minlength > maxlength
49
+ raise ArgumentError, "minlength cannot exceed maxlength"
50
+ end
51
+ if @type == :file && !value.nil?
52
+ raise ArgumentError, "file inputs never carry a value; omit value:"
53
+ end
54
+ reject_owned_attributes!(html)
55
+
6
56
  super(
7
- attrs,
8
- class: base_class
57
+ component: :input,
58
+ attributes: {
59
+ type: @type,
60
+ id:,
61
+ name:,
62
+ value:,
63
+ placeholder:,
64
+ disabled:,
65
+ readonly:,
66
+ required:,
67
+ autocomplete:,
68
+ min:,
69
+ max:,
70
+ step:,
71
+ minlength:,
72
+ maxlength:,
73
+ multiple:,
74
+ accept:,
75
+ pattern:,
76
+ inputmode:,
77
+ checked:
78
+ }.compact,
79
+ html:,
80
+ aria:,
81
+ data:,
82
+ desperately_need_a_class:
9
83
  )
10
84
  end
11
85
 
86
+ attr_reader :type
87
+
12
88
  def view_template
13
- input(**attrs)
89
+ input(**root_attributes)
14
90
  end
15
91
 
16
92
  private
17
93
 
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-visible:outline-none ring-ring ring-offset-2 ring-offset-background focus-visible:ring-2"
23
- ]
94
+ def reject_owned_attributes!(html)
95
+ return unless html.is_a?(Hash)
96
+
97
+ html.each_key do |key|
98
+ normalized = key.to_s.downcase.tr("_", "-")
99
+ owned = OWNED_ATTRIBUTES.find { |name| name.to_s.tr("_", "-") == normalized }
100
+ next unless owned
101
+
102
+ raise ArgumentError, "#{normalized} is owned by Input; pass #{owned}: as a keyword"
103
+ end
104
+ end
105
+
106
+ def validate_non_negative_integer!(name, value)
107
+ return if value.nil?
108
+ return value if value.is_a?(Integer) && value >= 0
109
+
110
+ raise ArgumentError, "#{name} must be a non-negative Integer"
24
111
  end
25
112
  end
26
113
  end
@@ -2,22 +2,38 @@
2
2
 
3
3
  module NitroKit
4
4
  class Label < Component
5
- def initialize(text = nil, **attrs)
5
+ def initialize(
6
+ text = nil,
7
+ for: nil,
8
+ id: nil,
9
+ html: {},
10
+ aria: {},
11
+ data: {},
12
+ desperately_need_a_class: nil
13
+ )
14
+ if !text.nil? && (!text.is_a?(String) || text.strip.empty?)
15
+ raise ArgumentError, "label text must be a non-blank String"
16
+ end
6
17
  @text = text
7
18
 
8
19
  super(
9
- attrs,
10
- class: "text-sm font-medium select-none",
11
- data: { slot: "label" }
20
+ component: :label,
21
+ attributes: { for:, id: }.compact,
22
+ html:,
23
+ aria:,
24
+ data:,
25
+ desperately_need_a_class:
12
26
  )
13
27
  end
14
28
 
15
29
  attr_reader :text
16
30
 
17
31
  def view_template(&block)
18
- label(**attrs) do
19
- text_or_block(text, &block)
32
+ if text.nil? && !block
33
+ raise ArgumentError, "label requires text or a block"
20
34
  end
35
+
36
+ label(**root_attributes) { text_or_block(text, &block) }
21
37
  end
22
38
  end
23
39
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ module LayoutOptions
5
+ GAPS = [ 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16 ].freeze
6
+ end
7
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class PageHeader < Component
5
+ Child = Data.define(:component, :content)
6
+ TITLE_LEVELS = (1..6).freeze
7
+
8
+ def initialize(
9
+ title: nil,
10
+ eyebrow: nil,
11
+ description: nil,
12
+ level: 1,
13
+ id: nil,
14
+ html: {},
15
+ aria: {},
16
+ data: {},
17
+ desperately_need_a_class: nil
18
+ )
19
+ @title_content = content_from_keyword(:title, title)
20
+ @eyebrow_content = content_from_keyword(:eyebrow, eyebrow)
21
+ @description_content = content_from_keyword(:description, description)
22
+ @level = validate_choice!(:level, level, TITLE_LEVELS)
23
+ @actions = nil
24
+
25
+ super(
26
+ component: :page_header,
27
+ attributes: { id: }.compact,
28
+ html:,
29
+ aria:,
30
+ data:,
31
+ desperately_need_a_class:
32
+ )
33
+ end
34
+
35
+ attr_reader :level
36
+
37
+ def view_template
38
+ yield self if block_given?
39
+ require_content!("PageHeader", :title, @title_content)
40
+
41
+ header(**root_attributes) do
42
+ p(**slot_attributes(:eyebrow)) { render_deferred_content(@eyebrow_content) } if @eyebrow_content
43
+ public_send(:"h#{level}", **slot_attributes(:title)) { render_deferred_content(@title_content) }
44
+ if @description_content
45
+ p(**slot_attributes(:description)) { render_deferred_content(@description_content) }
46
+ end
47
+ render_in_slot(@actions.component, :actions, &@actions.content) if @actions
48
+ end
49
+ end
50
+
51
+ def eyebrow(text = nil, &block)
52
+ @eyebrow_content = declare_content(:eyebrow, @eyebrow_content, text, &block)
53
+ nil
54
+ end
55
+
56
+ def title(text = nil, &block)
57
+ @title_content = declare_content(:title, @title_content, text, &block)
58
+ nil
59
+ end
60
+
61
+ def description(text = nil, &block)
62
+ @description_content = declare_content(:description, @description_content, text, &block)
63
+ nil
64
+ end
65
+
66
+ def actions(component, &content)
67
+ unless component.is_a?(NitroKit::ButtonGroup)
68
+ raise ArgumentError, "PageHeader actions must be a NitroKit::ButtonGroup"
69
+ end
70
+ raise ArgumentError, "PageHeader accepts at most one actions group" if @actions
71
+
72
+ @actions = Child.new(component:, content:)
73
+ nil
74
+ end
75
+ end
76
+ end