kiso 0.1.0.pre → 0.2.0.pre

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 (236) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -2
  3. data/README.md +67 -27
  4. data/Rakefile +8 -0
  5. data/app/assets/tailwind/kiso/checkbox.css +18 -0
  6. data/app/assets/tailwind/kiso/color-mode.css +9 -0
  7. data/app/assets/tailwind/kiso/dashboard.css +194 -0
  8. data/app/assets/tailwind/kiso/engine.css +117 -0
  9. data/app/assets/tailwind/kiso/input-otp.css +10 -0
  10. data/app/assets/tailwind/kiso/radio-group.css +17 -0
  11. data/app/helpers/kiso/component_helper.rb +46 -27
  12. data/app/helpers/kiso/icon_helper.rb +53 -9
  13. data/app/helpers/kiso/theme_helper.rb +38 -0
  14. data/app/javascript/controllers/kiso/combobox_controller.js +616 -0
  15. data/app/javascript/controllers/kiso/command_controller.js +184 -0
  16. data/app/javascript/controllers/kiso/command_dialog_controller.js +104 -0
  17. data/app/javascript/controllers/kiso/dropdown_menu_controller.js +684 -0
  18. data/app/javascript/controllers/kiso/index.d.ts +12 -0
  19. data/app/javascript/controllers/kiso/index.js +42 -0
  20. data/app/javascript/controllers/kiso/input_otp_controller.js +195 -0
  21. data/app/javascript/controllers/kiso/popover_controller.js +254 -0
  22. data/app/javascript/controllers/kiso/select_controller.js +307 -0
  23. data/app/javascript/controllers/kiso/sidebar_controller.js +84 -0
  24. data/app/javascript/controllers/kiso/theme_controller.js +89 -0
  25. data/app/javascript/controllers/kiso/toggle_controller.js +24 -0
  26. data/app/javascript/controllers/kiso/toggle_group_controller.js +128 -0
  27. data/app/javascript/kiso/utils/focusable.js +8 -0
  28. data/app/javascript/kiso/utils/highlight.js +43 -0
  29. data/app/javascript/kiso/utils/positioning.js +86 -0
  30. data/app/javascript/kiso/vendor/floating-ui-core.js +1 -0
  31. data/app/javascript/kiso/vendor/floating-ui-dom.js +1 -0
  32. data/app/views/kiso/components/_alert.html.erb +1 -1
  33. data/app/views/kiso/components/_avatar.html.erb +23 -0
  34. data/app/views/kiso/components/_badge.html.erb +1 -1
  35. data/app/views/kiso/components/_breadcrumb.html.erb +8 -0
  36. data/app/views/kiso/components/_button.html.erb +1 -1
  37. data/app/views/kiso/components/_card.html.erb +1 -1
  38. data/app/views/kiso/components/_checkbox.html.erb +7 -0
  39. data/app/views/kiso/components/_color_mode_button.html.erb +14 -0
  40. data/app/views/kiso/components/_color_mode_select.html.erb +24 -0
  41. data/app/views/kiso/components/_combobox.html.erb +12 -0
  42. data/app/views/kiso/components/_command.html.erb +7 -0
  43. data/app/views/kiso/components/_dashboard_group.html.erb +14 -0
  44. data/app/views/kiso/components/_dashboard_navbar.html.erb +7 -0
  45. data/app/views/kiso/components/_dashboard_panel.html.erb +7 -0
  46. data/app/views/kiso/components/_dashboard_sidebar.html.erb +11 -0
  47. data/app/views/kiso/components/_dashboard_toolbar.html.erb +7 -0
  48. data/app/views/kiso/components/_dropdown_menu.html.erb +7 -0
  49. data/app/views/kiso/components/{_empty_state.html.erb → _empty.html.erb} +2 -2
  50. data/app/views/kiso/components/_field.html.erb +12 -0
  51. data/app/views/kiso/components/_field_group.html.erb +7 -0
  52. data/app/views/kiso/components/_field_set.html.erb +7 -0
  53. data/app/views/kiso/components/_input.html.erb +8 -0
  54. data/app/views/kiso/components/_input_group.html.erb +8 -0
  55. data/app/views/kiso/components/_input_otp.html.erb +22 -0
  56. data/app/views/kiso/components/_kbd.html.erb +7 -0
  57. data/app/views/kiso/components/_label.html.erb +5 -0
  58. data/app/views/kiso/components/_nav.html.erb +7 -0
  59. data/app/views/kiso/components/_pagination.html.erb +9 -0
  60. data/app/views/kiso/components/_popover.html.erb +8 -0
  61. data/app/views/kiso/components/_radio_group.html.erb +8 -0
  62. data/app/views/kiso/components/_select.html.erb +8 -0
  63. data/app/views/kiso/components/_select_native.html.erb +16 -0
  64. data/app/views/kiso/components/_separator.html.erb +1 -1
  65. data/app/views/kiso/components/_stats_card.html.erb +1 -1
  66. data/app/views/kiso/components/_stats_grid.html.erb +1 -1
  67. data/app/views/kiso/components/_switch.html.erb +10 -0
  68. data/app/views/kiso/components/_table.html.erb +2 -1
  69. data/app/views/kiso/components/_textarea.html.erb +9 -0
  70. data/app/views/kiso/components/_toggle.html.erb +12 -0
  71. data/app/views/kiso/components/_toggle_group.html.erb +12 -0
  72. data/app/views/kiso/components/alert/_description.html.erb +1 -1
  73. data/app/views/kiso/components/alert/_title.html.erb +1 -1
  74. data/app/views/kiso/components/avatar/_badge.html.erb +7 -0
  75. data/app/views/kiso/components/avatar/_fallback.html.erb +7 -0
  76. data/app/views/kiso/components/avatar/_group.html.erb +7 -0
  77. data/app/views/kiso/components/avatar/_group_count.html.erb +7 -0
  78. data/app/views/kiso/components/avatar/_image.html.erb +6 -0
  79. data/app/views/kiso/components/breadcrumb/_ellipsis.html.erb +10 -0
  80. data/app/views/kiso/components/breadcrumb/_item.html.erb +7 -0
  81. data/app/views/kiso/components/breadcrumb/_link.html.erb +7 -0
  82. data/app/views/kiso/components/breadcrumb/_list.html.erb +7 -0
  83. data/app/views/kiso/components/breadcrumb/_page.html.erb +9 -0
  84. data/app/views/kiso/components/breadcrumb/_separator.html.erb +9 -0
  85. data/app/views/kiso/components/card/_action.html.erb +7 -0
  86. data/app/views/kiso/components/card/_content.html.erb +1 -1
  87. data/app/views/kiso/components/card/_description.html.erb +1 -1
  88. data/app/views/kiso/components/card/_footer.html.erb +1 -1
  89. data/app/views/kiso/components/card/_header.html.erb +1 -1
  90. data/app/views/kiso/components/card/_title.html.erb +1 -1
  91. data/app/views/kiso/components/combobox/_chip.html.erb +19 -0
  92. data/app/views/kiso/components/combobox/_chips.html.erb +20 -0
  93. data/app/views/kiso/components/combobox/_chips_input.html.erb +10 -0
  94. data/app/views/kiso/components/combobox/_content.html.erb +9 -0
  95. data/app/views/kiso/components/combobox/_empty.html.erb +9 -0
  96. data/app/views/kiso/components/combobox/_group.html.erb +8 -0
  97. data/app/views/kiso/components/combobox/_input.html.erb +23 -0
  98. data/app/views/kiso/components/combobox/_item.html.erb +19 -0
  99. data/app/views/kiso/components/combobox/_label.html.erb +7 -0
  100. data/app/views/kiso/components/combobox/_list.html.erb +10 -0
  101. data/app/views/kiso/components/combobox/_separator.html.erb +6 -0
  102. data/app/views/kiso/components/command/_dialog.html.erb +11 -0
  103. data/app/views/kiso/components/command/_empty.html.erb +9 -0
  104. data/app/views/kiso/components/command/_group.html.erb +14 -0
  105. data/app/views/kiso/components/command/_input.html.erb +16 -0
  106. data/app/views/kiso/components/command/_item.html.erb +13 -0
  107. data/app/views/kiso/components/command/_list.html.erb +10 -0
  108. data/app/views/kiso/components/command/_separator.html.erb +7 -0
  109. data/app/views/kiso/components/command/_shortcut.html.erb +7 -0
  110. data/app/views/kiso/components/dashboard_navbar/_toggle.html.erb +11 -0
  111. data/app/views/kiso/components/dashboard_sidebar/_collapse.html.erb +12 -0
  112. data/app/views/kiso/components/dashboard_sidebar/_footer.html.erb +7 -0
  113. data/app/views/kiso/components/dashboard_sidebar/_header.html.erb +7 -0
  114. data/app/views/kiso/components/dashboard_sidebar/_toggle.html.erb +11 -0
  115. data/app/views/kiso/components/dashboard_toolbar/_left.html.erb +7 -0
  116. data/app/views/kiso/components/dashboard_toolbar/_right.html.erb +7 -0
  117. data/app/views/kiso/components/dropdown_menu/_checkbox_item.html.erb +18 -0
  118. data/app/views/kiso/components/dropdown_menu/_content.html.erb +10 -0
  119. data/app/views/kiso/components/dropdown_menu/_group.html.erb +8 -0
  120. data/app/views/kiso/components/dropdown_menu/_item.html.erb +15 -0
  121. data/app/views/kiso/components/dropdown_menu/_label.html.erb +8 -0
  122. data/app/views/kiso/components/dropdown_menu/_radio_group.html.erb +10 -0
  123. data/app/views/kiso/components/dropdown_menu/_radio_item.html.erb +19 -0
  124. data/app/views/kiso/components/dropdown_menu/_separator.html.erb +6 -0
  125. data/app/views/kiso/components/dropdown_menu/_shortcut.html.erb +7 -0
  126. data/app/views/kiso/components/dropdown_menu/_sub.html.erb +8 -0
  127. data/app/views/kiso/components/dropdown_menu/_sub_content.html.erb +10 -0
  128. data/app/views/kiso/components/dropdown_menu/_sub_trigger.html.erb +12 -0
  129. data/app/views/kiso/components/dropdown_menu/_trigger.html.erb +9 -0
  130. data/app/views/kiso/components/empty/_content.html.erb +7 -0
  131. data/app/views/kiso/components/empty/_description.html.erb +7 -0
  132. data/app/views/kiso/components/empty/_header.html.erb +7 -0
  133. data/app/views/kiso/components/empty/_media.html.erb +7 -0
  134. data/app/views/kiso/components/empty/_title.html.erb +7 -0
  135. data/app/views/kiso/components/field/_content.html.erb +7 -0
  136. data/app/views/kiso/components/field/_description.html.erb +7 -0
  137. data/app/views/kiso/components/field/_error.html.erb +22 -0
  138. data/app/views/kiso/components/field/_label.html.erb +5 -0
  139. data/app/views/kiso/components/field/_separator.html.erb +15 -0
  140. data/app/views/kiso/components/field/_title.html.erb +7 -0
  141. data/app/views/kiso/components/field_set/_legend.html.erb +9 -0
  142. data/app/views/kiso/components/input_group/_addon.html.erb +7 -0
  143. data/app/views/kiso/components/input_otp/_group.html.erb +7 -0
  144. data/app/views/kiso/components/input_otp/_separator.html.erb +8 -0
  145. data/app/views/kiso/components/input_otp/_slot.html.erb +11 -0
  146. data/app/views/kiso/components/kbd/_group.html.erb +7 -0
  147. data/app/views/kiso/components/nav/_item.html.erb +15 -0
  148. data/app/views/kiso/components/nav/_section.html.erb +37 -0
  149. data/app/views/kiso/components/nav/_section_title.html.erb +7 -0
  150. data/app/views/kiso/components/pagination/_content.html.erb +7 -0
  151. data/app/views/kiso/components/pagination/_ellipsis.html.erb +9 -0
  152. data/app/views/kiso/components/pagination/_item.html.erb +7 -0
  153. data/app/views/kiso/components/pagination/_link.html.erb +9 -0
  154. data/app/views/kiso/components/pagination/_next.html.erb +12 -0
  155. data/app/views/kiso/components/pagination/_previous.html.erb +12 -0
  156. data/app/views/kiso/components/popover/_anchor.html.erb +8 -0
  157. data/app/views/kiso/components/popover/_content.html.erb +11 -0
  158. data/app/views/kiso/components/popover/_description.html.erb +7 -0
  159. data/app/views/kiso/components/popover/_header.html.erb +7 -0
  160. data/app/views/kiso/components/popover/_title.html.erb +7 -0
  161. data/app/views/kiso/components/popover/_trigger.html.erb +9 -0
  162. data/app/views/kiso/components/radio_group/_item.html.erb +6 -0
  163. data/app/views/kiso/components/select/_content.html.erb +10 -0
  164. data/app/views/kiso/components/select/_group.html.erb +8 -0
  165. data/app/views/kiso/components/select/_item.html.erb +19 -0
  166. data/app/views/kiso/components/select/_label.html.erb +7 -0
  167. data/app/views/kiso/components/select/_separator.html.erb +6 -0
  168. data/app/views/kiso/components/select/_trigger.html.erb +13 -0
  169. data/app/views/kiso/components/select/_value.html.erb +11 -0
  170. data/app/views/kiso/components/stats_card/_description.html.erb +1 -1
  171. data/app/views/kiso/components/stats_card/_header.html.erb +1 -1
  172. data/app/views/kiso/components/stats_card/_label.html.erb +1 -1
  173. data/app/views/kiso/components/stats_card/_value.html.erb +1 -1
  174. data/app/views/kiso/components/table/_body.html.erb +1 -1
  175. data/app/views/kiso/components/table/_caption.html.erb +1 -1
  176. data/app/views/kiso/components/table/_cell.html.erb +1 -1
  177. data/app/views/kiso/components/table/_footer.html.erb +1 -1
  178. data/app/views/kiso/components/table/_head.html.erb +1 -1
  179. data/app/views/kiso/components/table/_header.html.erb +1 -1
  180. data/app/views/kiso/components/table/_row.html.erb +1 -1
  181. data/app/views/kiso/components/toggle_group/_item.html.erb +13 -0
  182. data/config/deploy.docs.yml +31 -0
  183. data/config/deploy.yml +34 -0
  184. data/config/importmap.rb +10 -0
  185. data/lib/kiso/cli/base.rb +15 -0
  186. data/lib/kiso/cli/icons.rb +2 -1
  187. data/lib/kiso/cli/main.rb +6 -0
  188. data/lib/kiso/cli/make.rb +22 -12
  189. data/lib/kiso/configuration.rb +54 -0
  190. data/lib/kiso/engine.rb +36 -1
  191. data/lib/kiso/theme_overrides.rb +130 -0
  192. data/lib/kiso/themes/alert.rb +16 -1
  193. data/lib/kiso/themes/avatar.rb +53 -0
  194. data/lib/kiso/themes/badge.rb +15 -5
  195. data/lib/kiso/themes/breadcrumb.rb +44 -0
  196. data/lib/kiso/themes/button.rb +15 -2
  197. data/lib/kiso/themes/card.rb +18 -2
  198. data/lib/kiso/themes/checkbox.rb +33 -0
  199. data/lib/kiso/themes/color_mode_button.rb +15 -0
  200. data/lib/kiso/themes/color_mode_select.rb +7 -0
  201. data/lib/kiso/themes/combobox.rb +97 -0
  202. data/lib/kiso/themes/command.rb +79 -0
  203. data/lib/kiso/themes/dashboard.rb +51 -0
  204. data/lib/kiso/themes/dropdown_menu.rb +108 -0
  205. data/lib/kiso/themes/empty.rb +54 -0
  206. data/lib/kiso/themes/field.rb +76 -0
  207. data/lib/kiso/themes/field_group.rb +15 -0
  208. data/lib/kiso/themes/field_set.rb +32 -0
  209. data/lib/kiso/themes/input.rb +33 -0
  210. data/lib/kiso/themes/input_group.rb +39 -0
  211. data/lib/kiso/themes/input_otp.rb +46 -0
  212. data/lib/kiso/themes/kbd.rb +31 -0
  213. data/lib/kiso/themes/label.rb +16 -0
  214. data/lib/kiso/themes/nav.rb +27 -0
  215. data/lib/kiso/themes/pagination.rb +73 -0
  216. data/lib/kiso/themes/popover.rb +32 -0
  217. data/lib/kiso/themes/radio_group.rb +43 -0
  218. data/lib/kiso/themes/select.rb +78 -0
  219. data/lib/kiso/themes/select_native.rb +49 -0
  220. data/lib/kiso/themes/separator.rb +8 -2
  221. data/lib/kiso/themes/shared.rb +51 -0
  222. data/lib/kiso/themes/stats_card.rb +26 -14
  223. data/lib/kiso/themes/switch.rb +56 -0
  224. data/lib/kiso/themes/table.rb +18 -15
  225. data/lib/kiso/themes/textarea.rb +33 -0
  226. data/lib/kiso/themes/toggle.rb +71 -0
  227. data/lib/kiso/themes/toggle_group.rb +13 -0
  228. data/lib/kiso/version.rb +4 -1
  229. data/lib/kiso.rb +70 -2
  230. metadata +183 -22
  231. data/app/views/kiso/components/empty_state/_content.html.erb +0 -7
  232. data/app/views/kiso/components/empty_state/_description.html.erb +0 -7
  233. data/app/views/kiso/components/empty_state/_header.html.erb +0 -7
  234. data/app/views/kiso/components/empty_state/_media.html.erb +0 -7
  235. data/app/views/kiso/components/empty_state/_title.html.erb +0 -7
  236. data/lib/kiso/themes/empty_state.rb +0 -42
@@ -1,36 +1,29 @@
1
1
  module Kiso
2
+ # View helpers for rendering Kiso components.
3
+ #
4
+ # Included in all views automatically by {Engine}.
2
5
  module ComponentHelper
3
- # Renders a component element with data-attribute API.
6
+ # Renders a Kiso component partial.
4
7
  #
5
- # component_tag(:span, :badge, variant: :primary, size: :md) { "Active" }
6
- # # => <span data-component="badge" data-variant="primary" data-size="md">Active</span>
8
+ # Components live in +app/views/kiso/components/+. Sub-parts are nested
9
+ # in a directory matching the parent component name.
7
10
  #
8
- # component_tag(:div, :card, part: :header) { ... }
9
- # # => <div data-card-part="header">...</div>
11
+ # @param component [Symbol] the component name (e.g. +:badge+, +:card+)
12
+ # @param part [Symbol, nil] optional sub-part name (e.g. +:header+, +:footer+)
13
+ # @param collection [Array, nil] renders the partial once per item when present
14
+ # @param kwargs [Hash] locals passed to the partial (e.g. +color:+, +variant:+, +css_classes:+)
15
+ # @yield optional block for component content
16
+ # @return [ActiveSupport::SafeBuffer] rendered HTML
10
17
  #
11
- def component_tag(element, component, variant: nil, size: nil, part: nil, **options, &block)
12
- data = options.delete(:data) || {}
13
-
14
- if part
15
- data[:"#{component}-part"] = part
16
- else
17
- data[:component] = component
18
- data[:variant] = variant if variant
19
- data[:size] = size if size
20
- end
21
-
22
- options[:data] = data
23
-
24
- content_tag(element, **options, &block)
25
- end
26
-
27
- # Renders a Kiso component partial.
18
+ # @example Render a badge
19
+ # kui(:badge, color: :success, variant: :soft) { "Active" }
28
20
  #
29
- # kiso(:badge, variant: :success) { "Active" }
30
- # kiso(:card, :header) { ... }
31
- # kiso(:badge, collection: @statuses)
21
+ # @example Render a card sub-part
22
+ # kui(:card, :header) { "Title" }
32
23
  #
33
- def kiso(component, part = nil, collection: nil, **kwargs, &block)
24
+ # @example Render a collection
25
+ # kui(:badge, collection: @tags)
26
+ def kui(component, part = nil, collection: nil, **kwargs, &block)
34
27
  path = if part
35
28
  "kiso/components/#{component}/#{part}"
36
29
  else
@@ -38,10 +31,36 @@ module Kiso
38
31
  end
39
32
 
40
33
  if collection
41
- render partial: path, collection: collection, **kwargs, &block
34
+ render partial: path, collection: collection, locals: kwargs, &block
42
35
  else
43
36
  render path, **kwargs, &block
44
37
  end
45
38
  end
39
+
40
+ # Prepares +component_options+ for use with +content_tag+.
41
+ #
42
+ # Sets +data-slot+ for component identity (shadcn v4 convention) and
43
+ # merges any additional data attributes. Raises if +class:+ is passed
44
+ # (use +css_classes:+ instead).
45
+ #
46
+ # @param component_options [Hash] the +**component_options+ splat from the partial.
47
+ # Any +data:+ key is extracted and merged with +slot+ and +data_attrs+.
48
+ # @param slot [String] the +data-slot+ value (kebab-case, e.g. +"card-header"+)
49
+ # @param data_attrs [Hash] additional data attributes (e.g. +controller: "kiso--toggle"+)
50
+ # @return [Hash] merged data attributes hash for +content_tag+
51
+ # @raise [ArgumentError] if +component_options+ contains a +class:+ key
52
+ #
53
+ # @example In a component partial
54
+ # data: kiso_prepare_options(component_options, slot: "badge")
55
+ #
56
+ # @example With a Stimulus controller
57
+ # data: kiso_prepare_options(component_options, slot: "toggle", controller: "kiso--toggle")
58
+ def kiso_prepare_options(component_options, slot:, **data_attrs)
59
+ if component_options.key?(:class)
60
+ raise ArgumentError, "Use css_classes: instead of class: for Kiso components"
61
+ end
62
+
63
+ (component_options.delete(:data) || {}).merge(slot: slot, **data_attrs)
64
+ end
46
65
  end
47
66
  end
@@ -3,7 +3,13 @@
3
3
  require "tailwind_merge"
4
4
 
5
5
  module Kiso
6
+ # View helpers for rendering inline SVG icons.
7
+ #
8
+ # Included in all views automatically by {Engine}.
9
+ # Delegates to +kiso_icon_tag+ (provided by the kiso-icons gem) for
10
+ # actual SVG rendering.
6
11
  module IconHelper
12
+ # @return [Hash{Symbol => String}] maps size presets to Tailwind size utilities
7
13
  SIZE_PRESETS = {
8
14
  xs: "size-3",
9
15
  sm: "size-4",
@@ -12,23 +18,56 @@ module Kiso
12
18
  xl: "size-8"
13
19
  }.freeze
14
20
 
21
+ # @return [String] base Tailwind classes applied to every icon
15
22
  BASE_CLASSES = "shrink-0"
16
23
 
24
+ # Renders a configurable component icon.
25
+ #
26
+ # Components call this instead of {#kiso_icon} when the icon name should
27
+ # be overridable by host apps via {Configuration#icons Kiso.config.icons}.
28
+ #
29
+ # @param semantic_name [Symbol] a key from {Configuration#icons}
30
+ # (e.g. +:chevron_right+, +:check+, +:x+)
31
+ # @param options [Hash] forwarded to {#kiso_icon}
32
+ # @return [ActiveSupport::SafeBuffer] rendered SVG
33
+ # @raise [KeyError] if +semantic_name+ is not registered
34
+ #
35
+ # @example
36
+ # kiso_component_icon(:chevron_right, class: "size-3.5")
37
+ # kiso_component_icon(:ellipsis, size: :sm)
38
+ def kiso_component_icon(semantic_name, **)
39
+ icon_name = Kiso.config.icons.fetch(semantic_name) {
40
+ raise KeyError, "Unknown Kiso icon: #{semantic_name.inspect}. " \
41
+ "Known icons: #{Kiso.config.icons.keys.join(", ")}"
42
+ }
43
+ kiso_icon(icon_name, **)
44
+ end
45
+
17
46
  # Renders an inline SVG icon with Tailwind classes.
18
47
  #
19
- # kiso_icon("lucide:check")
20
- # kiso_icon("check") # uses default set (lucide)
21
- # kiso_icon("check", size: :md) # size preset (standalone use)
22
- # kiso_icon("check", class: "text-success") # extra classes
23
- # kiso_icon("check", aria: { label: "Done" }) # accessible icon
48
+ # Size defaults to +nil+ so parent components (Button, Alert, Badge) can
49
+ # control icon sizing via CSS selectors like +[&_svg]:size-4+.
50
+ # Pass an explicit +size:+ for standalone icons outside components.
24
51
  #
25
- # Size defaults to nil so parent components (Button, Alert, Badge) can
26
- # control icon sizing via CSS selectors like [&_svg]:size-4.
27
- # Pass an explicit size: for standalone icons outside components.
52
+ # @param name [String] icon identifier, optionally prefixed with set
53
+ # (e.g. +"check"+, +"lucide:check"+)
54
+ # @param size [Symbol, nil] size preset from {SIZE_PRESETS}
55
+ # (+:xs+, +:sm+, +:md+, +:lg+, +:xl+)
56
+ # @param options [Hash] forwarded to +kiso_icon_tag+
57
+ # (e.g. +class:+, +aria:+, +data:+)
58
+ # @return [ActiveSupport::SafeBuffer] rendered SVG
28
59
  #
60
+ # @example Basic usage
61
+ # kiso_icon("check")
62
+ # @example With size preset
63
+ # kiso_icon("check", size: :md)
64
+ # @example With extra classes
65
+ # kiso_icon("check", class: "text-success")
66
+ # @example Accessible icon
67
+ # kiso_icon("check", aria: { label: "Done" })
29
68
  def kiso_icon(name, size: nil, **options)
30
69
  css_classes = options.delete(:class) || ""
31
- size_class = size ? SIZE_PRESETS.fetch(size, nil) : nil
70
+ size_class = size ? SIZE_PRESETS.fetch(size) : nil
32
71
  merged = merge_icon_classes(BASE_CLASSES, size_class, css_classes)
33
72
 
34
73
  kiso_icon_tag(name, class: merged, **options)
@@ -36,11 +75,16 @@ module Kiso
36
75
 
37
76
  private
38
77
 
78
+ # Merges icon class parts via TailwindMerge, filtering out blanks.
79
+ #
80
+ # @param parts [Array<String, nil>] class strings to merge
81
+ # @return [String] deduplicated class string
39
82
  def merge_icon_classes(*parts)
40
83
  combined = parts.reject { |p| p.nil? || p.to_s.empty? }.join(" ")
41
84
  icon_class_merger.merge(combined)
42
85
  end
43
86
 
87
+ # @return [TailwindMerge::Merger] memoized merger instance
44
88
  def icon_class_merger
45
89
  @icon_class_merger ||= TailwindMerge::Merger.new
46
90
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kiso
4
+ # View helper for dark mode FOUC prevention.
5
+ #
6
+ # Included in all views automatically by {Engine}.
7
+ module ThemeHelper
8
+ THEME_SCRIPT = <<~JS.squish.freeze
9
+ (function(){
10
+ var k="theme";
11
+ var t=localStorage.getItem(k);
12
+ if(!t){var m=document.cookie.match(new RegExp("(?:^|; )"+k+"=([^;]*)"));t=m&&m[1]}
13
+ if(t==="dark"||((!t||t==="system")&&matchMedia("(prefers-color-scheme:dark)").matches)){
14
+ document.documentElement.classList.add("dark")
15
+ }
16
+ })()
17
+ JS
18
+
19
+ # Outputs an inline script that prevents dark mode FOUC.
20
+ #
21
+ # Reads the theme preference from localStorage (primary) or cookie
22
+ # (fallback), respects +prefers-color-scheme+ as default, and sets
23
+ # +.dark+ on +<html>+ synchronously before first paint.
24
+ #
25
+ # Call in +<head>+, ideally before stylesheet links.
26
+ #
27
+ # @example In a layout
28
+ # <head>
29
+ # <%= kiso_theme_script %>
30
+ # <%= stylesheet_link_tag "tailwind" %>
31
+ # </head>
32
+ #
33
+ # @return [ActiveSupport::SafeBuffer]
34
+ def kiso_theme_script
35
+ javascript_tag(THEME_SCRIPT, nonce: true)
36
+ end
37
+ end
38
+ end