kiso 0.1.0.pre → 0.1.1.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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -2
  3. data/README.md +69 -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 +176 -0
  8. data/app/assets/tailwind/kiso/engine.css +103 -0
  9. data/app/assets/tailwind/kiso/radio-group.css +17 -0
  10. data/app/helpers/kiso/component_helper.rb +46 -27
  11. data/app/helpers/kiso/icon_helper.rb +53 -9
  12. data/app/helpers/kiso/theme_helper.rb +38 -0
  13. data/app/javascript/controllers/kiso/combobox_controller.js +616 -0
  14. data/app/javascript/controllers/kiso/command_controller.js +184 -0
  15. data/app/javascript/controllers/kiso/command_dialog_controller.js +104 -0
  16. data/app/javascript/controllers/kiso/dropdown_menu_controller.js +684 -0
  17. data/app/javascript/controllers/kiso/index.d.ts +12 -0
  18. data/app/javascript/controllers/kiso/index.js +39 -0
  19. data/app/javascript/controllers/kiso/popover_controller.js +254 -0
  20. data/app/javascript/controllers/kiso/select_controller.js +307 -0
  21. data/app/javascript/controllers/kiso/sidebar_controller.js +84 -0
  22. data/app/javascript/controllers/kiso/theme_controller.js +89 -0
  23. data/app/javascript/controllers/kiso/toggle_controller.js +24 -0
  24. data/app/javascript/controllers/kiso/toggle_group_controller.js +128 -0
  25. data/app/javascript/kiso/utils/focusable.js +8 -0
  26. data/app/javascript/kiso/utils/highlight.js +43 -0
  27. data/app/javascript/kiso/utils/positioning.js +86 -0
  28. data/app/javascript/kiso/vendor/floating-ui-core.js +1 -0
  29. data/app/javascript/kiso/vendor/floating-ui-dom.js +1 -0
  30. data/app/views/kiso/components/_alert.html.erb +1 -1
  31. data/app/views/kiso/components/_avatar.html.erb +23 -0
  32. data/app/views/kiso/components/_badge.html.erb +1 -1
  33. data/app/views/kiso/components/_breadcrumb.html.erb +8 -0
  34. data/app/views/kiso/components/_button.html.erb +1 -1
  35. data/app/views/kiso/components/_card.html.erb +1 -1
  36. data/app/views/kiso/components/_checkbox.html.erb +7 -0
  37. data/app/views/kiso/components/_color_mode_button.html.erb +14 -0
  38. data/app/views/kiso/components/_color_mode_select.html.erb +24 -0
  39. data/app/views/kiso/components/_combobox.html.erb +12 -0
  40. data/app/views/kiso/components/_command.html.erb +7 -0
  41. data/app/views/kiso/components/_dashboard_group.html.erb +14 -0
  42. data/app/views/kiso/components/_dashboard_navbar.html.erb +7 -0
  43. data/app/views/kiso/components/_dashboard_panel.html.erb +7 -0
  44. data/app/views/kiso/components/_dashboard_sidebar.html.erb +11 -0
  45. data/app/views/kiso/components/_dashboard_toolbar.html.erb +7 -0
  46. data/app/views/kiso/components/_dropdown_menu.html.erb +7 -0
  47. data/app/views/kiso/components/{_empty_state.html.erb → _empty.html.erb} +2 -2
  48. data/app/views/kiso/components/_field.html.erb +12 -0
  49. data/app/views/kiso/components/_field_group.html.erb +7 -0
  50. data/app/views/kiso/components/_field_set.html.erb +7 -0
  51. data/app/views/kiso/components/_input.html.erb +8 -0
  52. data/app/views/kiso/components/_input_group.html.erb +8 -0
  53. data/app/views/kiso/components/_kbd.html.erb +7 -0
  54. data/app/views/kiso/components/_label.html.erb +5 -0
  55. data/app/views/kiso/components/_nav.html.erb +7 -0
  56. data/app/views/kiso/components/_pagination.html.erb +9 -0
  57. data/app/views/kiso/components/_popover.html.erb +8 -0
  58. data/app/views/kiso/components/_radio_group.html.erb +8 -0
  59. data/app/views/kiso/components/_select.html.erb +8 -0
  60. data/app/views/kiso/components/_separator.html.erb +1 -1
  61. data/app/views/kiso/components/_stats_card.html.erb +1 -1
  62. data/app/views/kiso/components/_stats_grid.html.erb +1 -1
  63. data/app/views/kiso/components/_switch.html.erb +10 -0
  64. data/app/views/kiso/components/_table.html.erb +2 -1
  65. data/app/views/kiso/components/_textarea.html.erb +9 -0
  66. data/app/views/kiso/components/_toggle.html.erb +12 -0
  67. data/app/views/kiso/components/_toggle_group.html.erb +12 -0
  68. data/app/views/kiso/components/alert/_description.html.erb +1 -1
  69. data/app/views/kiso/components/alert/_title.html.erb +1 -1
  70. data/app/views/kiso/components/avatar/_badge.html.erb +7 -0
  71. data/app/views/kiso/components/avatar/_fallback.html.erb +7 -0
  72. data/app/views/kiso/components/avatar/_group.html.erb +7 -0
  73. data/app/views/kiso/components/avatar/_group_count.html.erb +7 -0
  74. data/app/views/kiso/components/avatar/_image.html.erb +6 -0
  75. data/app/views/kiso/components/breadcrumb/_ellipsis.html.erb +10 -0
  76. data/app/views/kiso/components/breadcrumb/_item.html.erb +7 -0
  77. data/app/views/kiso/components/breadcrumb/_link.html.erb +7 -0
  78. data/app/views/kiso/components/breadcrumb/_list.html.erb +7 -0
  79. data/app/views/kiso/components/breadcrumb/_page.html.erb +9 -0
  80. data/app/views/kiso/components/breadcrumb/_separator.html.erb +9 -0
  81. data/app/views/kiso/components/card/_action.html.erb +7 -0
  82. data/app/views/kiso/components/card/_content.html.erb +1 -1
  83. data/app/views/kiso/components/card/_description.html.erb +1 -1
  84. data/app/views/kiso/components/card/_footer.html.erb +1 -1
  85. data/app/views/kiso/components/card/_header.html.erb +1 -1
  86. data/app/views/kiso/components/card/_title.html.erb +1 -1
  87. data/app/views/kiso/components/combobox/_chip.html.erb +19 -0
  88. data/app/views/kiso/components/combobox/_chips.html.erb +20 -0
  89. data/app/views/kiso/components/combobox/_chips_input.html.erb +10 -0
  90. data/app/views/kiso/components/combobox/_content.html.erb +9 -0
  91. data/app/views/kiso/components/combobox/_empty.html.erb +9 -0
  92. data/app/views/kiso/components/combobox/_group.html.erb +8 -0
  93. data/app/views/kiso/components/combobox/_input.html.erb +23 -0
  94. data/app/views/kiso/components/combobox/_item.html.erb +19 -0
  95. data/app/views/kiso/components/combobox/_label.html.erb +7 -0
  96. data/app/views/kiso/components/combobox/_list.html.erb +10 -0
  97. data/app/views/kiso/components/combobox/_separator.html.erb +6 -0
  98. data/app/views/kiso/components/command/_dialog.html.erb +11 -0
  99. data/app/views/kiso/components/command/_empty.html.erb +9 -0
  100. data/app/views/kiso/components/command/_group.html.erb +14 -0
  101. data/app/views/kiso/components/command/_input.html.erb +16 -0
  102. data/app/views/kiso/components/command/_item.html.erb +13 -0
  103. data/app/views/kiso/components/command/_list.html.erb +10 -0
  104. data/app/views/kiso/components/command/_separator.html.erb +7 -0
  105. data/app/views/kiso/components/command/_shortcut.html.erb +7 -0
  106. data/app/views/kiso/components/dashboard_navbar/_toggle.html.erb +11 -0
  107. data/app/views/kiso/components/dashboard_sidebar/_collapse.html.erb +12 -0
  108. data/app/views/kiso/components/dashboard_sidebar/_footer.html.erb +7 -0
  109. data/app/views/kiso/components/dashboard_sidebar/_header.html.erb +7 -0
  110. data/app/views/kiso/components/dashboard_sidebar/_toggle.html.erb +11 -0
  111. data/app/views/kiso/components/dashboard_toolbar/_left.html.erb +7 -0
  112. data/app/views/kiso/components/dashboard_toolbar/_right.html.erb +7 -0
  113. data/app/views/kiso/components/dropdown_menu/_checkbox_item.html.erb +18 -0
  114. data/app/views/kiso/components/dropdown_menu/_content.html.erb +10 -0
  115. data/app/views/kiso/components/dropdown_menu/_group.html.erb +8 -0
  116. data/app/views/kiso/components/dropdown_menu/_item.html.erb +15 -0
  117. data/app/views/kiso/components/dropdown_menu/_label.html.erb +8 -0
  118. data/app/views/kiso/components/dropdown_menu/_radio_group.html.erb +10 -0
  119. data/app/views/kiso/components/dropdown_menu/_radio_item.html.erb +19 -0
  120. data/app/views/kiso/components/dropdown_menu/_separator.html.erb +6 -0
  121. data/app/views/kiso/components/dropdown_menu/_shortcut.html.erb +7 -0
  122. data/app/views/kiso/components/dropdown_menu/_sub.html.erb +8 -0
  123. data/app/views/kiso/components/dropdown_menu/_sub_content.html.erb +10 -0
  124. data/app/views/kiso/components/dropdown_menu/_sub_trigger.html.erb +12 -0
  125. data/app/views/kiso/components/dropdown_menu/_trigger.html.erb +9 -0
  126. data/app/views/kiso/components/empty/_content.html.erb +7 -0
  127. data/app/views/kiso/components/empty/_description.html.erb +7 -0
  128. data/app/views/kiso/components/empty/_header.html.erb +7 -0
  129. data/app/views/kiso/components/empty/_media.html.erb +7 -0
  130. data/app/views/kiso/components/empty/_title.html.erb +7 -0
  131. data/app/views/kiso/components/field/_content.html.erb +7 -0
  132. data/app/views/kiso/components/field/_description.html.erb +7 -0
  133. data/app/views/kiso/components/field/_error.html.erb +22 -0
  134. data/app/views/kiso/components/field/_label.html.erb +5 -0
  135. data/app/views/kiso/components/field/_separator.html.erb +15 -0
  136. data/app/views/kiso/components/field/_title.html.erb +7 -0
  137. data/app/views/kiso/components/field_set/_legend.html.erb +9 -0
  138. data/app/views/kiso/components/input_group/_addon.html.erb +7 -0
  139. data/app/views/kiso/components/kbd/_group.html.erb +7 -0
  140. data/app/views/kiso/components/nav/_item.html.erb +15 -0
  141. data/app/views/kiso/components/nav/_section.html.erb +37 -0
  142. data/app/views/kiso/components/nav/_section_title.html.erb +7 -0
  143. data/app/views/kiso/components/pagination/_content.html.erb +7 -0
  144. data/app/views/kiso/components/pagination/_ellipsis.html.erb +9 -0
  145. data/app/views/kiso/components/pagination/_item.html.erb +7 -0
  146. data/app/views/kiso/components/pagination/_link.html.erb +9 -0
  147. data/app/views/kiso/components/pagination/_next.html.erb +12 -0
  148. data/app/views/kiso/components/pagination/_previous.html.erb +12 -0
  149. data/app/views/kiso/components/popover/_anchor.html.erb +8 -0
  150. data/app/views/kiso/components/popover/_content.html.erb +11 -0
  151. data/app/views/kiso/components/popover/_description.html.erb +7 -0
  152. data/app/views/kiso/components/popover/_header.html.erb +7 -0
  153. data/app/views/kiso/components/popover/_title.html.erb +7 -0
  154. data/app/views/kiso/components/popover/_trigger.html.erb +9 -0
  155. data/app/views/kiso/components/radio_group/_item.html.erb +6 -0
  156. data/app/views/kiso/components/select/_content.html.erb +10 -0
  157. data/app/views/kiso/components/select/_group.html.erb +8 -0
  158. data/app/views/kiso/components/select/_item.html.erb +19 -0
  159. data/app/views/kiso/components/select/_label.html.erb +7 -0
  160. data/app/views/kiso/components/select/_separator.html.erb +6 -0
  161. data/app/views/kiso/components/select/_trigger.html.erb +13 -0
  162. data/app/views/kiso/components/select/_value.html.erb +11 -0
  163. data/app/views/kiso/components/stats_card/_description.html.erb +1 -1
  164. data/app/views/kiso/components/stats_card/_header.html.erb +1 -1
  165. data/app/views/kiso/components/stats_card/_label.html.erb +1 -1
  166. data/app/views/kiso/components/stats_card/_value.html.erb +1 -1
  167. data/app/views/kiso/components/table/_body.html.erb +1 -1
  168. data/app/views/kiso/components/table/_caption.html.erb +1 -1
  169. data/app/views/kiso/components/table/_cell.html.erb +1 -1
  170. data/app/views/kiso/components/table/_footer.html.erb +1 -1
  171. data/app/views/kiso/components/table/_head.html.erb +1 -1
  172. data/app/views/kiso/components/table/_header.html.erb +1 -1
  173. data/app/views/kiso/components/table/_row.html.erb +1 -1
  174. data/app/views/kiso/components/toggle_group/_item.html.erb +13 -0
  175. data/config/deploy.docs.yml +31 -0
  176. data/config/deploy.yml +34 -0
  177. data/config/importmap.rb +10 -0
  178. data/lib/kiso/cli/base.rb +15 -0
  179. data/lib/kiso/cli/icons.rb +2 -1
  180. data/lib/kiso/cli/main.rb +6 -0
  181. data/lib/kiso/cli/make.rb +22 -12
  182. data/lib/kiso/configuration.rb +53 -0
  183. data/lib/kiso/engine.rb +36 -1
  184. data/lib/kiso/theme_overrides.rb +130 -0
  185. data/lib/kiso/themes/alert.rb +16 -1
  186. data/lib/kiso/themes/avatar.rb +53 -0
  187. data/lib/kiso/themes/badge.rb +15 -5
  188. data/lib/kiso/themes/breadcrumb.rb +44 -0
  189. data/lib/kiso/themes/button.rb +15 -2
  190. data/lib/kiso/themes/card.rb +18 -2
  191. data/lib/kiso/themes/checkbox.rb +33 -0
  192. data/lib/kiso/themes/color_mode_button.rb +15 -0
  193. data/lib/kiso/themes/color_mode_select.rb +7 -0
  194. data/lib/kiso/themes/combobox.rb +97 -0
  195. data/lib/kiso/themes/command.rb +79 -0
  196. data/lib/kiso/themes/dashboard.rb +51 -0
  197. data/lib/kiso/themes/dropdown_menu.rb +108 -0
  198. data/lib/kiso/themes/empty.rb +54 -0
  199. data/lib/kiso/themes/field.rb +76 -0
  200. data/lib/kiso/themes/field_group.rb +15 -0
  201. data/lib/kiso/themes/field_set.rb +32 -0
  202. data/lib/kiso/themes/input.rb +33 -0
  203. data/lib/kiso/themes/input_group.rb +39 -0
  204. data/lib/kiso/themes/kbd.rb +31 -0
  205. data/lib/kiso/themes/label.rb +16 -0
  206. data/lib/kiso/themes/nav.rb +27 -0
  207. data/lib/kiso/themes/pagination.rb +73 -0
  208. data/lib/kiso/themes/popover.rb +32 -0
  209. data/lib/kiso/themes/radio_group.rb +43 -0
  210. data/lib/kiso/themes/select.rb +78 -0
  211. data/lib/kiso/themes/separator.rb +8 -2
  212. data/lib/kiso/themes/shared.rb +51 -0
  213. data/lib/kiso/themes/stats_card.rb +26 -14
  214. data/lib/kiso/themes/switch.rb +56 -0
  215. data/lib/kiso/themes/table.rb +18 -15
  216. data/lib/kiso/themes/textarea.rb +33 -0
  217. data/lib/kiso/themes/toggle.rb +71 -0
  218. data/lib/kiso/themes/toggle_group.rb +13 -0
  219. data/lib/kiso/version.rb +4 -1
  220. data/lib/kiso.rb +68 -2
  221. metadata +174 -22
  222. data/app/views/kiso/components/empty_state/_content.html.erb +0 -7
  223. data/app/views/kiso/components/empty_state/_description.html.erb +0 -7
  224. data/app/views/kiso/components/empty_state/_header.html.erb +0 -7
  225. data/app/views/kiso/components/empty_state/_media.html.erb +0 -7
  226. data/app/views/kiso/components/empty_state/_title.html.erb +0 -7
  227. data/lib/kiso/themes/empty_state.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cd911512ec3d322db2ec1fd6ff2a24660fbc28e654424e710007e77a2e1317b
4
- data.tar.gz: 8506e424c434844d10dd1dd7899ace376a65119b22e4602ea1ded3280ea85e86
3
+ metadata.gz: bda2256a79d7581462417a394433826ba6289cdab8f7f6bc04fbc451ee929478
4
+ data.tar.gz: a444b9108f532af991b690f8620faa3019639b533d9fd6ccda9f1aabca3b4540
5
5
  SHA512:
6
- metadata.gz: ffa23334590c70c3576bba3f3cbab806d4965d1c673e12652f1c976f3a213d02a88fa568a2773d62a484eae7af9a4a62b018c3eed8fd04ecb383f7e122274b0f
7
- data.tar.gz: 371226d21fc79972ff6b7fc222f59b7fa81a5a3fc660cc6f73bb2bfc5986a22ba3200d05a360551c249583b33930f0370e899bb6af3bd8497335da4d80eddd39
6
+ metadata.gz: 1cc8613ffb244b57a7499bcb4b4f7e4c3ec2c3c9feb8b8e6235cb55c9288cf0ea40261598424e33c221055577a0d8eeb9ec229aa18e9e0fcb20796551d5985c7
7
+ data.tar.gz: 93f2dee31a6a6526b7d6548b394a37eaa6ccca2bed6fa47b331664bfcb9a233bb466e41f9b5d4d1cdb8a029dcf85a46ddfb9286cb304165dc3420e7bcabd5ac3
data/CHANGELOG.md CHANGED
@@ -7,11 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.1.pre] - 2026-03-03
11
+
12
+ ### Added
13
+
14
+ - Dashboard layout system — sidebar, navbar, panel, toolbar, and nav components with cookie-persisted sidebar state
15
+ - Avatar component with image, fallback, badge, and group support
16
+ - Form components — Field, Label, Input, Textarea, InputGroup, Checkbox, RadioGroup, Switch, Select, Combobox
17
+ - Overlay components — Popover, DropdownMenu, Command palette
18
+ - Navigation components — Breadcrumb, Pagination
19
+ - Element components — Kbd, Toggle, ToggleGroup
20
+ - Dark mode system — `kiso_theme_script` helper, ColorModeButton, ColorModeSelect
21
+ - Floating UI positioning for popovers and dropdowns
22
+ - Global theme overrides via `Kiso.configure`
23
+ - Configurable default icons via `kiso_component_icon`
24
+ - Getting Started guide
25
+
26
+ ### Changed
27
+
28
+ - Renamed `kiso()` helper to `kui()` to avoid Rails route proxy collision
29
+ - Renamed `empty_state` to `empty` to match shadcn naming
30
+ - Adopted `data-slot` convention from shadcn v4
31
+
10
32
  ## [0.1.0.pre] - 2026-02-25
11
33
 
12
34
  ### Added
13
35
 
14
- - Core engine with `kiso()` component helper and `component_tag` builder
36
+ - Core engine with `kui()` component helper and `kiso_prepare_options` builder
15
37
  - `class_variants` + `tailwind_merge` integration for variant definitions
16
38
  - Theme CSS with 7 palettes, surface tokens, and dark mode
17
39
  - Badge component (color × variant × size, pill shape, SVG handling)
@@ -23,5 +45,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
45
  - Lookbook component previews
24
46
  - Bridgetown documentation site
25
47
 
26
- [Unreleased]: https://github.com/steveclarke/kiso/compare/v0.1.0.pre...HEAD
48
+ [Unreleased]: https://github.com/steveclarke/kiso/compare/v0.1.1.pre...HEAD
49
+ [0.1.1.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.1.1.pre
27
50
  [0.1.0.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.1.0.pre
data/README.md CHANGED
@@ -2,13 +2,16 @@
2
2
 
3
3
  UI components for Rails. Built on ERB, Tailwind CSS, and Hotwire.
4
4
 
5
- Add one gem and get badges, buttons, cards, alerts, and more. They all work with screen readers and dark mode. The look follows [shadcn/ui](https://ui.shadcn.com).
5
+ Add one gem and get badges, buttons, cards, alerts, and more. They all work with screen readers and dark mode.
6
6
 
7
7
  No React. No extra build step. Just ERB with [class_variants](https://github.com/avo-hq/class_variants) for styling.
8
8
 
9
- Icons come from the [kiso-icons](https://github.com/steveclarke/kiso-icons) gem, which is bundled in.
9
+ > [!WARNING]
10
+ > Kiso is in early development and **not ready for production use**. The gem is published to reserve the name on RubyGems. APIs, component names, and theme tokens will change without notice. Watch the repo or check back in a few weeks.
10
11
 
11
- ## Installation
12
+ ## Getting started
13
+
14
+ Add the gem to your Gemfile and bundle:
12
15
 
13
16
  ```ruby
14
17
  # Gemfile
@@ -17,36 +20,70 @@ gem "kiso"
17
20
 
18
21
  ```bash
19
22
  bundle install
20
- bin/rails generate kiso:install
21
23
  ```
22
24
 
25
+ Then add one import to your Tailwind CSS entrypoint:
26
+
27
+ ```css
28
+ /* app/assets/tailwind/application.css */
29
+ @import "../builds/tailwind/kiso.css";
30
+ ```
31
+
32
+ And add the dark mode helper to your layout's `<head>`:
33
+
34
+ ```erb
35
+ <head>
36
+ <%= kiso_theme_script %>
37
+ <%= stylesheet_link_tag "tailwind" %>
38
+ <%= javascript_importmap_tags %>
39
+ </head>
40
+
41
+ <body class="bg-background text-foreground antialiased">
42
+ ```
43
+
44
+ That's it. Helpers, importmap pins, asset paths, and dark mode tokens are all
45
+ wired up automatically by the engine. See the
46
+ [Getting Started guide](https://kisoui.com/getting-started) for the full
47
+ walkthrough.
48
+
49
+ ### Bundler apps (esbuild, Vite, Bun)
50
+
51
+ If your app uses a JS bundler instead of importmaps, also install the npm
52
+ package for Stimulus controllers:
53
+
54
+ ```bash
55
+ npm install kiso-ui
56
+ ```
57
+
58
+ ```js
59
+ // app/javascript/controllers/index.js
60
+ import KisoUi from "kiso-ui"
61
+ KisoUi.start(application)
62
+ ```
63
+
64
+ Importmap apps get Stimulus controllers automatically — no npm install needed.
65
+
23
66
  ## Usage
24
67
 
25
- Use the `kiso()` helper to render components:
68
+ Use the `kui()` helper to render components:
26
69
 
27
70
  ```erb
28
- <%= kiso(:badge, variant: :primary) { "Active" } %>
71
+ <%= kui(:badge, color: :primary) { "Active" } %>
29
72
  ```
30
73
 
31
74
  Components are made of small parts. A card has a header, title, content, and footer:
32
75
 
33
76
  ```erb
34
- <%= kiso(:card) do %>
35
- <%= kiso(:card, :header) do %>
36
- <%= kiso(:card, :title, text: "Members") %>
77
+ <%= kui(:card) do %>
78
+ <%= kui(:card, :header) do %>
79
+ <%= kui(:card, :title, text: "Members") %>
37
80
  <% end %>
38
- <%= kiso(:card, :content) do %>
81
+ <%= kui(:card, :content) do %>
39
82
  ...
40
83
  <% end %>
41
84
  <% end %>
42
85
  ```
43
86
 
44
- Data attributes work on any HTML element too:
45
-
46
- ```erb
47
- <%= f.submit "Save", data: { component: "button", variant: "primary" } %>
48
- ```
49
-
50
87
  ## How it works
51
88
 
52
89
  Each component has two parts:
@@ -56,6 +93,10 @@ Each component has two parts:
56
93
 
57
94
  Colors use tokens like `bg-primary` and `text-muted`. They switch on their own in dark mode. No `dark:` classes needed.
58
95
 
96
+ ## Design system
97
+
98
+ Kiso follows a strict spatial system — consistent heights, padding, gaps, typography, border radius, and icon sizing across every component. See the [Design System](https://kisoui.com/design-system) page for the visual reference.
99
+
59
100
  ## Design principles
60
101
 
61
102
  1. **Native HTML first.** Use `<dialog>`, `[popover]`, `<details>` before adding JavaScript.
@@ -79,11 +120,14 @@ This starts [Lookbook](https://lookbook.build) on port 4001 with a Tailwind watc
79
120
 
80
121
  Cloned without `--recurse-submodules`? Run `bin/vendor init` to fetch the vendor repos.
81
122
 
82
- Run tests:
123
+ Run tests and lint:
83
124
 
84
125
  ```bash
85
- bundle exec rake test # all tests
86
- bundle exec standardrb # lint
126
+ bundle exec rake test # Ruby tests
127
+ npm run test:unit # JS unit tests (Vitest)
128
+ npm run test:e2e # E2E tests (Playwright, needs bin/dev)
129
+ bundle exec standardrb # Ruby lint
130
+ npm run lint && npm run fmt:check # JS lint + format check
87
131
  ```
88
132
 
89
133
  See [CONTRIBUTING.md](CONTRIBUTING.md) to help out.
@@ -93,12 +137,10 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) to help out.
93
137
  ```
94
138
  app/views/kiso/components/ ERB partials
95
139
  lib/kiso/themes/ Theme files (class_variants)
96
- app/helpers/kiso/ component_tag, kiso() helpers
97
- app/assets/stylesheets/kiso/ CSS (only transitions and pseudo-states)
98
- test/components/previews/ Lookbook previews
99
- test/dummy/ Dev Rails app
100
- vendor/shadcn-ui/ Layout reference (git submodule)
101
- vendor/nuxt-ui/ Theme reference (git submodule)
140
+ app/javascript/controllers/ Stimulus controllers (also shipped via npm as kiso-ui)
141
+ app/helpers/kiso/ kui(), kiso_prepare_options() helpers
142
+ app/assets/tailwind/kiso/ CSS (only transitions and pseudo-states)
143
+ lookbook/ Lookbook dev app (previews on port 4001)
102
144
  docs/ Docs site (Bridgetown)
103
145
  ```
104
146
 
@@ -106,11 +148,11 @@ docs/ Docs site (Bridgetown)
106
148
 
107
149
  - Ruby >= 3.3
108
150
  - Rails >= 8.0
109
- - [tailwindcss-rails](https://github.com/rails/tailwindcss-rails)
151
+ - A Tailwind CSS build pipeline ([tailwindcss-rails](https://github.com/rails/tailwindcss-rails) or [cssbundling-rails](https://github.com/rails/cssbundling-rails))
110
152
 
111
153
  ## Status
112
154
 
113
- Early development. See [VISION.md](VISION.md) for the roadmap and full component list.
155
+ Early development. APIs, component names, and theme tokens may change.
114
156
 
115
157
  ## License
116
158
 
data/Rakefile CHANGED
@@ -1,5 +1,13 @@
1
1
  require "bundler/setup"
2
2
  require "bundler/gem_tasks"
3
+ require "rake/testtask"
3
4
 
4
5
  APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
5
6
  load "rails/tasks/engine.rake"
7
+
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << "test"
10
+ t.pattern = "test/**/*_test.rb"
11
+ end
12
+
13
+ task default: :test
@@ -0,0 +1,18 @@
1
+ /* Checkbox indicator — CSS ::after for the checkmark since native <input>
2
+ can't contain child elements. Uses mask-image with the Lucide Check icon. */
3
+
4
+ [data-component="checkbox"] {
5
+ display: grid;
6
+ place-content: center;
7
+ }
8
+
9
+ [data-component="checkbox"]:checked::after {
10
+ content: '';
11
+ width: 0.875em;
12
+ height: 0.875em;
13
+ background-color: currentColor;
14
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
15
+ mask-size: contain;
16
+ mask-repeat: no-repeat;
17
+ mask-position: center;
18
+ }
@@ -0,0 +1,9 @@
1
+ /* ── Color Mode Components ────────────────────────────────────────────────────
2
+ Icon visibility for the color mode button. The .dark class on <html> drives
3
+ which icon is shown — sun in light mode, moon in dark mode. */
4
+
5
+ @layer components {
6
+ [data-slot="color-mode-icon-dark"] { display: none; }
7
+ .dark [data-slot="color-mode-icon-light"] { display: none; }
8
+ .dark [data-slot="color-mode-icon-dark"] { display: inline-block; }
9
+ }
@@ -0,0 +1,176 @@
1
+ /* ── Kiso Dashboard Layout ────────────────────────────────────────────────────
2
+ Tokens and mechanics for the full-screen sidebar + topbar dashboard shell.
3
+ Bundled with the engine — all apps get these tokens and layout rules.
4
+
5
+ Host apps can override any token in their own @theme block:
6
+ @theme { --sidebar-width: 18rem; }
7
+ */
8
+
9
+ /* ── Tokens ──────────────────────────────────────────────────────────────── */
10
+
11
+ @theme {
12
+ /* Sidebar geometry */
13
+ --sidebar-width: 16rem;
14
+
15
+ /* Layout heights */
16
+ --topbar-height: 3.5rem;
17
+
18
+ /* Sidebar surface tokens (light mode defaults) */
19
+ --sidebar-background: var(--color-white);
20
+ --sidebar-foreground: var(--color-zinc-900);
21
+ --sidebar-border: var(--color-zinc-200);
22
+ --sidebar-accent: var(--color-zinc-100);
23
+ --sidebar-accent-foreground: var(--color-zinc-700);
24
+
25
+ /* Animation */
26
+ --sidebar-duration: 220ms;
27
+ --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
28
+
29
+ /* Z-index stack */
30
+ --z-topbar: 30;
31
+ --z-sidebar: 40;
32
+ }
33
+
34
+ /* Dark mode sidebar token overrides */
35
+ .dark {
36
+ --sidebar-background: var(--color-zinc-950);
37
+ --sidebar-foreground: var(--color-zinc-100);
38
+ --sidebar-border: var(--color-zinc-800);
39
+ --sidebar-accent: var(--color-zinc-800);
40
+ --sidebar-accent-foreground: var(--color-zinc-300);
41
+ }
42
+
43
+ /* ── Layout mechanics ─────────────────────────────────────────────────────── */
44
+
45
+ @layer components {
46
+ /* Drive --sidebar-current-width from the data attribute set by sidebar controller */
47
+ [data-sidebar-open="true"] { --sidebar-current-width: var(--sidebar-width); }
48
+ [data-sidebar-open="false"] { --sidebar-current-width: 0rem; }
49
+
50
+ /* Hide sidebar border when collapsed to prevent ghost border at 0 width */
51
+ [data-sidebar-open="false"] [data-slot="dashboard-sidebar"] {
52
+ border-right-width: 0;
53
+ }
54
+
55
+ /* Flat 2×2 grid: topbar row + sidebar/panel row */
56
+ [data-slot="dashboard-group"] {
57
+ grid-template-rows: var(--topbar-height) 1fr;
58
+ grid-template-columns: var(--sidebar-current-width) 1fr;
59
+ transition: grid-template-columns var(--sidebar-duration) var(--ease-out-expo);
60
+ }
61
+
62
+ /* Topbar spans panel column only (sidebar owns full height) */
63
+ [data-slot="dashboard-navbar"] {
64
+ grid-column: 2;
65
+ }
66
+
67
+ /*
68
+ Sidebar: spans full height (both grid rows). background-color and
69
+ border-right-color are set here (not via Tailwind arbitrary classes)
70
+ because bg-[--css-var] classes don't generate reliably.
71
+ */
72
+ [data-slot="dashboard-sidebar"] {
73
+ grid-row: 1 / -1;
74
+ background-color: var(--sidebar-background);
75
+ border-right: 1px solid var(--sidebar-border);
76
+ }
77
+
78
+ /* Sidebar header: matches topbar height, bottom border */
79
+ [data-slot="dashboard-sidebar-header"] {
80
+ height: var(--topbar-height);
81
+ border-bottom: 1px solid var(--sidebar-border);
82
+ }
83
+
84
+ /* Sidebar footer: top border */
85
+ [data-slot="dashboard-sidebar-footer"] {
86
+ border-top: 1px solid var(--sidebar-border);
87
+ }
88
+
89
+ /* Inner nav: always sidebar-width, flex column for header/nav/footer layout */
90
+ [data-slot="dashboard-sidebar-inner"] {
91
+ width: var(--sidebar-width);
92
+ height: 100%;
93
+ overflow: hidden;
94
+ display: flex;
95
+ flex-direction: column;
96
+ color: var(--sidebar-foreground);
97
+ }
98
+
99
+ /* Panel: row 2, col 2 */
100
+ [data-slot="dashboard-panel"] {
101
+ grid-column: 2;
102
+ grid-row: 2;
103
+ }
104
+
105
+ /* ── Navbar layout variant: navbar spans full width, sidebar below ────── */
106
+ [data-layout="navbar"] [data-slot="dashboard-navbar"] {
107
+ grid-column: 1 / -1;
108
+ }
109
+ [data-layout="navbar"] [data-slot="dashboard-sidebar"] {
110
+ grid-row: auto;
111
+ }
112
+
113
+ /* Scrim: hidden on desktop */
114
+ [data-slot="dashboard-scrim"] { display: none; }
115
+
116
+ /* ── Collapse button icon switching ────────────────────────────────────── */
117
+ [data-sidebar-open="true"] [data-slot="collapse-icon-open"] { display: inline; }
118
+ [data-sidebar-open="true"] [data-slot="collapse-icon-closed"] { display: none; }
119
+ [data-sidebar-open="false"] [data-slot="collapse-icon-open"] { display: none; }
120
+ [data-sidebar-open="false"] [data-slot="collapse-icon-closed"] { display: inline; }
121
+
122
+ /* ── Nav section (details/summary) ───────────────────────────────────── */
123
+ [data-slot="nav-section-chevron"] {
124
+ transition: transform 200ms ease;
125
+ }
126
+ [data-slot="nav-section"][open] [data-slot="nav-section-chevron"] {
127
+ transform: rotate(180deg);
128
+ }
129
+
130
+ /* ── Nav item sidebar context ────────────────────────────────────────── */
131
+ [data-slot="dashboard-sidebar-inner"] [data-slot="nav-item"]:hover {
132
+ background-color: var(--sidebar-accent);
133
+ color: var(--sidebar-accent-foreground);
134
+ }
135
+ [data-slot="dashboard-sidebar-inner"] [data-slot="nav-item"][data-active="true"] {
136
+ background-color: var(--sidebar-accent);
137
+ color: var(--sidebar-accent-foreground);
138
+ }
139
+
140
+ /* ── Mobile: sidebar becomes a fixed full-width overlay ───────────────── */
141
+ @media (max-width: 767px) {
142
+ /* Collapse sidebar column to 0 on mobile */
143
+ [data-slot="dashboard-group"] {
144
+ grid-template-columns: 0 1fr;
145
+ }
146
+
147
+ /* Sidebar switches from grid column to fixed overlay */
148
+ [data-slot="dashboard-sidebar"] {
149
+ position: fixed;
150
+ inset-block: 0;
151
+ inset-inline-start: 0;
152
+ z-index: var(--z-sidebar);
153
+ width: 100dvw;
154
+ transform: translateX(-100dvw);
155
+ transition: transform var(--sidebar-duration) var(--ease-out-expo);
156
+ }
157
+
158
+ [data-sidebar-open="true"] [data-slot="dashboard-sidebar"] {
159
+ transform: translateX(0);
160
+ }
161
+
162
+ /* Inner nav fills the full-width sidebar on mobile */
163
+ [data-slot="dashboard-sidebar-inner"] {
164
+ width: 100dvw;
165
+ }
166
+
167
+ /* Scrim: appears behind the sidebar overlay on mobile */
168
+ [data-sidebar-open="true"] [data-slot="dashboard-scrim"] {
169
+ display: block;
170
+ position: fixed;
171
+ inset: 0;
172
+ z-index: calc(var(--z-sidebar) - 1);
173
+ background: oklch(0% 0 0 / 40%);
174
+ }
175
+ }
176
+ }
@@ -3,6 +3,18 @@
3
3
  that are awkward to express in ERB. Most styling lives in Ruby theme modules
4
4
  (lib/kiso/themes/) as computed Tailwind classes. */
5
5
 
6
+ @import "./checkbox.css";
7
+ @import "./radio-group.css";
8
+ @import "./color-mode.css";
9
+ @import "./dashboard.css";
10
+
11
+ /* Scan Kiso's own files so host apps don't need to know the gem's internals.
12
+ Paths are relative to THIS file (app/assets/tailwind/kiso/engine.css).
13
+ Count carefully — wrong paths fail SILENTLY (classes just won't generate). */
14
+ @source "../../../views"; /* → app/views */
15
+ @source "../../../helpers"; /* → app/helpers */
16
+ @source "../../../../lib/kiso/themes"; /* → lib/kiso/themes */
17
+
6
18
  /* === Geist Font (by Vercel) ===
7
19
  Self-hosted variable fonts — no CDN dependency.
8
20
  Licensed under the SIL Open Font License (see OFL.txt).
@@ -30,3 +42,94 @@
30
42
  --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
31
43
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
32
44
  }
45
+
46
+ /* === Semantic Color Tokens ===
47
+ Purpose-named tokens that flip in dark mode. Components use bg-primary,
48
+ text-foreground, etc. — never raw palette shades or dark: prefixes.
49
+
50
+ Override any token in your app's CSS to retheme:
51
+ @theme { --color-primary: var(--color-violet-600); }
52
+ */
53
+
54
+ @theme {
55
+ /* Brand / action colors */
56
+ --color-primary: var(--color-sky-600);
57
+ --color-primary-foreground: white;
58
+
59
+ --color-secondary: var(--color-teal-600);
60
+ --color-secondary-foreground: white;
61
+
62
+ --color-success: var(--color-green-600);
63
+ --color-success-foreground: white;
64
+
65
+ --color-info: var(--color-sky-600);
66
+ --color-info-foreground: white;
67
+
68
+ --color-warning: var(--color-amber-500);
69
+ --color-warning-foreground: var(--color-amber-950);
70
+
71
+ --color-error: var(--color-red-600);
72
+ --color-error-foreground: white;
73
+
74
+ /* Surface tokens */
75
+ --color-background: white;
76
+ --color-foreground: var(--color-zinc-950);
77
+
78
+ --color-muted: var(--color-zinc-100);
79
+ --color-muted-foreground: var(--color-zinc-500);
80
+
81
+ --color-accent: var(--color-zinc-100);
82
+ --color-accent-foreground: var(--color-zinc-900);
83
+
84
+ --color-inverted: var(--color-zinc-900);
85
+ --color-inverted-foreground: white;
86
+ --color-elevated: var(--color-zinc-100);
87
+ --color-accented: var(--color-zinc-300);
88
+
89
+ --color-border: var(--color-zinc-200);
90
+ --color-border-accented: var(--color-zinc-300);
91
+
92
+ --color-ring: var(--color-zinc-400);
93
+ }
94
+
95
+ /* === Dark Mode ===
96
+ Redefine tokens under .dark — components never use dark: prefixes. */
97
+
98
+ .dark {
99
+ --color-primary: var(--color-blue-400);
100
+ --color-primary-foreground: var(--color-zinc-950);
101
+
102
+ --color-secondary: var(--color-teal-400);
103
+ --color-secondary-foreground: var(--color-zinc-950);
104
+
105
+ --color-success: var(--color-green-400);
106
+ --color-success-foreground: var(--color-zinc-950);
107
+
108
+ --color-info: var(--color-sky-400);
109
+ --color-info-foreground: var(--color-zinc-950);
110
+
111
+ --color-warning: var(--color-amber-400);
112
+ --color-warning-foreground: var(--color-zinc-950);
113
+
114
+ --color-error: var(--color-red-400);
115
+ --color-error-foreground: var(--color-zinc-950);
116
+
117
+ --color-background: var(--color-zinc-950);
118
+ --color-foreground: var(--color-zinc-50);
119
+
120
+ --color-muted: var(--color-zinc-800);
121
+ --color-muted-foreground: var(--color-zinc-400);
122
+
123
+ --color-accent: var(--color-zinc-800);
124
+ --color-accent-foreground: var(--color-zinc-50);
125
+
126
+ --color-inverted: white;
127
+ --color-inverted-foreground: var(--color-zinc-950);
128
+ --color-elevated: var(--color-zinc-800);
129
+ --color-accented: var(--color-zinc-700);
130
+
131
+ --color-border: var(--color-zinc-800);
132
+ --color-border-accented: var(--color-zinc-700);
133
+
134
+ --color-ring: var(--color-zinc-600);
135
+ }
@@ -0,0 +1,17 @@
1
+ /* RadioGroupItem indicator — CSS ::after for the filled circle dot since
2
+ native <input type="radio"> can't contain child elements. Uses currentColor
3
+ which inherits from checked:text-{color}-foreground set by compound variants.
4
+ Follows the same pattern as Checkbox (mask-image with currentColor). */
5
+
6
+ [data-radio-group-part="item"] {
7
+ display: grid;
8
+ place-content: center;
9
+ }
10
+
11
+ [data-radio-group-part="item"]:checked::after {
12
+ content: '';
13
+ width: 0.5em;
14
+ height: 0.5em;
15
+ border-radius: 9999px;
16
+ background-color: currentColor;
17
+ }
@@ -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