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
@@ -0,0 +1,32 @@
1
+ module Kiso
2
+ module Themes
3
+ # Fieldset grouping for related form controls (e.g. checkbox/radio groups).
4
+ #
5
+ # Automatically tightens gap when containing checkbox or radio groups.
6
+ #
7
+ # @example
8
+ # FieldSet.render
9
+ #
10
+ # Sub-parts: {FieldLegend}
11
+ FieldSet = ClassVariants.build(
12
+ base: "flex flex-col gap-6 " \
13
+ "has-[>[data-slot=checkbox-group]]:gap-3 " \
14
+ "has-[>[data-slot=radio-group]]:gap-3"
15
+ )
16
+
17
+ # Heading for a {FieldSet}.
18
+ #
19
+ # Variants:
20
+ # - +variant+ — :legend (default, larger text), :label (smaller, label-sized)
21
+ FieldLegend = ClassVariants.build(
22
+ base: "mb-3 font-medium",
23
+ variants: {
24
+ variant: {
25
+ legend: "text-base",
26
+ label: "text-sm"
27
+ }
28
+ },
29
+ defaults: {variant: :legend}
30
+ )
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ module Kiso
2
+ module Themes
3
+ # Single-line text input field.
4
+ #
5
+ # @example
6
+ # Input.render(variant: :outline, size: :md)
7
+ #
8
+ # Variants:
9
+ # - +variant+ — :outline (default), :soft, :ghost
10
+ # - +size+ — :sm, :md (default), :lg
11
+ Input = ClassVariants.build(
12
+ base: "text-foreground w-full min-w-0 rounded-md outline-none transition-[color,box-shadow] " \
13
+ "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground " \
14
+ "file:text-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium " \
15
+ "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 " \
16
+ "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary " \
17
+ "aria-invalid:ring-error aria-invalid:focus-visible:ring-error",
18
+ variants: {
19
+ variant: {
20
+ outline: "bg-background ring ring-inset ring-accented shadow-xs",
21
+ soft: "bg-elevated/50 hover:bg-elevated focus:bg-elevated",
22
+ ghost: "bg-transparent hover:bg-elevated focus:bg-elevated"
23
+ },
24
+ size: {
25
+ sm: "h-8 px-2.5 py-1 text-sm",
26
+ md: "h-9 px-3 py-1 text-base md:text-sm",
27
+ lg: "h-10 px-3 py-2 text-base"
28
+ }
29
+ },
30
+ defaults: {variant: :outline, size: :md}
31
+ )
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ module Kiso
2
+ module Themes
3
+ # Wrapper that combines an input with leading/trailing addons (icons, text, buttons).
4
+ #
5
+ # Resets the inner input's ring/shadow so the group ring applies uniformly.
6
+ #
7
+ # @example
8
+ # InputGroup.render
9
+ #
10
+ # Sub-parts: {InputGroupAddon}
11
+ InputGroup = ClassVariants.build(
12
+ base: "relative flex w-full items-center rounded-md text-foreground " \
13
+ "ring ring-inset ring-accented shadow-xs " \
14
+ "h-9 min-w-0 has-[>textarea]:h-auto " \
15
+ "has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-inset has-[:focus-visible]:ring-primary " \
16
+ "has-[[aria-invalid]]:ring-error " \
17
+ "[&_input]:flex-1 [&_input]:rounded-none [&_input]:border-0 [&_input]:shadow-none [&_input]:ring-0 [&_input]:bg-transparent [&_input]:focus-visible:ring-0 " \
18
+ "[&_textarea]:flex-1 [&_textarea]:rounded-none [&_textarea]:border-0 [&_textarea]:shadow-none [&_textarea]:ring-0 [&_textarea]:bg-transparent [&_textarea]:focus-visible:ring-0",
19
+ variants: {},
20
+ defaults: {}
21
+ )
22
+
23
+ # Leading or trailing addon element (icon, text, button) inside an {InputGroup}.
24
+ #
25
+ # Variants:
26
+ # - +align+ — :start (default, left side), :end (right side)
27
+ InputGroupAddon = ClassVariants.build(
28
+ base: "text-muted-foreground flex items-center justify-center gap-2 py-1.5 text-sm font-medium select-none " \
29
+ "[&_svg:not([class*='size-'])]:size-4",
30
+ variants: {
31
+ align: {
32
+ start: "ps-3",
33
+ end: "pe-3"
34
+ }
35
+ },
36
+ defaults: {align: :start}
37
+ )
38
+ end
39
+ end
@@ -0,0 +1,46 @@
1
+ module Kiso
2
+ module Themes
3
+ # OTP/PIN code input container. Positions a transparent real +<input>+
4
+ # over visual slot divs managed by the +kiso--input-otp+ Stimulus controller.
5
+ #
6
+ # @example
7
+ # InputOtp.render
8
+ InputOtp = ClassVariants.build(
9
+ base: "relative text-foreground flex items-center gap-2 has-disabled:opacity-50"
10
+ )
11
+
12
+ # Groups adjacent slots visually (e.g., first 3 digits, last 3 digits).
13
+ InputOtpGroup = ClassVariants.build(
14
+ base: "flex items-center"
15
+ )
16
+
17
+ # Individual character display slot with connected borders.
18
+ # Content and active state managed by +kiso--input-otp+ controller.
19
+ #
20
+ # @example
21
+ # InputOtpSlot.render(size: :md)
22
+ InputOtpSlot = ClassVariants.build(
23
+ base: "border-accented relative flex items-center justify-center " \
24
+ "border-y border-r shadow-xs transition-all outline-none " \
25
+ "first:rounded-l-md first:border-l last:rounded-r-md " \
26
+ "data-[active=true]:z-10 data-[active=true]:border-primary " \
27
+ "data-[active=true]:ring-[3px] data-[active=true]:ring-primary/50 " \
28
+ "aria-invalid:border-error " \
29
+ "data-[active=true]:aria-invalid:border-error " \
30
+ "data-[active=true]:aria-invalid:ring-error/20",
31
+ variants: {
32
+ size: {
33
+ sm: "size-8 text-xs",
34
+ md: "size-9 text-sm",
35
+ lg: "size-10 text-base"
36
+ }
37
+ },
38
+ defaults: {size: :md}
39
+ )
40
+
41
+ # Separator between groups (renders a minus icon by default).
42
+ InputOtpSeparator = ClassVariants.build(
43
+ base: "flex items-center text-muted-foreground [&>svg]:size-4"
44
+ )
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ module Kiso
2
+ module Themes
3
+ # Keyboard shortcut indicator (e.g. +Ctrl+K+, +Cmd+S+).
4
+ #
5
+ # @example
6
+ # Kbd.render(size: :md)
7
+ #
8
+ # Variants:
9
+ # - +size+ — :sm, :md (default), :lg
10
+ #
11
+ # Sub-parts: {KbdGroup}
12
+ Kbd = ClassVariants.build(
13
+ base: "bg-muted text-foreground pointer-events-none inline-flex items-center " \
14
+ "justify-center gap-1 rounded-sm font-sans font-medium select-none " \
15
+ "[&_svg:not([class*='size-'])]:size-3",
16
+ variants: {
17
+ size: {
18
+ sm: "h-4 min-w-4 px-0.5 text-xs",
19
+ md: "h-5 min-w-5 px-1 text-xs",
20
+ lg: "h-6 min-w-6 px-1.5 text-xs"
21
+ }
22
+ },
23
+ defaults: {size: :md}
24
+ )
25
+
26
+ # Inline container for multiple {Kbd} elements (e.g. +Ctrl+ + +K+).
27
+ KbdGroup = ClassVariants.build(
28
+ base: "inline-flex items-center gap-1"
29
+ )
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module Kiso
2
+ module Themes
3
+ # Standalone text label for form controls.
4
+ #
5
+ # Automatically dims when the associated control is disabled (via
6
+ # +group-data-[disabled]+ and +peer-disabled+ selectors).
7
+ #
8
+ # @example
9
+ # Label.render
10
+ Label = ClassVariants.build(
11
+ base: "flex items-center gap-2 text-sm leading-none font-medium select-none " \
12
+ "group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 " \
13
+ "peer-disabled:cursor-not-allowed peer-disabled:opacity-50"
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ module Kiso
2
+ module Themes
3
+ Nav = ClassVariants.build(
4
+ base: "flex flex-col gap-2"
5
+ )
6
+
7
+ NavSection = ClassVariants.build(
8
+ base: "relative flex w-full min-w-0 flex-col gap-1 p-2"
9
+ )
10
+
11
+ NavSectionTitle = ClassVariants.build(
12
+ base: "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-muted-foreground cursor-pointer select-none list-none [&::-webkit-details-marker]:hidden"
13
+ )
14
+
15
+ NavSectionContent = ClassVariants.build(
16
+ base: "flex w-full min-w-0 flex-col gap-1 text-sm"
17
+ )
18
+
19
+ NavItem = ClassVariants.build(
20
+ base: "group relative w-full flex items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm outline-hidden transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring data-[active=true]:bg-accent data-[active=true]:font-medium data-[active=true]:text-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0"
21
+ )
22
+
23
+ NavItemBadge = ClassVariants.build(
24
+ base: "ms-auto pointer-events-none flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none"
25
+ )
26
+ end
27
+ end
@@ -0,0 +1,73 @@
1
+ module Kiso
2
+ module Themes
3
+ # @return [String] shared base classes for all pagination link-type elements.
4
+ # Matches shadcn's buttonVariants base (ghost/outline neutral).
5
+ PAGINATION_LINK_BASE = "inline-flex items-center justify-center whitespace-nowrap rounded-md " \
6
+ "text-sm font-medium transition-all " \
7
+ "focus-visible:outline-2 focus-visible:outline-offset-2 " \
8
+ "disabled:pointer-events-none disabled:opacity-50 " \
9
+ "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 " \
10
+ "#{Shared::SVG_BASE}"
11
+
12
+ # @return [Hash] shared active/inactive variants for pagination links.
13
+ # +active: true+ renders outline neutral style, +active: false+ renders ghost.
14
+ PAGINATION_ACTIVE_VARIANTS = {
15
+ active: {
16
+ true => "ring ring-inset text-foreground bg-background ring-accented " \
17
+ "hover:bg-elevated focus-visible:ring-2 focus-visible:ring-inverted",
18
+ false => "text-foreground hover:bg-elevated active:bg-accented/75 focus-visible:outline-inverted"
19
+ }
20
+ }.freeze
21
+
22
+ # Page navigation with numbered links, prev/next buttons, and ellipsis.
23
+ #
24
+ # @example
25
+ # Pagination.render
26
+ #
27
+ # Sub-parts: {PaginationContent}, {PaginationItem}, {PaginationLink},
28
+ # {PaginationPrevious}, {PaginationNext}, {PaginationEllipsis}
29
+ Pagination = ClassVariants.build(
30
+ base: "mx-auto flex w-full justify-center"
31
+ )
32
+
33
+ # Flex row container for pagination items.
34
+ PaginationContent = ClassVariants.build(
35
+ base: "flex flex-row items-center gap-1"
36
+ )
37
+
38
+ # Wrapper for a single pagination element (link, ellipsis, etc.).
39
+ PaginationItem = ClassVariants.build(
40
+ base: ""
41
+ )
42
+
43
+ # Square button for a page number. Switches between ghost and outline
44
+ # style based on the +active+ variant.
45
+ #
46
+ # Variants:
47
+ # - +active+ — +true+ (outline, current page), +false+ (default, ghost)
48
+ PaginationLink = ClassVariants.build(
49
+ base: "#{PAGINATION_LINK_BASE} size-9",
50
+ variants: PAGINATION_ACTIVE_VARIANTS,
51
+ defaults: {active: false}
52
+ )
53
+
54
+ # "Previous" navigation link with chevron icon.
55
+ PaginationPrevious = ClassVariants.build(
56
+ base: "#{PAGINATION_LINK_BASE} h-9 gap-1 px-2.5 sm:pl-2.5",
57
+ variants: PAGINATION_ACTIVE_VARIANTS,
58
+ defaults: {active: false}
59
+ )
60
+
61
+ # "Next" navigation link with chevron icon.
62
+ PaginationNext = ClassVariants.build(
63
+ base: "#{PAGINATION_LINK_BASE} h-9 gap-1 px-2.5 sm:pr-2.5",
64
+ variants: PAGINATION_ACTIVE_VARIANTS,
65
+ defaults: {active: false}
66
+ )
67
+
68
+ # Ellipsis indicator for skipped page ranges.
69
+ PaginationEllipsis = ClassVariants.build(
70
+ base: "pointer-events-none flex size-9 items-center justify-center text-muted-foreground"
71
+ )
72
+ end
73
+ end
@@ -0,0 +1,32 @@
1
+ module Kiso
2
+ module Themes
3
+ # Floating panel anchored to a trigger element.
4
+ #
5
+ # @example
6
+ # PopoverContent.render
7
+ #
8
+ # Sub-parts: {PopoverContent}, {PopoverHeader}, {PopoverTitle},
9
+ # {PopoverDescription}
10
+
11
+ # The floating panel itself. Positioned via the Popover Stimulus controller.
12
+ PopoverContent = ClassVariants.build(
13
+ base: "bg-background text-foreground z-50 w-72 rounded-md " \
14
+ "ring ring-inset ring-border p-4 shadow-md outline-hidden"
15
+ )
16
+
17
+ # Header section with title and description.
18
+ PopoverHeader = ClassVariants.build(
19
+ base: "flex flex-col gap-1 text-sm"
20
+ )
21
+
22
+ # Popover heading text.
23
+ PopoverTitle = ClassVariants.build(
24
+ base: "font-medium"
25
+ )
26
+
27
+ # Popover body text.
28
+ PopoverDescription = ClassVariants.build(
29
+ base: "text-muted-foreground"
30
+ )
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ module Kiso
2
+ module Themes
3
+ # Grid container for a set of {RadioGroupItem} elements.
4
+ #
5
+ # @example
6
+ # RadioGroup.render
7
+ #
8
+ # Sub-parts: {RadioGroupItem}
9
+ RadioGroup = ClassVariants.build(
10
+ base: "grid gap-3"
11
+ )
12
+
13
+ # Native +<input type="radio">+ styled with +appearance-none+ and semantic
14
+ # color tokens. Uses the same color compound variant pattern as {Checkbox}.
15
+ #
16
+ # @example
17
+ # RadioGroupItem.render(color: :primary)
18
+ #
19
+ # Variants:
20
+ # - +color+ — :primary (default), :secondary, :success, :info, :warning, :error, :neutral
21
+ RadioGroupItem = ClassVariants.build(
22
+ base: "appearance-none aspect-square size-4 shrink-0 rounded-full " \
23
+ "ring ring-inset ring-accented shadow-xs " \
24
+ "transition-[color,box-shadow] outline-none " \
25
+ "disabled:cursor-not-allowed disabled:opacity-50 " \
26
+ "focus-visible:ring-[3px] " \
27
+ "aria-invalid:ring-error/30 aria-invalid:ring-2",
28
+ variants: {
29
+ color: COLORS.index_with { "" }
30
+ },
31
+ compound_variants: [
32
+ {color: :primary, class: "checked:bg-primary checked:ring-primary checked:text-primary-foreground focus-visible:ring-primary/50"},
33
+ {color: :secondary, class: "checked:bg-secondary checked:ring-secondary checked:text-secondary-foreground focus-visible:ring-secondary/50"},
34
+ {color: :success, class: "checked:bg-success checked:ring-success checked:text-success-foreground focus-visible:ring-success/50"},
35
+ {color: :info, class: "checked:bg-info checked:ring-info checked:text-info-foreground focus-visible:ring-info/50"},
36
+ {color: :warning, class: "checked:bg-warning checked:ring-warning checked:text-warning-foreground focus-visible:ring-warning/50"},
37
+ {color: :error, class: "checked:bg-error checked:ring-error checked:text-error-foreground focus-visible:ring-error/50"},
38
+ {color: :neutral, class: "checked:bg-inverted checked:ring-inverted checked:text-inverted-foreground focus-visible:ring-inverted/50"}
39
+ ],
40
+ defaults: {color: :primary}
41
+ )
42
+ end
43
+ end
@@ -0,0 +1,78 @@
1
+ module Kiso
2
+ module Themes
3
+ # Native-like select dropdown with custom styling and keyboard navigation.
4
+ #
5
+ # @example
6
+ # Select.render
7
+ #
8
+ # Sub-parts: {SelectTrigger}, {SelectValue}, {SelectContent}, {SelectGroup},
9
+ # {SelectLabel}, {SelectItem}, {SelectItemIndicator}, {SelectSeparator}
10
+ Select = ClassVariants.build(
11
+ base: "relative text-foreground"
12
+ )
13
+
14
+ # Button that displays the selected value and opens the dropdown.
15
+ #
16
+ # Variants:
17
+ # - +size+ — :sm, :md (default)
18
+ SelectTrigger = ClassVariants.build(
19
+ base: "text-foreground flex w-full items-center justify-between gap-2 rounded-md " \
20
+ "bg-background px-3 py-2 text-sm whitespace-nowrap shadow-xs " \
21
+ "ring ring-inset ring-accented " \
22
+ "outline-none transition-[color,box-shadow] " \
23
+ "focus-visible:ring-2 focus-visible:ring-primary " \
24
+ "aria-invalid:ring-error aria-invalid:focus-visible:ring-error " \
25
+ "disabled:cursor-not-allowed disabled:opacity-50 " \
26
+ "#{Shared::SVG_BASE} " \
27
+ "[&_svg:not([class*='text-'])]:text-muted-foreground",
28
+ variants: {
29
+ size: {
30
+ sm: "h-8",
31
+ md: "h-9"
32
+ }
33
+ },
34
+ defaults: {size: :md}
35
+ )
36
+
37
+ # Display area for the currently selected value inside {SelectTrigger}.
38
+ SelectValue = ClassVariants.build(
39
+ base: "line-clamp-1 flex items-center gap-2"
40
+ )
41
+
42
+ # Dropdown panel containing the selectable items.
43
+ SelectContent = ClassVariants.build(
44
+ base: "relative bg-background text-foreground z-50 max-h-60 min-w-32 " \
45
+ "overflow-x-hidden overflow-y-auto rounded-md shadow-md ring ring-inset ring-border p-1"
46
+ )
47
+
48
+ # Semantic grouping wrapper for related select items.
49
+ SelectGroup = ClassVariants.build(
50
+ base: ""
51
+ )
52
+
53
+ # Non-interactive heading for a {SelectGroup}.
54
+ SelectLabel = ClassVariants.build(
55
+ base: Shared::MENU_LABEL
56
+ )
57
+
58
+ # Selectable option within the dropdown.
59
+ SelectItem = ClassVariants.build(
60
+ base: "relative flex w-full cursor-default items-center gap-2 rounded-sm " \
61
+ "py-1.5 pr-8 pl-2 text-sm outline-none select-none " \
62
+ "data-[highlighted]:bg-elevated data-[highlighted]:text-foreground " \
63
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50 " \
64
+ "#{Shared::SVG_BASE} " \
65
+ "[&_svg:not([class*='text-'])]:text-muted-foreground"
66
+ )
67
+
68
+ # Check mark indicator shown on the selected item.
69
+ SelectItemIndicator = ClassVariants.build(
70
+ base: "absolute right-2 flex size-3.5 items-center justify-center"
71
+ )
72
+
73
+ # Horizontal divider between select items or groups.
74
+ SelectSeparator = ClassVariants.build(
75
+ base: "bg-border pointer-events-none -mx-1 my-1 h-px"
76
+ )
77
+ end
78
+ end
@@ -0,0 +1,49 @@
1
+ module Kiso
2
+ module Themes
3
+ # Wrapper for the select native element and its chevron icon overlay.
4
+ #
5
+ # @example
6
+ # SelectNativeWrapper.render
7
+ SelectNativeWrapper = ClassVariants.build(
8
+ base: "relative w-full has-[select:disabled]:opacity-50"
9
+ )
10
+
11
+ # Native HTML `<select>` element with appearance-none and Kiso styling.
12
+ #
13
+ # @example
14
+ # SelectNative.render(variant: :outline, size: :md)
15
+ #
16
+ # Variants:
17
+ # - +variant+ — :outline (default), :soft, :ghost
18
+ # - +size+ — :sm, :md (default), :lg
19
+ SelectNative = ClassVariants.build(
20
+ base: "text-foreground w-full min-w-0 appearance-none rounded-md pr-9 outline-none " \
21
+ "transition-[color,box-shadow] " \
22
+ "disabled:pointer-events-none disabled:cursor-not-allowed " \
23
+ "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary " \
24
+ "aria-invalid:ring-error aria-invalid:focus-visible:ring-error",
25
+ variants: {
26
+ variant: {
27
+ outline: "bg-background ring ring-inset ring-accented shadow-xs",
28
+ soft: "bg-elevated/50 hover:bg-elevated focus:bg-elevated",
29
+ ghost: "bg-transparent hover:bg-elevated focus:bg-elevated"
30
+ },
31
+ size: {
32
+ sm: "h-8 px-2.5 py-1 text-sm",
33
+ md: "h-9 px-3 py-1 text-base md:text-sm",
34
+ lg: "h-10 px-3 py-2 text-base"
35
+ }
36
+ },
37
+ defaults: {variant: :outline, size: :md}
38
+ )
39
+
40
+ # Chevron icon overlay positioned inside the select native wrapper.
41
+ #
42
+ # @example
43
+ # SelectNativeIcon.render
44
+ SelectNativeIcon = ClassVariants.build(
45
+ base: "text-muted-foreground pointer-events-none absolute top-1/2 right-3.5 " \
46
+ "size-4 -translate-y-1/2 opacity-50 select-none"
47
+ )
48
+ end
49
+ end
@@ -1,8 +1,14 @@
1
1
  module Kiso
2
2
  module Themes
3
+ # Visual divider between content sections.
4
+ #
5
+ # @example
6
+ # Separator.render(orientation: :horizontal)
7
+ #
8
+ # Variants:
9
+ # - +orientation+ — :horizontal (default), :vertical
10
+ #
3
11
  # shadcn base: bg-border shrink-0
4
- # Horizontal: h-px w-full
5
- # Vertical: h-full w-px
6
12
  Separator = ClassVariants.build(
7
13
  base: "bg-border shrink-0",
8
14
  variants: {
@@ -0,0 +1,51 @@
1
+ module Kiso
2
+ # Theme definitions for all Kiso components.
3
+ #
4
+ # Each constant is a +ClassVariants+ instance that maps variant options
5
+ # to Tailwind CSS class strings. Call +.render+ with variant options to
6
+ # get the resolved class string:
7
+ #
8
+ # Kiso::Themes::Badge.render(color: :success, variant: :soft, size: :md)
9
+ # # => "inline-flex items-center ... bg-success/10 text-success ..."
10
+ #
11
+ # Compound components (Card, Table, Field, etc.) have multiple constants
12
+ # for the root and each sub-part (e.g. +Card+, +CardHeader+, +CardTitle+).
13
+ #
14
+ # @see project/design-system.md for compound variant formulas and token mapping
15
+ # @see project/component-strategy.md for architecture patterns
16
+ module Themes
17
+ # Shared class strings for patterns that are identical across multiple
18
+ # components. Using shared constants prevents drift when updating styles.
19
+ #
20
+ # Only extract constants that are byte-for-byte identical across components.
21
+ # Component-specific variations (e.g., CommandSeparator missing +my-1+,
22
+ # SelectSeparator with +pointer-events-none+) should remain inline to
23
+ # preserve shadcn fidelity.
24
+ module Shared
25
+ # Used by: Button, Toggle, ToggleGroupItem, SelectTrigger, SelectItem,
26
+ # ComboboxItem, CommandItem, DropdownMenuItem, DropdownMenuSubTrigger,
27
+ # DropdownMenuCheckboxItem, DropdownMenuRadioItem (via CHECKABLE_ITEM),
28
+ # PaginationLink/Previous/Next (via PAGINATION_LINK_BASE)
29
+ SVG_BASE = "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
30
+
31
+ # Used by: ComboboxSeparator, DropdownMenuSeparator
32
+ # (CommandSeparator omits my-1, SelectSeparator adds pointer-events-none — both per shadcn)
33
+ ITEM_SEPARATOR = "bg-border -mx-1 my-1 h-px"
34
+
35
+ # Used by: ComboboxLabel, SelectLabel
36
+ # (CommandGroupHeading adds font-medium — per shadcn)
37
+ MENU_LABEL = "text-muted-foreground px-2 py-1.5 text-xs"
38
+
39
+ # Used by: CommandShortcut, DropdownMenuShortcut
40
+ MENU_SHORTCUT = "text-muted-foreground ml-auto text-xs tracking-widest"
41
+
42
+ # Shared base for checkbox and radio items in DropdownMenu.
43
+ # These are structurally identical — same layout, same interactive states.
44
+ CHECKABLE_ITEM = "relative flex cursor-default items-center gap-2 rounded-sm " \
45
+ "py-1.5 pr-2 pl-8 text-sm outline-none select-none " \
46
+ "data-[highlighted]:bg-elevated data-[highlighted]:text-foreground " \
47
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50 " \
48
+ "#{SVG_BASE}"
49
+ end
50
+ end
51
+ end
@@ -1,9 +1,19 @@
1
1
  module Kiso
2
2
  module Themes
3
- # Stats card specialized Card layout for dashboard metrics.
4
- # Same variant axis as Card (outline/soft/subtle) with tighter spacing.
5
- # shadcn ref: section-cards.tsx (Card + CardDescription + CardTitle + CardAction)
6
- # Maquina ref: stats/_stats_card.html.erb
3
+ # Compact card for displaying a single dashboard metric.
4
+ #
5
+ # Same variant axis as {Card} (outline/soft/subtle) with tighter spacing.
6
+ #
7
+ # @example
8
+ # StatsCard.render(variant: :outline)
9
+ #
10
+ # Variants:
11
+ # - +variant+ — :outline (default), :soft, :subtle
12
+ #
13
+ # Sub-parts: {StatsCardHeader}, {StatsCardLabel}, {StatsCardValue},
14
+ # {StatsCardDescription}
15
+ #
16
+ # Related: {StatsGrid} for responsive grid layout of multiple stats cards.
7
17
  StatsCard = ClassVariants.build(
8
18
  base: "flex flex-col gap-2 rounded-xl p-4 text-foreground",
9
19
  variants: {
@@ -16,32 +26,34 @@ module Kiso
16
26
  defaults: {variant: :outline}
17
27
  )
18
28
 
19
- # shadcn: CardHeader with CardDescription + CardAction in a row
20
- # Flex row for label on left, optional icon/badge on right.
29
+ # Header row with label on the left and optional icon/badge on the right.
21
30
  StatsCardHeader = ClassVariants.build(
22
31
  base: "flex items-center justify-between gap-2"
23
32
  )
24
33
 
25
- # shadcn: CardDescription text-sm text-muted-foreground
26
- # The metric name ("Total Revenue", "New Customers").
34
+ # The metric name (e.g. "Total Revenue", "New Customers").
27
35
  StatsCardLabel = ClassVariants.build(
28
36
  base: "text-sm font-medium text-muted-foreground"
29
37
  )
30
38
 
31
- # shadcn: CardTitle overridden to text-2xl font-semibold tabular-nums
32
- # The big metric number ("$1,250.00", "45,678").
39
+ # The prominent metric number (e.g. "$1,250.00", "45,678").
40
+ # Uses +tabular-nums+ for aligned digits.
33
41
  StatsCardValue = ClassVariants.build(
34
42
  base: "text-2xl font-semibold tabular-nums"
35
43
  )
36
44
 
37
- # shadcn: CardFooter description text-xs text-muted-foreground
38
- # Trend text, subtitle, or additional context.
45
+ # Supplementary text below the value (e.g. trend info, subtitle).
39
46
  StatsCardDescription = ClassVariants.build(
40
47
  base: "text-xs text-muted-foreground"
41
48
  )
42
49
 
43
- # Responsive grid wrapper for stats cards.
44
- # Maquina ref: stats/_stats_grid.html.erb
50
+ # Responsive grid layout for arranging multiple {StatsCard} instances.
51
+ #
52
+ # @example
53
+ # StatsGrid.render(columns: 4)
54
+ #
55
+ # Variants:
56
+ # - +columns+ — 2, 3, 4 (default)
45
57
  StatsGrid = ClassVariants.build(
46
58
  base: "grid grid-cols-1 gap-4",
47
59
  variants: {