kiso 0.4.2.pre → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e7e4247e68272499ab572c3aa2ed94ad54860cf075f0b4e3240a1b11fd909d6
4
- data.tar.gz: 87e9710cb6428055adad01d89a4357e735ac5c8b326c547b01d696d208501cf8
3
+ metadata.gz: 4bf4475ba64978e58e6321ac224b3912cf463dae75462ee3372f86faf4b69799
4
+ data.tar.gz: ee8c23fdd9a81b0b4abd8a097371a4720b8327e2c719c6b1a5cb84ea21a22962
5
5
  SHA512:
6
- metadata.gz: 9f0842c23bdf92f06c826614ac8eb81d427eb136ea5fa2d6f735886387790cccda7943d450e8e771a7405cb9d6017eb2c1a5b00386b499b2c079ff933b520ec2
7
- data.tar.gz: 1322bed3abb54da45bb4163e7fa5ca5919efbb6af07f505606dc17e65ac100f5fbc6d438e336347bdb44a09f70a2a7f3c413df876ace869adcd2b7a758ee725a
6
+ metadata.gz: abdb896617cba586d6906624bb2e30434ad40ccd2c1a12d589284ca43149056724c4ecb6dad7cb4a27915f3dad846e64b2a12bc3efa5a0d0fe82378aaf6494ad
7
+ data.tar.gz: 00d0ce8bd9933e5f3500d3ac0dfa57a068ae8c3923371943e398122f179e508fe5c7bd39b308df15a92eda836e50a41693be5e295e5a7c1bd3f6aea1bf2a57af
@@ -0,0 +1,10 @@
1
+ /* Default display for buttons — lives in @layer components so utility classes
2
+ (including `hidden`) override via the higher-priority utilities layer. When
3
+ a utility like `hidden` is removed via JS, this rule restores inline-flex.
4
+ Fixes: https://github.com/steveclarke/kiso/issues/200 */
5
+
6
+ @layer components {
7
+ [data-slot="button"] {
8
+ display: inline-flex;
9
+ }
10
+ }
@@ -3,6 +3,7 @@
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 "./button.css";
6
7
  @import "./checkbox.css";
7
8
  @import "./radio-group.css";
8
9
  @import "./color-mode.css";
@@ -10,6 +11,7 @@
10
11
  @import "./dialog.css";
11
12
  @import "./input-otp.css";
12
13
  @import "./slider.css";
14
+ @import "./tooltip.css";
13
15
 
14
16
  /* Scan Kiso's own files so host apps don't need to know the gem's internals.
15
17
  Paths are relative to THIS file (app/assets/tailwind/kiso/engine.css).
@@ -0,0 +1,48 @@
1
+ /* Tooltip entry/exit animations and arrow positioning. */
2
+
3
+ /* Tooltip root — inline-flex via @layer components so utilities can override */
4
+ @layer components {
5
+ [data-slot="tooltip"] {
6
+ display: inline-flex;
7
+ }
8
+ }
9
+
10
+ /* The browser UA stylesheet hides [popover]:not(:popover-open) with
11
+ display: none, but Tailwind's `flex` utility (author layer) overrides it.
12
+ Explicitly enforce hidden state for non-open popovers. */
13
+ [data-slot="tooltip-content"]:not(:popover-open) {
14
+ display: none;
15
+ }
16
+
17
+ /* Reset UA popover styles — browsers apply inset: 0, margin: auto, and
18
+ width/height: fit-content to [popover]:popover-open, which overrides
19
+ Floating UI's inline-style positioning. */
20
+ [data-slot="tooltip-content"] {
21
+ inset: unset;
22
+ margin: 0;
23
+ }
24
+
25
+ /* Entry animation — fade + scale from 95% */
26
+ [data-slot="tooltip-content"][data-state="open"] {
27
+ animation: kiso-tooltip-in 150ms ease-out;
28
+ }
29
+
30
+ /* Exit animation — fade out + scale to 95% */
31
+ [data-slot="tooltip-content"][data-state="closed"] {
32
+ animation: kiso-tooltip-out 100ms ease-in forwards;
33
+ }
34
+
35
+ @keyframes kiso-tooltip-in {
36
+ from { opacity: 0; transform: scale(0.95); }
37
+ }
38
+ @keyframes kiso-tooltip-out {
39
+ to { opacity: 0; transform: scale(0.95); }
40
+ }
41
+
42
+ /* Respect reduced motion */
43
+ @media (prefers-reduced-motion: reduce) {
44
+ [data-slot="tooltip-content"][data-state="open"],
45
+ [data-slot="tooltip-content"][data-state="closed"] {
46
+ animation: none;
47
+ }
48
+ }
@@ -102,10 +102,17 @@ module Kiso
102
102
  # yield
103
103
  # end
104
104
  def kui_tag(tag, theme:, slot:, css_classes: "", variants: {}, **component_options, &block)
105
- content_tag(tag,
105
+ html_options = {
106
106
  class: theme.render(**variants, class: css_classes),
107
107
  data: kiso_prepare_options(component_options, slot: slot),
108
- **component_options, &block)
108
+ **component_options
109
+ }
110
+
111
+ if block
112
+ content_tag(tag, html_options, &block)
113
+ else
114
+ content_tag(tag, nil, html_options)
115
+ end
109
116
  end
110
117
 
111
118
  private
@@ -172,6 +179,10 @@ module Kiso
172
179
  # Push context for composed sub-parts to read (skip when empty)
173
180
  kiso_push_ui_context(component, merged_ui) if has_ui
174
181
  kiso_push_scope(component, scope) if has_scope
182
+
183
+ # Merge scope into parent kwargs so the parent partial receives
184
+ # scope values as strict locals (scope as defaults, explicit kwargs win)
185
+ kwargs = scope.merge(kwargs) if has_scope
175
186
  begin
176
187
  locals = has_ui ? kwargs.merge(ui: merged_ui) : kwargs
177
188
 
@@ -0,0 +1,80 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ /**
4
+ * Opens or closes a dialog from anywhere on the page by ID.
5
+ *
6
+ * Use when the trigger button lives outside the dialog's DOM scope
7
+ * (e.g., a toolbar button opening a confirmation dialog rendered
8
+ * elsewhere on the page).
9
+ *
10
+ * Works with both `kui(:dialog)` and `kui(:alert_dialog)` since
11
+ * both use the `kiso--dialog` Stimulus controller internally.
12
+ *
13
+ * @example
14
+ * <!-- Dialog rendered anywhere -->
15
+ * <dialog id="invite-dialog" data-controller="kiso--dialog" ...>
16
+ * ...
17
+ * </dialog>
18
+ *
19
+ * <!-- Trigger from a toolbar, sidebar, or any other scope -->
20
+ * <button data-controller="kiso--dialog-trigger"
21
+ * data-kiso--dialog-trigger-dialog-id-value="invite-dialog"
22
+ * data-action="kiso--dialog-trigger#open">
23
+ * Invite
24
+ * </button>
25
+ *
26
+ * @property {string} dialogIdValue - The DOM id of the target dialog element
27
+ *
28
+ * @fires kiso--dialog:open - Forwarded from the target dialog controller
29
+ * @fires kiso--dialog:close - Forwarded from the target dialog controller
30
+ */
31
+ export default class extends Controller {
32
+ static values = {
33
+ dialogId: String,
34
+ }
35
+
36
+ /**
37
+ * Opens the target dialog.
38
+ */
39
+ open() {
40
+ this._withDialogController((controller) => controller.open())
41
+ }
42
+
43
+ /**
44
+ * Closes the target dialog.
45
+ */
46
+ close() {
47
+ this._withDialogController((controller) => controller.close())
48
+ }
49
+
50
+ /**
51
+ * Toggles the target dialog open or closed.
52
+ */
53
+ toggle() {
54
+ this._withDialogController((controller) => {
55
+ if (controller.element.open) {
56
+ controller.close()
57
+ } else {
58
+ controller.open()
59
+ }
60
+ })
61
+ }
62
+
63
+ /**
64
+ * Finds the dialog element and its Stimulus controller, then
65
+ * calls the provided callback with the controller instance.
66
+ *
67
+ * @param {function} callback - Receives the dialog controller
68
+ * @private
69
+ */
70
+ _withDialogController(callback) {
71
+ const dialog = document.getElementById(this.dialogIdValue)
72
+ if (!dialog) return
73
+
74
+ const controller = this.application.getControllerForElementAndIdentifier(dialog, "kiso--dialog")
75
+
76
+ if (controller) {
77
+ callback(controller)
78
+ }
79
+ }
80
+ }
@@ -5,8 +5,19 @@ declare const KisoUi: {
5
5
  }
6
6
 
7
7
  export default KisoUi
8
+ export const KisoAlertController: typeof Controller
8
9
  export const KisoComboboxController: typeof Controller
10
+ export const KisoCommandController: typeof Controller
11
+ export const KisoCommandDialogController: typeof Controller
12
+ export const KisoDialogController: typeof Controller
13
+ export const KisoDialogTriggerController: typeof Controller
9
14
  export const KisoDropdownMenuController: typeof Controller
15
+ export const KisoInputOtpController: typeof Controller
16
+ export const KisoPopoverController: typeof Controller
10
17
  export const KisoSelectController: typeof Controller
18
+ export const KisoSidebarController: typeof Controller
19
+ export const KisoSliderController: typeof Controller
20
+ export const KisoThemeController: typeof Controller
11
21
  export const KisoToggleController: typeof Controller
12
22
  export const KisoToggleGroupController: typeof Controller
23
+ export const KisoTooltipController: typeof Controller
@@ -3,14 +3,17 @@ import KisoComboboxController from "./combobox_controller.js"
3
3
  import KisoCommandController from "./command_controller.js"
4
4
  import KisoCommandDialogController from "./command_dialog_controller.js"
5
5
  import KisoDialogController from "./dialog_controller.js"
6
+ import KisoDialogTriggerController from "./dialog_trigger_controller.js"
6
7
  import KisoDropdownMenuController from "./dropdown_menu_controller.js"
7
8
  import KisoInputOtpController from "./input_otp_controller.js"
8
9
  import KisoPopoverController from "./popover_controller.js"
9
10
  import KisoSelectController from "./select_controller.js"
10
11
  import KisoSidebarController from "./sidebar_controller.js"
12
+ import KisoSliderController from "./slider_controller.js"
11
13
  import KisoThemeController from "./theme_controller.js"
12
14
  import KisoToggleController from "./toggle_controller.js"
13
15
  import KisoToggleGroupController from "./toggle_group_controller.js"
16
+ import KisoTooltipController from "./tooltip_controller.js"
14
17
 
15
18
  const KisoUi = {
16
19
  start(application) {
@@ -19,14 +22,17 @@ const KisoUi = {
19
22
  application.register("kiso--command", KisoCommandController)
20
23
  application.register("kiso--command-dialog", KisoCommandDialogController)
21
24
  application.register("kiso--dialog", KisoDialogController)
25
+ application.register("kiso--dialog-trigger", KisoDialogTriggerController)
22
26
  application.register("kiso--dropdown-menu", KisoDropdownMenuController)
23
27
  application.register("kiso--input-otp", KisoInputOtpController)
24
28
  application.register("kiso--popover", KisoPopoverController)
25
29
  application.register("kiso--select", KisoSelectController)
26
30
  application.register("kiso--sidebar", KisoSidebarController)
31
+ application.register("kiso--slider", KisoSliderController)
27
32
  application.register("kiso--theme", KisoThemeController)
28
33
  application.register("kiso--toggle", KisoToggleController)
29
34
  application.register("kiso--toggle-group", KisoToggleGroupController)
35
+ application.register("kiso--tooltip", KisoTooltipController)
30
36
  },
31
37
  }
32
38
 
@@ -37,12 +43,15 @@ export {
37
43
  KisoCommandController,
38
44
  KisoCommandDialogController,
39
45
  KisoDialogController,
46
+ KisoDialogTriggerController,
40
47
  KisoDropdownMenuController,
41
48
  KisoInputOtpController,
42
49
  KisoPopoverController,
43
50
  KisoSelectController,
44
51
  KisoSidebarController,
52
+ KisoSliderController,
45
53
  KisoThemeController,
46
54
  KisoToggleController,
47
55
  KisoToggleGroupController,
56
+ KisoTooltipController,
48
57
  }
@@ -0,0 +1,212 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { startPositioning } from "kiso-ui/utils/positioning"
3
+
4
+ /**
5
+ * Tooltip controller — shows a floating tooltip on hover or keyboard focus.
6
+ * Uses Floating UI for positioning and the native `[popover]` attribute for
7
+ * top-layer rendering (no z-index issues).
8
+ *
9
+ * @example
10
+ * <div data-controller="kiso--tooltip"
11
+ * data-kiso--tooltip-side-value="top"
12
+ * data-kiso--tooltip-align-value="center"
13
+ * data-kiso--tooltip-delay-value="0"
14
+ * data-slot="tooltip">
15
+ * <div data-kiso--tooltip-target="trigger"
16
+ * data-action="mouseenter->kiso--tooltip#show mouseleave->kiso--tooltip#hide
17
+ * focusin->kiso--tooltip#show focusout->kiso--tooltip#hide"
18
+ * data-slot="tooltip-trigger">
19
+ * <button type="button">Hover me</button>
20
+ * </div>
21
+ * <div data-kiso--tooltip-target="content"
22
+ * data-slot="tooltip-content"
23
+ * popover="manual"
24
+ * role="tooltip">
25
+ * Tooltip text
26
+ * <div data-kiso--tooltip-target="arrow" data-slot="tooltip-arrow"></div>
27
+ * </div>
28
+ * </div>
29
+ *
30
+ * @property {HTMLElement} triggerTarget - Element that activates the tooltip on hover/focus
31
+ * @property {HTMLElement} contentTarget - The floating tooltip panel (popover)
32
+ * @property {HTMLElement} arrowTarget - Arrow element positioned by Floating UI
33
+ *
34
+ * @fires kiso--tooltip:show - When the tooltip becomes visible
35
+ * @fires kiso--tooltip:hide - When the tooltip is hidden
36
+ */
37
+ export default class extends Controller {
38
+ static targets = ["trigger", "content", "arrow"]
39
+ static values = {
40
+ side: { type: String, default: "top" },
41
+ align: { type: String, default: "center" },
42
+ delay: { type: Number, default: 0 },
43
+ }
44
+
45
+ connect() {
46
+ this._open = false
47
+
48
+ // Link trigger to tooltip content via aria-describedby
49
+ this._triggerEl =
50
+ this.triggerTarget.querySelector("button, a, [tabindex]") || this.triggerTarget
51
+ this._tooltipId = this.contentTarget.id || `tooltip-${crypto.randomUUID().slice(0, 8)}`
52
+ this.contentTarget.id = this._tooltipId
53
+ this._triggerEl.setAttribute("aria-describedby", this._tooltipId)
54
+ }
55
+
56
+ disconnect() {
57
+ this._cleanupPosition?.()
58
+ this._clearTimers()
59
+ }
60
+
61
+ /**
62
+ * Shows the tooltip after the configured delay.
63
+ * Called on mouseenter and focusin on the trigger.
64
+ */
65
+ show() {
66
+ this._clearHideTimeout()
67
+
68
+ if (this._open) return
69
+
70
+ const delay = this.delayValue
71
+ if (delay > 0) {
72
+ this._showTimeout = setTimeout(() => this._doShow(), delay)
73
+ } else {
74
+ this._doShow()
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Hides the tooltip with a small delay to allow the mouse to cross
80
+ * the gap between trigger and tooltip content.
81
+ * Called on mouseleave and focusout on the trigger.
82
+ */
83
+ hide() {
84
+ this._clearShowTimeout()
85
+
86
+ if (!this._open) return
87
+
88
+ // Small delay lets the mouse cross the offset gap to the content
89
+ this._hideTimeout = setTimeout(() => this._doHide(), 100)
90
+ }
91
+
92
+ /**
93
+ * Cancels a pending hide when the mouse enters the tooltip content.
94
+ * Keeps the tooltip visible while the user reads it.
95
+ */
96
+ contentMouseEnter() {
97
+ this._clearHideTimeout()
98
+ }
99
+
100
+ /**
101
+ * Hides the tooltip when the mouse leaves the tooltip content.
102
+ */
103
+ contentMouseLeave() {
104
+ this._doHide()
105
+ }
106
+
107
+ // --- Private ---
108
+
109
+ /**
110
+ * Shows the tooltip immediately: opens the popover, positions it,
111
+ * and triggers the entry animation.
112
+ *
113
+ * @private
114
+ */
115
+ _doShow() {
116
+ if (this._open) return
117
+ this._open = true
118
+
119
+ this.contentTarget.showPopover()
120
+ this.contentTarget.setAttribute("data-state", "open")
121
+
122
+ const placement = this._buildPlacement()
123
+ const options = {
124
+ placement,
125
+ strategy: "fixed",
126
+ offset: 8,
127
+ }
128
+ if (this.hasArrowTarget) {
129
+ options.arrow = this.arrowTarget
130
+ }
131
+
132
+ this._cleanupPosition = startPositioning(this.triggerTarget, this.contentTarget, options)
133
+
134
+ this.dispatch("show")
135
+ }
136
+
137
+ /**
138
+ * Hides the tooltip: triggers exit animation, then hides the popover.
139
+ *
140
+ * @private
141
+ */
142
+ _doHide() {
143
+ if (!this._open) return
144
+ this._open = false
145
+
146
+ this._cleanupPosition?.()
147
+ this._cleanupPosition = null
148
+ this._clearTimers()
149
+
150
+ this.contentTarget.setAttribute("data-state", "closed")
151
+
152
+ // Hide after exit animation completes
153
+ this._animHandler = () => {
154
+ this.contentTarget.hidePopover()
155
+ this.contentTarget.removeEventListener("animationend", this._animHandler)
156
+ this._animHandler = null
157
+ }
158
+ this.contentTarget.addEventListener("animationend", this._animHandler)
159
+
160
+ // Fallback: hide immediately if no animation runs
161
+ this._closeTimeout = setTimeout(() => {
162
+ if (!this._open && this.contentTarget.matches(":popover-open")) {
163
+ this.contentTarget.hidePopover()
164
+ }
165
+ }, 150)
166
+
167
+ this.dispatch("hide")
168
+ }
169
+
170
+ /**
171
+ * Builds a Floating UI placement string from side and align values.
172
+ *
173
+ * @returns {string} e.g. "top", "top-start", "bottom-end"
174
+ * @private
175
+ */
176
+ _buildPlacement() {
177
+ const side = this.sideValue
178
+ const align = this.alignValue
179
+ if (align === "center") return side
180
+ return `${side}-${align}`
181
+ }
182
+
183
+ /** @private */
184
+ _clearShowTimeout() {
185
+ if (this._showTimeout) {
186
+ clearTimeout(this._showTimeout)
187
+ this._showTimeout = null
188
+ }
189
+ }
190
+
191
+ /** @private */
192
+ _clearHideTimeout() {
193
+ if (this._hideTimeout) {
194
+ clearTimeout(this._hideTimeout)
195
+ this._hideTimeout = null
196
+ }
197
+ }
198
+
199
+ /** @private */
200
+ _clearTimers() {
201
+ this._clearShowTimeout()
202
+ this._clearHideTimeout()
203
+ if (this._closeTimeout) {
204
+ clearTimeout(this._closeTimeout)
205
+ this._closeTimeout = null
206
+ }
207
+ if (this._animHandler) {
208
+ this.contentTarget.removeEventListener("animationend", this._animHandler)
209
+ this._animHandler = null
210
+ }
211
+ }
212
+ }
@@ -2,12 +2,20 @@
2
2
  * Shared positioning utilities for floating components.
3
3
  * Wraps Floating UI for smart positioning with flip, shift, and auto-update.
4
4
  *
5
- * Used by select, combobox, popover, and dropdown_menu controllers.
5
+ * Used by select, combobox, popover, dropdown_menu, and tooltip controllers.
6
6
  *
7
7
  * @module utils/positioning
8
8
  */
9
9
 
10
- import { autoUpdate, computePosition, flip, offset, shift, size } from "@floating-ui/dom"
10
+ import {
11
+ arrow as arrowMiddleware,
12
+ autoUpdate,
13
+ computePosition,
14
+ flip,
15
+ offset,
16
+ shift,
17
+ size,
18
+ } from "@floating-ui/dom"
11
19
 
12
20
  /**
13
21
  * Starts positioning a floating element relative to a reference element.
@@ -24,6 +32,7 @@ import { autoUpdate, computePosition, flip, offset, shift, size } from "@floatin
24
32
  * @param {number} [options.offset=4] - Pixel gap between reference and floating element
25
33
  * @param {"absolute"|"fixed"} [options.strategy="absolute"] - CSS positioning strategy
26
34
  * @param {boolean} [options.matchWidth=false] - Set floating element minWidth to reference width
35
+ * @param {HTMLElement|null} [options.arrow=null] - Arrow element to position via Floating UI arrow middleware
27
36
  * @returns {Function} Cleanup function — call on close or disconnect to remove listeners
28
37
  *
29
38
  * @example
@@ -49,6 +58,7 @@ export function startPositioning(reference, floating, options = {}) {
49
58
  offset: offsetDistance = 4,
50
59
  strategy = "absolute",
51
60
  matchWidth = false,
61
+ arrow: arrowElement = null,
52
62
  } = options
53
63
 
54
64
  const middleware = [offset(offsetDistance), flip(), shift({ padding: 8 })]
@@ -63,12 +73,19 @@ export function startPositioning(reference, floating, options = {}) {
63
73
  )
64
74
  }
65
75
 
76
+ if (arrowElement) {
77
+ middleware.push(arrowMiddleware({ element: arrowElement }))
78
+ }
79
+
80
+ /** @type {Record<string, string>} Maps placement side to the opposite side where the arrow attaches */
81
+ const ARROW_STATIC_SIDE = { top: "bottom", right: "left", bottom: "top", left: "right" }
82
+
66
83
  const update = () => {
67
84
  computePosition(reference, floating, {
68
85
  placement,
69
86
  strategy,
70
87
  middleware,
71
- }).then(({ x, y, placement: finalPlacement }) => {
88
+ }).then(({ x, y, placement: finalPlacement, middlewareData }) => {
72
89
  Object.assign(floating.style, {
73
90
  position: strategy,
74
91
  left: `${x}px`,
@@ -79,6 +96,20 @@ export function startPositioning(reference, floating, options = {}) {
79
96
  if (floating.dataset.side !== side) {
80
97
  floating.dataset.side = side
81
98
  }
99
+
100
+ // Position arrow element if provided
101
+ if (arrowElement && middlewareData.arrow) {
102
+ const { x: arrowX, y: arrowY } = middlewareData.arrow
103
+ const staticSide = ARROW_STATIC_SIDE[side]
104
+
105
+ Object.assign(arrowElement.style, {
106
+ left: arrowX != null ? `${arrowX}px` : "",
107
+ top: arrowY != null ? `${arrowY}px` : "",
108
+ right: "",
109
+ bottom: "",
110
+ [staticSide]: "-4px",
111
+ })
112
+ }
82
113
  })
83
114
  }
84
115
 
@@ -1,6 +1,7 @@
1
- <%# locals: (src: nil, alt: "", text: nil, size: :md, ui: {}, css_classes: "", **component_options) %>
1
+ <%# locals: (src: nil, alt: "", text: nil, size: :md, color: nil, ui: {}, css_classes: "", **component_options) %>
2
2
  <%= content_tag :span,
3
3
  class: Kiso::Themes::Avatar.render(size: size, class: css_classes),
4
+ style: (color ? "background-color: #{color};" : nil),
4
5
  data: kiso_prepare_options(component_options, slot: "avatar", size: size),
5
6
  **component_options do %>
6
7
  <% content = capture { yield }.presence %>
@@ -8,7 +9,9 @@
8
9
  <%= content %>
9
10
  <% else %>
10
11
  <% if text.present? %>
11
- <%= tag.span class: Kiso::Themes::AvatarFallback.render(size: size, class: ui[:fallback]),
12
+ <% fallback_classes = ui[:fallback].to_s %>
13
+ <% fallback_classes = "bg-transparent text-inherit #{fallback_classes}" if color %>
14
+ <%= tag.span class: Kiso::Themes::AvatarFallback.render(size: size, class: fallback_classes),
12
15
  data: { slot: "avatar-fallback" } do %>
13
16
  <%= text %>
14
17
  <% end %>
@@ -0,0 +1,7 @@
1
+ <%# locals: (label: nil, css_classes: "", **component_options) %>
2
+ <%= kiso_component_icon(:spinner,
3
+ role: "status",
4
+ aria: { label: label || t("kiso.spinner.loading") },
5
+ class: Kiso::Themes::Spinner.render(class: css_classes),
6
+ data: kiso_prepare_options(component_options, slot: "spinner"),
7
+ **component_options) %>
@@ -0,0 +1,25 @@
1
+ <%# locals: (text: nil, kbds: nil, side: :top, align: :center, delay: 0, ui: {}, css_classes: "", **component_options) %>
2
+ <%= tag.div(
3
+ class: css_classes.presence,
4
+ data: kiso_prepare_options(component_options, slot: "tooltip",
5
+ controller: "kiso--tooltip",
6
+ kiso__tooltip_side_value: side,
7
+ kiso__tooltip_align_value: align,
8
+ kiso__tooltip_delay_value: delay),
9
+ **component_options) do %>
10
+ <% if text %>
11
+ <%= kui(:tooltip, :trigger) { yield } %>
12
+ <%= kui(:tooltip, :content, css_classes: ui[:content].to_s) do %>
13
+ <span data-slot="tooltip-text"><%= text %></span>
14
+ <% if kbds.present? %>
15
+ <span data-slot="tooltip-kbds" class="hidden lg:inline-flex items-center shrink-0 gap-0.5">
16
+ <% Array(kbds).each do |kbd| %>
17
+ <%= kui(:kbd, size: :sm) { kbd } %>
18
+ <% end %>
19
+ </span>
20
+ <% end %>
21
+ <% end %>
22
+ <% else %>
23
+ <%= yield %>
24
+ <% end %>
25
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <%# locals: (css_classes: "", ui: {}, **component_options) %>
2
+ <%= tag.div(
3
+ class: Kiso::Themes::TooltipContent.render(class: css_classes),
4
+ role: "tooltip",
5
+ popover: "manual",
6
+ data: kiso_prepare_options(component_options, slot: "tooltip-content",
7
+ kiso__tooltip_target: "content",
8
+ action: "mouseenter->kiso--tooltip#contentMouseEnter mouseleave->kiso--tooltip#contentMouseLeave"),
9
+ **component_options) do %>
10
+ <%= yield %>
11
+ <%= tag.div(
12
+ class: Kiso::Themes::TooltipArrow.render(class: ui[:arrow].to_s),
13
+ data: { slot: "tooltip-arrow", kiso__tooltip_target: "arrow" }) %>
14
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <%# locals: (css_classes: "", **component_options) %>
2
+ <%= tag.div(
3
+ class: css_classes.presence,
4
+ data: kiso_prepare_options(component_options, slot: "tooltip-trigger",
5
+ kiso__tooltip_target: "trigger",
6
+ action: "mouseenter->kiso--tooltip#show mouseleave->kiso--tooltip#hide focusin->kiso--tooltip#show focusout->kiso--tooltip#hide"),
7
+ **component_options) do %>
8
+ <%= yield %>
9
+ <% end %>
@@ -24,6 +24,10 @@ en:
24
24
  collapse: "Collapse sidebar"
25
25
  dialog:
26
26
  close: "Close"
27
+ spinner:
28
+ loading: "Loading"
29
+ tooltip:
30
+ label: "Tooltip"
27
31
  pagination:
28
32
  label: "pagination"
29
33
  more_pages: "More pages"
@@ -87,7 +87,8 @@ module Kiso
87
87
  menu: "menu",
88
88
  minus: "minus",
89
89
  panel_left_close: "panel-left-close",
90
- panel_left_open: "panel-left-open"
90
+ panel_left_open: "panel-left-open",
91
+ spinner: "loader-circle"
91
92
  }
92
93
  end
93
94
  end
@@ -130,7 +130,10 @@ module Kiso
130
130
  nav_item: {base: "rounded-full"},
131
131
 
132
132
  # Nav item badge: rounded-md → rounded-full
133
- nav_item_badge: {base: "rounded-full"}
133
+ nav_item_badge: {base: "rounded-full"},
134
+
135
+ # Tooltip content: rounded-md → rounded-xl
136
+ tooltip_content: {base: "rounded-xl"}
134
137
  }.freeze
135
138
  end
136
139
  end
@@ -172,7 +172,10 @@ module Kiso
172
172
  nav_item: {base: "rounded-none"},
173
173
 
174
174
  # Nav item badge: rounded-md → rounded-none
175
- nav_item_badge: {base: "rounded-none"}
175
+ nav_item_badge: {base: "rounded-none"},
176
+
177
+ # Tooltip content: rounded-md → rounded-none
178
+ tooltip_content: {base: "rounded-none"}
176
179
  }.freeze
177
180
  end
178
181
  end
@@ -4,6 +4,7 @@ module Kiso
4
4
  base: "group/avatar relative flex shrink-0 rounded-full select-none items-center justify-center bg-muted",
5
5
  variants: {
6
6
  size: {
7
+ xs: "size-5",
7
8
  sm: "size-6",
8
9
  md: "size-8",
9
10
  lg: "size-10"
@@ -20,6 +21,7 @@ module Kiso
20
21
  base: "flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground font-medium",
21
22
  variants: {
22
23
  size: {
24
+ xs: "text-[10px]",
23
25
  sm: "text-xs",
24
26
  md: "text-sm",
25
27
  lg: "text-base"
@@ -31,6 +33,7 @@ module Kiso
31
33
  AvatarBadge = ClassVariants.build(
32
34
  base: "bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 " \
33
35
  "inline-flex items-center justify-center rounded-full ring-2 select-none " \
36
+ "group-data-[size=xs]/avatar:size-1.5 group-data-[size=xs]/avatar:[&>svg]:hidden " \
34
37
  "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden " \
35
38
  "group-data-[size=md]/avatar:size-2.5 group-data-[size=md]/avatar:[&>svg]:size-2 " \
36
39
  "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2"
@@ -44,9 +47,11 @@ module Kiso
44
47
  AvatarGroupCount = ClassVariants.build(
45
48
  base: "bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 " \
46
49
  "items-center justify-center rounded-full text-sm ring-2 " \
50
+ "group-has-data-[size=xs]/avatar-group:size-5 " \
47
51
  "group-has-data-[size=lg]/avatar-group:size-10 " \
48
52
  "group-has-data-[size=sm]/avatar-group:size-6 " \
49
53
  "[&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 " \
54
+ "group-has-data-[size=xs]/avatar-group:[&>svg]:size-2.5 " \
50
55
  "group-has-data-[size=sm]/avatar-group:[&>svg]:size-3"
51
56
  )
52
57
  end
@@ -14,7 +14,7 @@ module Kiso
14
14
  # - +size+ — :xs, :sm, :md (default), :lg, :xl
15
15
  # - +block+ — +true+ for full-width, +false+ (default)
16
16
  Button = ClassVariants.build(
17
- base: "inline-flex items-center justify-center gap-2 font-medium whitespace-nowrap shrink-0 " \
17
+ base: "items-center justify-center gap-2 font-medium whitespace-nowrap shrink-0 " \
18
18
  "transition-all " \
19
19
  "focus-visible:outline-2 focus-visible:outline-offset-2 " \
20
20
  "disabled:pointer-events-none disabled:opacity-50 " \
@@ -0,0 +1,16 @@
1
+ module Kiso
2
+ module Themes
3
+ # Spinning loading indicator.
4
+ #
5
+ # @example
6
+ # Spinner.render
7
+ #
8
+ # No variants — size is controlled via +css_classes:+. Inherits
9
+ # +currentColor+ from the parent context.
10
+ #
11
+ # shadcn base: size-4 animate-spin
12
+ Spinner = ClassVariants.build(
13
+ base: "animate-spin size-4"
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module Kiso
2
+ module Themes
3
+ # Floating tooltip content panel with inverted colors.
4
+ #
5
+ # @example
6
+ # TooltipContent.render
7
+ #
8
+ # No variants — tooltip always uses inverted colors. Inherits position
9
+ # from Floating UI via the Stimulus controller.
10
+ #
11
+ # shadcn base: bg-foreground text-background px-3 py-1.5 text-xs rounded-md
12
+ # Kiso: bg-inverted text-inverted-foreground (semantic equivalents)
13
+ TooltipContent = ClassVariants.build(
14
+ base: "bg-inverted text-inverted-foreground px-3 py-1.5 text-xs rounded-md " \
15
+ "flex items-center gap-1.5 select-none w-max max-w-xs"
16
+ )
17
+
18
+ # Arrow element pointing from tooltip content to the trigger.
19
+ #
20
+ # Positioned by Floating UI arrow middleware. CSS rotates 45° to form
21
+ # a diamond, with half hidden behind the tooltip edge.
22
+ TooltipArrow = ClassVariants.build(
23
+ base: "absolute size-2 rotate-45 bg-inverted"
24
+ )
25
+ end
26
+ end
data/lib/kiso/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Kiso
4
4
  # @return [String] the current gem version
5
- VERSION = "0.4.2.pre"
5
+ VERSION = "0.5.0.pre"
6
6
  end
data/lib/kiso.rb CHANGED
@@ -49,6 +49,8 @@ require "kiso/themes/nav"
49
49
  require "kiso/themes/page"
50
50
  require "kiso/themes/avatar"
51
51
  require "kiso/themes/skeleton"
52
+ require "kiso/themes/spinner"
53
+ require "kiso/themes/tooltip"
52
54
  require "kiso/themes/slider"
53
55
  require "kiso/themes/layout"
54
56
  require "kiso/icons"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kiso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2.pre
4
+ version: 0.5.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Clarke
@@ -73,13 +73,13 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
- - CHANGELOG.md
77
76
  - MIT-LICENSE
78
77
  - README.md
79
78
  - Rakefile
80
79
  - app/assets/fonts/kiso/GeistMonoVF.woff2
81
80
  - app/assets/fonts/kiso/GeistVF.woff2
82
81
  - app/assets/fonts/kiso/OFL.txt
82
+ - app/assets/tailwind/kiso/button.css
83
83
  - app/assets/tailwind/kiso/checkbox.css
84
84
  - app/assets/tailwind/kiso/color-mode.css
85
85
  - app/assets/tailwind/kiso/dashboard.css
@@ -93,6 +93,7 @@ files:
93
93
  - app/assets/tailwind/kiso/palettes/zinc.css
94
94
  - app/assets/tailwind/kiso/radio-group.css
95
95
  - app/assets/tailwind/kiso/slider.css
96
+ - app/assets/tailwind/kiso/tooltip.css
96
97
  - app/helpers/kiso/app_component_helper.rb
97
98
  - app/helpers/kiso/component_helper.rb
98
99
  - app/helpers/kiso/icon_helper.rb
@@ -103,6 +104,7 @@ files:
103
104
  - app/javascript/controllers/kiso/command_controller.js
104
105
  - app/javascript/controllers/kiso/command_dialog_controller.js
105
106
  - app/javascript/controllers/kiso/dialog_controller.js
107
+ - app/javascript/controllers/kiso/dialog_trigger_controller.js
106
108
  - app/javascript/controllers/kiso/dropdown_menu_controller.js
107
109
  - app/javascript/controllers/kiso/index.d.ts
108
110
  - app/javascript/controllers/kiso/index.js
@@ -114,6 +116,7 @@ files:
114
116
  - app/javascript/controllers/kiso/theme_controller.js
115
117
  - app/javascript/controllers/kiso/toggle_controller.js
116
118
  - app/javascript/controllers/kiso/toggle_group_controller.js
119
+ - app/javascript/controllers/kiso/tooltip_controller.js
117
120
  - app/javascript/kiso/utils/focusable.js
118
121
  - app/javascript/kiso/utils/highlight.js
119
122
  - app/javascript/kiso/utils/positioning.js
@@ -168,6 +171,7 @@ files:
168
171
  - app/views/kiso/components/_separator.html.erb
169
172
  - app/views/kiso/components/_skeleton.html.erb
170
173
  - app/views/kiso/components/_slider.html.erb
174
+ - app/views/kiso/components/_spinner.html.erb
171
175
  - app/views/kiso/components/_stats_card.html.erb
172
176
  - app/views/kiso/components/_stats_grid.html.erb
173
177
  - app/views/kiso/components/_switch.html.erb
@@ -175,6 +179,7 @@ files:
175
179
  - app/views/kiso/components/_textarea.html.erb
176
180
  - app/views/kiso/components/_toggle.html.erb
177
181
  - app/views/kiso/components/_toggle_group.html.erb
182
+ - app/views/kiso/components/_tooltip.html.erb
178
183
  - app/views/kiso/components/alert/_actions.html.erb
179
184
  - app/views/kiso/components/alert/_description.html.erb
180
185
  - app/views/kiso/components/alert/_title.html.erb
@@ -320,6 +325,8 @@ files:
320
325
  - app/views/kiso/components/table/_header.html.erb
321
326
  - app/views/kiso/components/table/_row.html.erb
322
327
  - app/views/kiso/components/toggle_group/_item.html.erb
328
+ - app/views/kiso/components/tooltip/_content.html.erb
329
+ - app/views/kiso/components/tooltip/_trigger.html.erb
323
330
  - config/deploy.docs.yml
324
331
  - config/deploy.yml
325
332
  - config/importmap.rb
@@ -380,12 +387,14 @@ files:
380
387
  - lib/kiso/themes/shared.rb
381
388
  - lib/kiso/themes/skeleton.rb
382
389
  - lib/kiso/themes/slider.rb
390
+ - lib/kiso/themes/spinner.rb
383
391
  - lib/kiso/themes/stats_card.rb
384
392
  - lib/kiso/themes/switch.rb
385
393
  - lib/kiso/themes/table.rb
386
394
  - lib/kiso/themes/textarea.rb
387
395
  - lib/kiso/themes/toggle.rb
388
396
  - lib/kiso/themes/toggle_group.rb
397
+ - lib/kiso/themes/tooltip.rb
389
398
  - lib/kiso/version.rb
390
399
  - lib/tasks/kiso.rake
391
400
  homepage: https://github.com/steveclarke/kiso
@@ -394,7 +403,7 @@ licenses:
394
403
  metadata:
395
404
  homepage_uri: https://github.com/steveclarke/kiso
396
405
  source_code_uri: https://github.com/steveclarke/kiso
397
- changelog_uri: https://github.com/steveclarke/kiso/blob/master/CHANGELOG.md
406
+ changelog_uri: https://github.com/steveclarke/kiso/releases
398
407
  bug_tracker_uri: https://github.com/steveclarke/kiso/issues
399
408
  rubygems_mfa_required: 'true'
400
409
  allowed_push_host: https://rubygems.org
data/CHANGELOG.md DELETED
@@ -1,130 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ## [0.4.2.pre] - 2026-03-08
11
-
12
- ### Added
13
-
14
- - `scope:` prop for sharing domain locals from parent components to sub-parts — `appui(:room_card, scope: { room: room })` makes `room:` available to all sub-parts automatically without repeating it on every call. Explicit kwargs on sub-part calls override scope values. One level deep only (parent to its own sub-parts).
15
-
16
- ## [0.4.1.pre] - 2026-03-08
17
-
18
- ### Added
19
-
20
- - `center:` variant on App layout component — `kui(:app, center: true)` applies full-viewport centering for login pages and similar single-focus layouts
21
- - `kui_tag` / `appui_tag` helpers — collapse the common `content_tag` + `kiso_prepare_options` + theme rendering boilerplate into a single call; component generator templates now use `appui_tag` by default
22
- - Shade scale auto-wiring — host apps defining `--color-primary-50` through `--color-primary-950` in their Tailwind `@theme` block now automatically feed into Kiso's semantic color tokens (shade 500 for light mode, 400 for dark mode, matching Nuxt UI conventions)
23
-
24
- ### Fixed
25
-
26
- - Palette CSS files are now importable in host apps — a `kiso:palettes` rake task generates CSS stubs so `@import "../builds/tailwind/kiso/palettes/blue.css"` resolves correctly
27
-
28
- ## [0.4.0.pre] - 2026-03-08
29
-
30
- ### Added
31
-
32
- - Layout component family: App, Container (4 size variants), Header, Footer, Main
33
- - Page component family: Page (grid with sidebars), PageHeader, PageBody, PageSection, PageGrid, PageCard (4 variants)
34
- - `appui()` helper for host app components with `app/themes/` and `app/views/components/`
35
- - `kiso:framework_component` generator for scaffolding engine components
36
- - `kiso:component` generator for scaffolding host app components
37
- - Theme presets: `apply_preset(:rounded)` and `apply_preset(:sharp)`
38
- - 5 OKLCH color palettes (zinc, blue, green, orange, violet)
39
- - i18n support: all component strings use `t()` with `config/locales/en.yml`
40
- - [Building Your Own Components](/guide/building-components) guide — how to wrap Kiso components with domain logic and build standalone components with `appui()`, themes, and sub-parts
41
- - [Detailed release notes](/releases/batch-merge) with upgrade guide and examples for all new features
42
-
43
- ## [0.3.0.pre] - 2026-03-03
44
-
45
- ### Added
46
-
47
- - Dialog component — modal dialog wrapping the native `<dialog>` element with `showModal()` for focus trapping and backdrop. Sub-parts: header, title, description, body, footer, close. Entry/exit CSS animations with reduced-motion support. Stimulus controller for programmatic open/close.
48
- - Alert Dialog component — confirmation dialog that requires an explicit user action (`role="alertdialog"`). Cannot be dismissed by Escape or backdrop click. Sub-parts: header, title, description, media, footer, action, cancel. Size variants (default/sm) with responsive media grid layout. Auto-linked `aria-labelledby` and `aria-describedby`.
49
- - AspectRatio component — lightweight wrapper that applies an aspect ratio via inline style. Accepts any `ratio:` value (defaults to 16:9).
50
- - Slider component — range input with track, thumb, and fill styling. Supports min/max/step/value, three sizes (sm/md/lg), and disabled state. Stimulus controller for real-time value display.
51
- - Empty component `:actions` slot for placing buttons below the description.
52
- - Button `method:` prop — renders a Rails `button_to` form for DELETE/POST/PUT/PATCH actions while preserving all Button styling.
53
- - Icons guide added to documentation site.
54
-
55
- ### Fixed
56
-
57
- - InputOTP slots missing visible border when a separator is placed inside a group.
58
- - Sidebar header and footer now use `flex-col` layout matching shadcn structure.
59
-
60
- ## [0.2.2.pre] - 2026-03-03
61
-
62
- ### Fixed
63
-
64
- - Dashboard layout rendering — components called without a block inside a layout (e.g., sidebar toggle, collapse) would capture the entire page template via ERB yield bubbling, breaking the dashboard grid. The `kui()` helper now passes an empty proc to prevent yield from reaching the layout.
65
- - Dashboard toggle and collapse icon sizing — SVG icons now render at the correct size via `[&>svg]:size-4`.
66
-
67
- ## [0.2.1.pre] - 2026-03-03
68
-
69
- ### Fixed
70
-
71
- - Propshaft `stylesheet_link_tag :app` compatibility — the Rails 8.1 default `:app` symbol caused Propshaft to serve `tailwindcss-rails` engine CSS stubs directly to the browser, resulting in 404 errors for absolute filesystem paths. Kiso now filters these build-time intermediates from Propshaft's stylesheet resolution automatically. Host apps using either `:app` or explicit `"tailwind"` work correctly.
72
-
73
- ## [0.2.0.pre] - 2026-03-03
74
-
75
- ### Added
76
-
77
- - InputOTP component — one-time password input with individual character slots, auto-advance, paste support, and mobile SMS autofill via `autocomplete="one-time-code"`. Stimulus controller distributes a single transparent input to visual slot divs. Sub-parts: group, slot, separator. Dispatches `change` and `complete` events for auto-submit workflows.
78
- - SelectNative component — styled native `<select>` with chevron icon overlay. Variant × size axes matching Input (outline/soft/ghost, sm/md/lg). No JavaScript required.
79
- - Sidebar state variants — `kui-sidebar-open:` and `kui-sidebar-closed:` custom Tailwind variants for showing/hiding any element based on sidebar open/closed state. Composable with breakpoints (e.g., `kui-sidebar-open:lg:hidden`).
80
- - Custom toggle icon override — sidebar toggle and collapse buttons accept a block to replace the default icon.
81
- - Auto body base styles — engine CSS now applies `bg-background text-foreground antialiased` on `<body>` via `@layer base`, so host apps no longer need to add these classes manually.
82
-
83
- ## [0.1.1.pre] - 2026-03-03
84
-
85
- ### Added
86
-
87
- - Dashboard layout system — sidebar, navbar, panel, toolbar, and nav components with cookie-persisted sidebar state
88
- - Avatar component with image, fallback, badge, and group support
89
- - Form components — Field, Label, Input, Textarea, InputGroup, Checkbox, RadioGroup, Switch, Select, Combobox
90
- - Overlay components — Popover, DropdownMenu, Command palette
91
- - Navigation components — Breadcrumb, Pagination
92
- - Element components — Kbd, Toggle, ToggleGroup
93
- - Dark mode system — `kiso_theme_script` helper, ColorModeButton, ColorModeSelect
94
- - Floating UI positioning for popovers and dropdowns
95
- - Global theme overrides via `Kiso.configure`
96
- - Configurable default icons via `kiso_component_icon`
97
- - Getting Started guide
98
-
99
- ### Changed
100
-
101
- - Renamed `kiso()` helper to `kui()` to avoid Rails route proxy collision
102
- - Renamed `empty_state` to `empty` to match shadcn naming
103
- - Adopted `data-slot` convention from shadcn v4
104
-
105
- ## [0.1.0.pre] - 2026-02-25
106
-
107
- ### Added
108
-
109
- - Core engine with `kui()` component helper and `kiso_prepare_options` builder
110
- - `class_variants` + `tailwind_merge` integration for variant definitions
111
- - Theme CSS with 7 palettes, surface tokens, and dark mode
112
- - Badge component (color × variant × size, pill shape, SVG handling)
113
- - Alert component (color × variant, CSS Grid layout, title/description sub-parts)
114
- - Button component (6 variants, smart tag, 5 sizes, icon support)
115
- - Card component (3 variants, 6 sub-parts, shadcn gap-6/py-6 spacing)
116
- - Separator component (horizontal/vertical, decorative prop)
117
- - Empty State component (5 sub-parts, media variant)
118
- - Lookbook component previews
119
- - Bridgetown documentation site
120
-
121
- [Unreleased]: https://github.com/steveclarke/kiso/compare/v0.4.2.pre...HEAD
122
- [0.4.2.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.4.2.pre
123
- [0.4.1.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.4.1.pre
124
- [0.4.0.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.4.0.pre
125
- [0.3.0.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.3.0.pre
126
- [0.2.2.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.2.2.pre
127
- [0.2.1.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.2.1.pre
128
- [0.2.0.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.2.0.pre
129
- [0.1.1.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.1.1.pre
130
- [0.1.0.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.1.0.pre