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,44 @@
1
+ module Kiso
2
+ module Themes
3
+ # Navigation breadcrumb trail rendered as a +<nav>+ element.
4
+ #
5
+ # @example
6
+ # Breadcrumb.render
7
+ #
8
+ # Sub-parts: {BreadcrumbList}, {BreadcrumbItem}, {BreadcrumbLink},
9
+ # {BreadcrumbPage}, {BreadcrumbSeparator}, {BreadcrumbEllipsis}
10
+ Breadcrumb = ClassVariants.build(
11
+ base: ""
12
+ )
13
+
14
+ # Ordered list (+<ol>+) containing breadcrumb items.
15
+ BreadcrumbList = ClassVariants.build(
16
+ base: "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5"
17
+ )
18
+
19
+ # List item (+<li>+) wrapping a link or page indicator.
20
+ BreadcrumbItem = ClassVariants.build(
21
+ base: "inline-flex items-center gap-1.5"
22
+ )
23
+
24
+ # Clickable link to an ancestor page.
25
+ BreadcrumbLink = ClassVariants.build(
26
+ base: "hover:text-foreground transition-colors"
27
+ )
28
+
29
+ # Current page indicator (non-interactive, +aria-current="page"+).
30
+ BreadcrumbPage = ClassVariants.build(
31
+ base: "text-foreground font-normal"
32
+ )
33
+
34
+ # Chevron or custom separator between breadcrumb items.
35
+ BreadcrumbSeparator = ClassVariants.build(
36
+ base: "[&>svg]:size-3.5"
37
+ )
38
+
39
+ # Ellipsis indicator for collapsed breadcrumb levels.
40
+ BreadcrumbEllipsis = ClassVariants.build(
41
+ base: "flex size-9 items-center justify-center"
42
+ )
43
+ end
44
+ end
@@ -1,12 +1,25 @@
1
1
  module Kiso
2
2
  module Themes
3
+ # Clickable button with color, variant, and size axes.
4
+ #
5
+ # Renders as a +<button>+ by default; the partial switches to +<a>+ when
6
+ # +href:+ is provided (smart tag).
7
+ #
8
+ # @example
9
+ # Button.render(color: :primary, variant: :solid, size: :md)
10
+ #
11
+ # Variants:
12
+ # - +color+ — :primary (default), :secondary, :success, :info, :warning, :error, :neutral
13
+ # - +variant+ — :solid (default), :outline, :soft, :subtle, :ghost, :link
14
+ # - +size+ — :xs, :sm, :md (default), :lg, :xl
15
+ # - +block+ — +true+ for full-width, +false+ (default)
3
16
  Button = ClassVariants.build(
4
17
  base: "inline-flex items-center justify-center gap-2 font-medium whitespace-nowrap shrink-0 " \
5
18
  "transition-all " \
6
19
  "focus-visible:outline-2 focus-visible:outline-offset-2 " \
7
20
  "disabled:pointer-events-none disabled:opacity-50 " \
8
21
  "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 " \
9
- "[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
22
+ "#{Shared::SVG_BASE}",
10
23
  variants: {
11
24
  variant: {
12
25
  solid: "",
@@ -21,7 +34,7 @@ module Kiso
21
34
  sm: "h-8 px-3 py-1.5 text-xs rounded-md gap-1.5 has-[>svg]:px-2.5",
22
35
  md: "h-9 px-4 py-2 text-sm rounded-md gap-2 has-[>svg]:px-3",
23
36
  lg: "h-10 px-5 py-2.5 text-sm rounded-md gap-2 has-[>svg]:px-4",
24
- xl: "h-11 px-6 py-3 text-base rounded-lg gap-2.5 has-[>svg]:px-5 [&_svg:not([class*='size-'])]:size-5"
37
+ xl: "h-11 px-6 py-3 text-base rounded-md gap-2.5 has-[>svg]:px-5 [&_svg:not([class*='size-'])]:size-5"
25
38
  },
26
39
  color: COLORS.index_with { "" },
27
40
  block: {
@@ -1,5 +1,16 @@
1
1
  module Kiso
2
2
  module Themes
3
+ # Container card with header, content, and footer sections.
4
+ #
5
+ # @example
6
+ # Card.render(variant: :outline)
7
+ #
8
+ # Variants:
9
+ # - +variant+ — :outline (default), :soft, :subtle
10
+ #
11
+ # Sub-parts: {CardHeader}, {CardTitle}, {CardDescription}, {CardAction},
12
+ # {CardContent}, {CardFooter}
13
+ #
3
14
  # shadcn base: bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm
4
15
  # We use variant: instead of hardcoded border + shadow, and our semantic tokens.
5
16
  Card = ClassVariants.build(
@@ -16,9 +27,9 @@ module Kiso
16
27
 
17
28
  # shadcn: @container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6
18
29
  # has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6
19
- # Simplified: we don't have CardAction yet, so flex-col instead of grid.
20
30
  CardHeader = ClassVariants.build(
21
- base: "flex flex-col gap-1.5 px-6"
31
+ base: "grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 " \
32
+ "has-[>[data-card-part=action]]:grid-cols-[1fr_auto]"
22
33
  )
23
34
 
24
35
  # shadcn: leading-none font-semibold
@@ -31,6 +42,11 @@ module Kiso
31
42
  base: "text-sm text-muted-foreground"
32
43
  )
33
44
 
45
+ # shadcn: col-start-2 row-span-2 row-start-1 self-start justify-self-end
46
+ CardAction = ClassVariants.build(
47
+ base: "col-start-2 row-span-2 row-start-1 self-start justify-self-end"
48
+ )
49
+
34
50
  # shadcn: px-6
35
51
  CardContent = ClassVariants.build(
36
52
  base: "px-6"
@@ -0,0 +1,33 @@
1
+ module Kiso
2
+ module Themes
3
+ # Native +<input type="checkbox">+ styled with +appearance-none+ and
4
+ # semantic color tokens.
5
+ #
6
+ # @example
7
+ # Checkbox.render(color: :primary)
8
+ #
9
+ # Variants:
10
+ # - +color+ — :primary (default), :secondary, :success, :info, :warning, :error, :neutral
11
+ Checkbox = ClassVariants.build(
12
+ base: "appearance-none size-4 shrink-0 rounded-[4px] " \
13
+ "ring ring-inset ring-accented shadow-xs " \
14
+ "transition-shadow outline-none " \
15
+ "disabled:cursor-not-allowed disabled:opacity-50 " \
16
+ "focus-visible:ring-[3px] " \
17
+ "aria-invalid:ring-error/30 aria-invalid:ring-2",
18
+ variants: {
19
+ color: COLORS.index_with { "" }
20
+ },
21
+ compound_variants: [
22
+ {color: :primary, class: "checked:bg-primary checked:ring-primary checked:text-primary-foreground focus-visible:ring-primary/50"},
23
+ {color: :secondary, class: "checked:bg-secondary checked:ring-secondary checked:text-secondary-foreground focus-visible:ring-secondary/50"},
24
+ {color: :success, class: "checked:bg-success checked:ring-success checked:text-success-foreground focus-visible:ring-success/50"},
25
+ {color: :info, class: "checked:bg-info checked:ring-info checked:text-info-foreground focus-visible:ring-info/50"},
26
+ {color: :warning, class: "checked:bg-warning checked:ring-warning checked:text-warning-foreground focus-visible:ring-warning/50"},
27
+ {color: :error, class: "checked:bg-error checked:ring-error checked:text-error-foreground focus-visible:ring-error/50"},
28
+ {color: :neutral, class: "checked:bg-inverted checked:ring-inverted checked:text-inverted-foreground focus-visible:ring-inverted/50"}
29
+ ],
30
+ defaults: {color: :primary}
31
+ )
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module Kiso
2
+ module Themes
3
+ ColorModeButton = ClassVariants.build(
4
+ base: "inline-flex items-center justify-center rounded-md text-foreground/50 hover:text-foreground hover:bg-accent transition-colors duration-150 shrink-0 cursor-pointer",
5
+ variants: {
6
+ size: {
7
+ sm: "w-7 h-7 [&>svg]:size-3.5",
8
+ md: "w-8 h-8 [&>svg]:size-4",
9
+ lg: "w-9 h-9 [&>svg]:size-5"
10
+ }
11
+ },
12
+ defaults: {size: :md}
13
+ )
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Kiso
2
+ module Themes
3
+ ColorModeSelect = ClassVariants.build(
4
+ base: ""
5
+ )
6
+ end
7
+ end
@@ -0,0 +1,97 @@
1
+ module Kiso
2
+ module Themes
3
+ # Searchable dropdown (autocomplete) with keyboard navigation.
4
+ #
5
+ # @example
6
+ # Combobox.render
7
+ #
8
+ # Sub-parts: {ComboboxInput}, {ComboboxContent}, {ComboboxList},
9
+ # {ComboboxItem}, {ComboboxItemIndicator}, {ComboboxGroup}, {ComboboxLabel},
10
+ # {ComboboxEmpty}, {ComboboxSeparator}, {ComboboxChips}, {ComboboxChip},
11
+ # {ComboboxChipsInput}
12
+ Combobox = ClassVariants.build(
13
+ base: "relative text-foreground"
14
+ )
15
+
16
+ # Input wrapper (replicates {InputGroup} pattern with embedded text input).
17
+ ComboboxInput = ClassVariants.build(
18
+ base: "text-foreground flex w-full items-center rounded-md " \
19
+ "bg-background ring ring-inset ring-accented shadow-xs " \
20
+ "h-9 min-w-0 " \
21
+ "has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-inset has-[:focus-visible]:ring-primary " \
22
+ "has-[[aria-invalid]]:ring-error " \
23
+ "[&_input]:flex-1 [&_input]:rounded-none [&_input]:border-0 [&_input]:shadow-none " \
24
+ "[&_input]:ring-0 [&_input]:bg-transparent [&_input]:focus-visible:ring-0 " \
25
+ "[&_input]:h-full [&_input]:px-3 [&_input]:py-1 [&_input]:text-base [&_input]:md:text-sm " \
26
+ "[&_input]:outline-none [&_input]:placeholder:text-muted-foreground " \
27
+ "[&_input]:disabled:cursor-not-allowed [&_input]:disabled:opacity-50"
28
+ )
29
+
30
+ # Dropdown panel containing the filtered list of items.
31
+ ComboboxContent = ClassVariants.build(
32
+ base: "bg-background text-foreground z-50 max-h-96 min-w-32 " \
33
+ "overflow-hidden rounded-md shadow-md ring ring-inset ring-border"
34
+ )
35
+
36
+ # Scrollable list container inside {ComboboxContent}.
37
+ ComboboxList = ClassVariants.build(
38
+ base: "scroll-py-1 overflow-y-auto p-1"
39
+ )
40
+
41
+ # Selectable option within the combobox dropdown.
42
+ ComboboxItem = ClassVariants.build(
43
+ base: "relative flex w-full cursor-default items-center gap-2 rounded-sm " \
44
+ "py-1.5 pr-8 pl-2 text-sm outline-none select-none " \
45
+ "data-[highlighted]:bg-elevated data-[highlighted]:text-foreground " \
46
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50 " \
47
+ "#{Shared::SVG_BASE}"
48
+ )
49
+
50
+ # Check mark indicator on the selected item.
51
+ ComboboxItemIndicator = ClassVariants.build(
52
+ base: "pointer-events-none absolute right-2 flex size-4 items-center justify-center"
53
+ )
54
+
55
+ # Semantic grouping wrapper for related combobox items.
56
+ ComboboxGroup = ClassVariants.build(
57
+ base: ""
58
+ )
59
+
60
+ # Non-interactive heading for a {ComboboxGroup}.
61
+ ComboboxLabel = ClassVariants.build(
62
+ base: Shared::MENU_LABEL
63
+ )
64
+
65
+ # "No results" message shown when the filter matches nothing.
66
+ ComboboxEmpty = ClassVariants.build(
67
+ base: "text-muted-foreground flex w-full justify-center py-2 text-center text-sm"
68
+ )
69
+
70
+ # Horizontal divider between combobox items or groups.
71
+ ComboboxSeparator = ClassVariants.build(
72
+ base: Shared::ITEM_SEPARATOR
73
+ )
74
+
75
+ # Multi-select input container displaying selected values as chips.
76
+ ComboboxChips = ClassVariants.build(
77
+ base: "text-foreground flex min-h-9 flex-wrap items-center gap-1.5 rounded-md " \
78
+ "bg-background ring ring-inset ring-accented shadow-xs " \
79
+ "px-2.5 py-1.5 text-sm transition-[color,box-shadow] " \
80
+ "focus-within:ring-2 focus-within:ring-inset focus-within:ring-primary " \
81
+ "has-[[aria-invalid]]:ring-error"
82
+ )
83
+
84
+ # Individual chip representing a selected value in {ComboboxChips}.
85
+ ComboboxChip = ClassVariants.build(
86
+ base: "bg-muted text-foreground flex h-5.5 w-fit items-center " \
87
+ "justify-center gap-1 rounded-sm px-1.5 text-xs font-medium whitespace-nowrap"
88
+ )
89
+
90
+ # Inline text input within the {ComboboxChips} container.
91
+ ComboboxChipsInput = ClassVariants.build(
92
+ base: "min-w-16 flex-1 bg-transparent outline-none text-base md:text-sm " \
93
+ "placeholder:text-muted-foreground " \
94
+ "disabled:cursor-not-allowed disabled:opacity-50"
95
+ )
96
+ end
97
+ end
@@ -0,0 +1,79 @@
1
+ module Kiso
2
+ module Themes
3
+ # Command palette (cmdk-style) with search input, grouped items, and
4
+ # keyboard navigation.
5
+ #
6
+ # @example
7
+ # Command.render
8
+ #
9
+ # Sub-parts: {CommandInputWrapper}, {CommandInput}, {CommandList},
10
+ # {CommandEmpty}, {CommandGroup}, {CommandGroupHeading}, {CommandItem},
11
+ # {CommandSeparator}, {CommandShortcut}, {CommandDialog}, {CommandDialogContent}
12
+ Command = ClassVariants.build(
13
+ base: "bg-background text-foreground flex h-full w-full flex-col overflow-hidden rounded-md"
14
+ )
15
+
16
+ # Container for the search icon and input, separated from the list by a border.
17
+ CommandInputWrapper = ClassVariants.build(
18
+ base: "flex h-9 items-center gap-2 border-b border-border px-3"
19
+ )
20
+
21
+ # The search text input element.
22
+ CommandInput = ClassVariants.build(
23
+ base: "placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent " \
24
+ "py-3 text-sm outline-none " \
25
+ "disabled:cursor-not-allowed disabled:opacity-50"
26
+ )
27
+
28
+ # Scrollable results container.
29
+ CommandList = ClassVariants.build(
30
+ base: "max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto"
31
+ )
32
+
33
+ # "No results" message shown when the search matches nothing.
34
+ CommandEmpty = ClassVariants.build(
35
+ base: "py-6 text-center text-sm"
36
+ )
37
+
38
+ # Logical group of related command items.
39
+ CommandGroup = ClassVariants.build(
40
+ base: "text-foreground overflow-hidden p-1"
41
+ )
42
+
43
+ # Label text displayed above a {CommandGroup}.
44
+ CommandGroupHeading = ClassVariants.build(
45
+ base: "text-muted-foreground px-2 py-1.5 text-xs font-medium"
46
+ )
47
+
48
+ # Selectable action item within the command palette.
49
+ CommandItem = ClassVariants.build(
50
+ base: "data-[selected]:bg-elevated data-[selected]:text-foreground " \
51
+ "[&_svg:not([class*='text-'])]:text-muted-foreground " \
52
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm " \
53
+ "outline-none select-none " \
54
+ "data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 " \
55
+ "#{Shared::SVG_BASE}"
56
+ )
57
+
58
+ # Horizontal divider between command groups.
59
+ CommandSeparator = ClassVariants.build(
60
+ base: "bg-border -mx-1 h-px"
61
+ )
62
+
63
+ # Keyboard shortcut hint displayed on the right side of a {CommandItem}.
64
+ CommandShortcut = ClassVariants.build(
65
+ base: Shared::MENU_SHORTCUT
66
+ )
67
+
68
+ # The native +<dialog>+ element for modal command palette usage.
69
+ CommandDialog = ClassVariants.build(
70
+ base: "fixed inset-0 z-50 m-0 h-full w-full max-w-none max-h-none bg-black/50 p-0 " \
71
+ "flex items-center justify-center backdrop:bg-transparent open:flex"
72
+ )
73
+
74
+ # Inner content wrapper inside the {CommandDialog}.
75
+ CommandDialogContent = ClassVariants.build(
76
+ base: "bg-background text-foreground overflow-hidden rounded-lg ring ring-inset ring-border shadow-lg w-full max-w-lg"
77
+ )
78
+ end
79
+ end
@@ -0,0 +1,51 @@
1
+ module Kiso
2
+ module Themes
3
+ DashboardGroup = ClassVariants.build(
4
+ base: "grid h-dvh overflow-hidden bg-background text-foreground antialiased"
5
+ )
6
+
7
+ DashboardNavbar = ClassVariants.build(
8
+ base: "flex items-center gap-3 px-4 border-b border-border bg-background shrink-0 z-[--z-topbar]"
9
+ )
10
+
11
+ DashboardNavbarToggle = ClassVariants.build(
12
+ base: "flex items-center justify-center w-8 h-8 rounded-md text-foreground/50 hover:text-foreground hover:bg-accent transition-colors duration-150 shrink-0"
13
+ )
14
+
15
+ DashboardSidebar = ClassVariants.build(
16
+ base: "overflow-hidden"
17
+ )
18
+
19
+ DashboardPanel = ClassVariants.build(
20
+ base: "min-w-0 overflow-y-auto bg-background"
21
+ )
22
+
23
+ DashboardSidebarHeader = ClassVariants.build(
24
+ base: "shrink-0 flex items-center gap-1.5 px-4"
25
+ )
26
+
27
+ DashboardSidebarFooter = ClassVariants.build(
28
+ base: "shrink-0 flex items-center gap-1.5 px-4 py-2"
29
+ )
30
+
31
+ DashboardSidebarToggle = ClassVariants.build(
32
+ base: "lg:hidden flex items-center justify-center w-8 h-8 rounded-md text-foreground/50 hover:text-foreground hover:bg-accent transition-colors duration-150 shrink-0 cursor-pointer"
33
+ )
34
+
35
+ DashboardSidebarCollapse = ClassVariants.build(
36
+ base: "hidden lg:flex items-center justify-center w-8 h-8 rounded-md text-foreground/50 hover:text-foreground hover:bg-accent transition-colors duration-150 shrink-0 cursor-pointer"
37
+ )
38
+
39
+ DashboardToolbar = ClassVariants.build(
40
+ base: "shrink-0 flex items-center justify-between border-b border-border px-4 sm:px-6 gap-1.5 overflow-x-auto h-(--topbar-height)"
41
+ )
42
+
43
+ DashboardToolbarLeft = ClassVariants.build(
44
+ base: "flex items-center gap-1.5"
45
+ )
46
+
47
+ DashboardToolbarRight = ClassVariants.build(
48
+ base: "flex items-center gap-1.5"
49
+ )
50
+ end
51
+ end
@@ -0,0 +1,108 @@
1
+ module Kiso
2
+ module Themes
3
+ # Context menu or action menu triggered by a button click.
4
+ #
5
+ # @example
6
+ # DropdownMenu.render
7
+ #
8
+ # Sub-parts: {DropdownMenuTrigger}, {DropdownMenuContent}, {DropdownMenuItem},
9
+ # {DropdownMenuCheckboxItem}, {DropdownMenuRadioGroup}, {DropdownMenuRadioItem},
10
+ # {DropdownMenuLabel}, {DropdownMenuSeparator}, {DropdownMenuShortcut},
11
+ # {DropdownMenuGroup}, {DropdownMenuSub}, {DropdownMenuSubTrigger},
12
+ # {DropdownMenuSubContent}
13
+ DropdownMenu = ClassVariants.build(
14
+ base: "relative inline-block text-foreground"
15
+ )
16
+
17
+ # Wrapper for the button that opens the menu.
18
+ DropdownMenuTrigger = ClassVariants.build(
19
+ base: "inline-flex"
20
+ )
21
+
22
+ # The dropdown panel containing menu items.
23
+ DropdownMenuContent = ClassVariants.build(
24
+ base: "bg-background text-foreground z-50 min-w-32 " \
25
+ "overflow-x-hidden overflow-y-auto rounded-md ring ring-inset ring-border p-1 shadow-md"
26
+ )
27
+
28
+ # Clickable menu action. Supports a +destructive+ variant for
29
+ # dangerous actions (delete, remove, etc.).
30
+ #
31
+ # Variants:
32
+ # - +variant+ — :default, :destructive
33
+ DropdownMenuItem = ClassVariants.build(
34
+ base: "relative flex cursor-default items-center gap-2 rounded-sm " \
35
+ "px-2 py-1.5 text-sm outline-none select-none " \
36
+ "data-[highlighted]:bg-elevated data-[highlighted]:text-foreground " \
37
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50 " \
38
+ "data-[inset]:pl-8 " \
39
+ "#{Shared::SVG_BASE} " \
40
+ "[&_svg:not([class*='text-'])]:text-muted-foreground",
41
+ variants: {
42
+ variant: {
43
+ default: "",
44
+ destructive: "text-error data-[highlighted]:bg-error/10 data-[highlighted]:text-error " \
45
+ "[&_svg:not([class*='text-'])]:text-error"
46
+ }
47
+ },
48
+ defaults: {variant: :default}
49
+ )
50
+
51
+ # Menu item with a checkbox indicator. Uses {Shared::CHECKABLE_ITEM} base.
52
+ DropdownMenuCheckboxItem = ClassVariants.build(
53
+ base: Shared::CHECKABLE_ITEM
54
+ )
55
+
56
+ # Wrapper for a set of mutually exclusive {DropdownMenuRadioItem} elements.
57
+ DropdownMenuRadioGroup = ClassVariants.build(
58
+ base: ""
59
+ )
60
+
61
+ # Menu item with a radio indicator. Uses {Shared::CHECKABLE_ITEM} base.
62
+ DropdownMenuRadioItem = ClassVariants.build(
63
+ base: Shared::CHECKABLE_ITEM
64
+ )
65
+
66
+ # Non-interactive heading within the menu.
67
+ DropdownMenuLabel = ClassVariants.build(
68
+ base: "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8"
69
+ )
70
+
71
+ # Horizontal divider between menu sections.
72
+ DropdownMenuSeparator = ClassVariants.build(
73
+ base: Shared::ITEM_SEPARATOR
74
+ )
75
+
76
+ # Keyboard shortcut hint on the right side of a {DropdownMenuItem}.
77
+ DropdownMenuShortcut = ClassVariants.build(
78
+ base: Shared::MENU_SHORTCUT
79
+ )
80
+
81
+ # Semantic grouping wrapper for related menu items.
82
+ DropdownMenuGroup = ClassVariants.build(
83
+ base: ""
84
+ )
85
+
86
+ # Wrapper for a nested sub-menu (trigger + content pair).
87
+ DropdownMenuSub = ClassVariants.build(
88
+ base: "relative"
89
+ )
90
+
91
+ # Menu item that opens a nested {DropdownMenuSubContent} on hover.
92
+ DropdownMenuSubTrigger = ClassVariants.build(
93
+ base: "flex cursor-default items-center gap-2 rounded-sm " \
94
+ "px-2 py-1.5 text-sm outline-none select-none " \
95
+ "data-[highlighted]:bg-elevated data-[highlighted]:text-foreground " \
96
+ "data-[state=open]:bg-elevated data-[state=open]:text-foreground " \
97
+ "data-[inset]:pl-8 " \
98
+ "#{Shared::SVG_BASE} " \
99
+ "[&_svg:not([class*='text-'])]:text-muted-foreground"
100
+ )
101
+
102
+ # Panel for nested sub-menu items (same as {DropdownMenuContent} but +shadow-lg+).
103
+ DropdownMenuSubContent = ClassVariants.build(
104
+ base: "bg-background text-foreground z-50 min-w-32 " \
105
+ "overflow-hidden rounded-md ring ring-inset ring-border p-1 shadow-lg"
106
+ )
107
+ end
108
+ end
@@ -0,0 +1,54 @@
1
+ module Kiso
2
+ module Themes
3
+ # Empty state placeholder for when there's no content to display.
4
+ #
5
+ # Centered layout with dashed border (border becomes visible when the user
6
+ # adds +border+ via +css_classes:+).
7
+ #
8
+ # @example
9
+ # Empty.render
10
+ #
11
+ # Sub-parts: {EmptyHeader}, {EmptyMedia}, {EmptyTitle}, {EmptyDescription},
12
+ # {EmptyContent}
13
+ #
14
+ # shadcn base: flex min-w-0 flex-1 flex-col ... border-dashed p-6 ... md:p-12
15
+ Empty = ClassVariants.build(
16
+ base: "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance text-foreground md:p-12"
17
+ )
18
+
19
+ # Container for the empty state's title and description.
20
+ EmptyHeader = ClassVariants.build(
21
+ base: "flex max-w-sm flex-col items-center gap-2 text-center"
22
+ )
23
+
24
+ # Media container for an illustration or icon above the title.
25
+ #
26
+ # Variants:
27
+ # - +variant+ — :default (transparent), :icon (muted background + rounded container)
28
+ EmptyMedia = ClassVariants.build(
29
+ base: "flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
30
+ variants: {
31
+ variant: {
32
+ default: "bg-transparent",
33
+ icon: "bg-muted text-foreground size-10 rounded-lg [&_svg:not([class*='size-'])]:size-6"
34
+ }
35
+ },
36
+ defaults: {variant: :default}
37
+ )
38
+
39
+ # Empty state heading text.
40
+ EmptyTitle = ClassVariants.build(
41
+ base: "text-lg font-medium tracking-tight"
42
+ )
43
+
44
+ # Empty state body text with automatic link styling.
45
+ EmptyDescription = ClassVariants.build(
46
+ base: "text-muted-foreground text-sm/relaxed [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4"
47
+ )
48
+
49
+ # Container for action buttons or other interactive content.
50
+ EmptyContent = ClassVariants.build(
51
+ base: "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance"
52
+ )
53
+ end
54
+ end
@@ -0,0 +1,76 @@
1
+ module Kiso
2
+ module Themes
3
+ # Form field wrapper that groups a label, input, description, and error message.
4
+ #
5
+ # @example
6
+ # Field.render(orientation: :vertical)
7
+ #
8
+ # Variants:
9
+ # - +orientation+ — :vertical (default), :horizontal, :responsive
10
+ #
11
+ # Sub-parts: {FieldContent}, {FieldLabel}, {FieldTitle}, {FieldDescription},
12
+ # {FieldError}, {FieldSeparator}, {FieldSeparatorText}
13
+ Field = ClassVariants.build(
14
+ base: "group/field flex w-full gap-3 text-foreground data-[invalid=true]:text-error",
15
+ variants: {
16
+ orientation: {
17
+ vertical: "flex-col [&>*]:w-full [&>.sr-only]:w-auto",
18
+ horizontal: "flex-row items-center " \
19
+ "[&>[data-slot=field-label]]:flex-auto " \
20
+ "has-[>[data-slot=field-content]]:items-start " \
21
+ "has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
22
+ responsive: "flex-col [&>*]:w-full [&>.sr-only]:w-auto " \
23
+ "@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto " \
24
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto " \
25
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start " \
26
+ "@md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
27
+ }
28
+ },
29
+ defaults: {orientation: :vertical}
30
+ )
31
+
32
+ # Wrapper for the input element plus description/error text within a {Field}.
33
+ FieldContent = ClassVariants.build(
34
+ base: "group/field-content flex flex-1 flex-col gap-1.5 leading-snug"
35
+ )
36
+
37
+ # Label container within a {Field}. Supports nested field layouts for
38
+ # checkbox/radio card patterns.
39
+ FieldLabel = ClassVariants.build(
40
+ base: "group/field-label peer/field-label flex w-fit gap-2 leading-snug " \
41
+ "group-data-[disabled=true]/field:opacity-50 " \
42
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col " \
43
+ "has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border " \
44
+ "[&>*[data-slot=field]]:p-4"
45
+ )
46
+
47
+ # Title text within a {FieldLabel}.
48
+ FieldTitle = ClassVariants.build(
49
+ base: "flex w-fit items-center gap-2 text-sm leading-snug font-medium " \
50
+ "group-data-[disabled=true]/field:opacity-50"
51
+ )
52
+
53
+ # Help text below the input. Includes automatic link styling.
54
+ FieldDescription = ClassVariants.build(
55
+ base: "text-muted-foreground text-sm leading-normal font-normal " \
56
+ "group-has-[[data-orientation=horizontal]]/field:text-balance " \
57
+ "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5 " \
58
+ "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4"
59
+ )
60
+
61
+ # Validation error message displayed below the input.
62
+ FieldError = ClassVariants.build(
63
+ base: "text-error text-sm font-normal"
64
+ )
65
+
66
+ # Horizontal rule between fields (e.g. "or" divider).
67
+ FieldSeparator = ClassVariants.build(
68
+ base: "relative -my-2 h-5 text-sm"
69
+ )
70
+
71
+ # Text label centered on the {FieldSeparator} line.
72
+ FieldSeparatorText = ClassVariants.build(
73
+ base: "bg-background text-muted-foreground relative mx-auto block w-fit px-2"
74
+ )
75
+ end
76
+ end
@@ -0,0 +1,15 @@
1
+ module Kiso
2
+ module Themes
3
+ # Groups multiple {Field} elements with consistent vertical spacing.
4
+ #
5
+ # Supports nesting — inner field groups use tighter +gap-4+ spacing.
6
+ # Uses +@container/field-group+ for responsive field orientation queries.
7
+ #
8
+ # @example
9
+ # FieldGroup.render
10
+ FieldGroup = ClassVariants.build(
11
+ base: "group/field-group @container/field-group flex w-full flex-col gap-7 " \
12
+ "[&>[data-slot=field-group]]:gap-4"
13
+ )
14
+ end
15
+ end