kiso 0.1.1.pre → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bda2256a79d7581462417a394433826ba6289cdab8f7f6bc04fbc451ee929478
4
- data.tar.gz: a444b9108f532af991b690f8620faa3019639b533d9fd6ccda9f1aabca3b4540
3
+ metadata.gz: fc81659a56fbdc6091f37b314d096fa6eeca6a430284f594a2666e7646b49bed
4
+ data.tar.gz: 75d5f93f6d15684a044b340c1159fca84d0ac8285e16bf859c6805ceef35da4a
5
5
  SHA512:
6
- metadata.gz: 1cc8613ffb244b57a7499bcb4b4f7e4c3ec2c3c9feb8b8e6235cb55c9288cf0ea40261598424e33c221055577a0d8eeb9ec229aa18e9e0fcb20796551d5985c7
7
- data.tar.gz: 93f2dee31a6a6526b7d6548b394a37eaa6ccca2bed6fa47b331664bfcb9a233bb466e41f9b5d4d1cdb8a029dcf85a46ddfb9286cb304165dc3420e7bcabd5ac3
6
+ metadata.gz: 452f1fcafc008d9257fe03b6530a182e4be66a8fbcf7e42ae6d03131cc0fb3fe8f8f144330a6c667d7e9fbb4dad65a592dd17e24c1833fb28cfbd71c5e4224fc
7
+ data.tar.gz: cae0cecec57517234b7f5c02d44cfe7b3542b463bec938ae955793c345345be7daf63a7212adfd040cf1849ec2d5faeaa377e410b79c477abfe68e6a92110699
data/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1.pre] - 2026-03-03
11
+
12
+ ### Fixed
13
+
14
+ - 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.
15
+
16
+ ## [0.2.0.pre] - 2026-03-03
17
+
18
+ ### Added
19
+
20
+ - 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.
21
+ - SelectNative component — styled native `<select>` with chevron icon overlay. Variant × size axes matching Input (outline/soft/ghost, sm/md/lg). No JavaScript required.
22
+ - 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`).
23
+ - Custom toggle icon override — sidebar toggle and collapse buttons accept a block to replace the default icon.
24
+ - 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.
25
+
10
26
  ## [0.1.1.pre] - 2026-03-03
11
27
 
12
28
  ### Added
@@ -45,6 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
45
61
  - Lookbook component previews
46
62
  - Bridgetown documentation site
47
63
 
48
- [Unreleased]: https://github.com/steveclarke/kiso/compare/v0.1.1.pre...HEAD
64
+ [Unreleased]: https://github.com/steveclarke/kiso/compare/v0.2.1.pre...HEAD
65
+ [0.2.1.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.2.1.pre
66
+ [0.2.0.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.2.0.pre
49
67
  [0.1.1.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.1.1.pre
50
68
  [0.1.0.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.1.0.pre
data/README.md CHANGED
@@ -37,12 +37,10 @@ And add the dark mode helper to your layout's `<head>`:
37
37
  <%= stylesheet_link_tag "tailwind" %>
38
38
  <%= javascript_importmap_tags %>
39
39
  </head>
40
-
41
- <body class="bg-background text-foreground antialiased">
42
40
  ```
43
41
 
44
- That's it. Helpers, importmap pins, asset paths, and dark mode tokens are all
45
- wired up automatically by the engine. See the
42
+ That's it. Helpers, importmap pins, asset paths, dark mode tokens, and base
43
+ body styles are all wired up automatically by the engine. See the
46
44
  [Getting Started guide](https://kisoui.com/getting-started) for the full
47
45
  walkthrough.
48
46
 
@@ -40,6 +40,24 @@
40
40
  --sidebar-accent-foreground: var(--color-zinc-300);
41
41
  }
42
42
 
43
+ /* ── Sidebar state variants ──────────────────────────────────────────────────
44
+ Custom Tailwind variants for showing/hiding content based on sidebar state.
45
+ Works on any descendant of dashboard_group. Compose with breakpoints:
46
+ kui-sidebar-open:lg:hidden — hide on desktop when sidebar expanded
47
+ kui-sidebar-closed:lg:hidden — hide on desktop when sidebar collapsed */
48
+
49
+ @custom-variant kui-sidebar-open {
50
+ [data-sidebar-open="true"] & {
51
+ @slot;
52
+ }
53
+ }
54
+
55
+ @custom-variant kui-sidebar-closed {
56
+ [data-sidebar-open="false"] & {
57
+ @slot;
58
+ }
59
+ }
60
+
43
61
  /* ── Layout mechanics ─────────────────────────────────────────────────────── */
44
62
 
45
63
  @layer components {
@@ -7,6 +7,7 @@
7
7
  @import "./radio-group.css";
8
8
  @import "./color-mode.css";
9
9
  @import "./dashboard.css";
10
+ @import "./input-otp.css";
10
11
 
11
12
  /* Scan Kiso's own files so host apps don't need to know the gem's internals.
12
13
  Paths are relative to THIS file (app/assets/tailwind/kiso/engine.css).
@@ -133,3 +134,16 @@
133
134
 
134
135
  --color-ring: var(--color-zinc-600);
135
136
  }
137
+
138
+ /* === Page Defaults ===
139
+ Kiso automatically applies sensible base styles to <body> so host apps
140
+ start with the correct colors and rendering. Uses @layer base (lowest
141
+ Tailwind priority) — utility classes on <body> override automatically. */
142
+
143
+ @layer base {
144
+ body {
145
+ @apply bg-background text-foreground antialiased;
146
+ font-synthesis-weight: none;
147
+ text-rendering: optimizeLegibility;
148
+ }
149
+ }
@@ -0,0 +1,10 @@
1
+ /* InputOTP caret blink animation — the blinking cursor in the active empty slot. */
2
+
3
+ @theme inline {
4
+ --animate-caret-blink: caret-blink 1s ease-out infinite;
5
+ }
6
+
7
+ @keyframes caret-blink {
8
+ 0%, 70%, 100% { opacity: 1; }
9
+ 20%, 50% { opacity: 0; }
10
+ }
@@ -2,6 +2,7 @@ import KisoComboboxController from "./combobox_controller.js"
2
2
  import KisoCommandController from "./command_controller.js"
3
3
  import KisoCommandDialogController from "./command_dialog_controller.js"
4
4
  import KisoDropdownMenuController from "./dropdown_menu_controller.js"
5
+ import KisoInputOtpController from "./input_otp_controller.js"
5
6
  import KisoPopoverController from "./popover_controller.js"
6
7
  import KisoSelectController from "./select_controller.js"
7
8
  import KisoSidebarController from "./sidebar_controller.js"
@@ -15,6 +16,7 @@ const KisoUi = {
15
16
  application.register("kiso--command", KisoCommandController)
16
17
  application.register("kiso--command-dialog", KisoCommandDialogController)
17
18
  application.register("kiso--dropdown-menu", KisoDropdownMenuController)
19
+ application.register("kiso--input-otp", KisoInputOtpController)
18
20
  application.register("kiso--popover", KisoPopoverController)
19
21
  application.register("kiso--select", KisoSelectController)
20
22
  application.register("kiso--sidebar", KisoSidebarController)
@@ -30,6 +32,7 @@ export {
30
32
  KisoCommandController,
31
33
  KisoCommandDialogController,
32
34
  KisoDropdownMenuController,
35
+ KisoInputOtpController,
33
36
  KisoPopoverController,
34
37
  KisoSelectController,
35
38
  KisoSidebarController,
@@ -0,0 +1,195 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ /**
4
+ * Manages a one-time password input with individual visual character slots.
5
+ *
6
+ * A single transparent `<input>` overlays the visual slots. The browser handles
7
+ * all native input behavior (focus, paste, mobile autofill). This controller
8
+ * distributes the input value to slot elements and tracks the active slot via
9
+ * `selectionStart`.
10
+ *
11
+ * @example
12
+ * <div data-controller="kiso--input-otp"
13
+ * data-kiso--input-otp-length-value="6"
14
+ * data-kiso--input-otp-pattern-value="\\d">
15
+ * <input data-kiso--input-otp-target="input"
16
+ * type="text" maxlength="6" autocomplete="one-time-code"
17
+ * class="absolute inset-0 z-10 w-full h-full opacity-0">
18
+ * <div data-slot="input-otp-group">
19
+ * <div data-kiso--input-otp-target="slot" data-slot="input-otp-slot">
20
+ * <span data-slot="input-otp-slot-char"></span>
21
+ * <div data-slot="input-otp-caret" hidden>
22
+ * <div class="bg-foreground h-4 w-px animate-caret-blink"></div>
23
+ * </div>
24
+ * </div>
25
+ * </div>
26
+ * </div>
27
+ *
28
+ * @property {HTMLInputElement} inputTarget - The transparent real input element
29
+ * @property {HTMLElement[]} slotTargets - Visual slot div elements
30
+ * @property {Number} lengthValue - Expected OTP code length
31
+ * @property {String} patternValue - Regex pattern for allowed characters (default: digits only)
32
+ *
33
+ * @fires kiso--input-otp:change - When the OTP value changes.
34
+ * Detail: `{ value: string }`
35
+ * @fires kiso--input-otp:complete - When all slots are filled.
36
+ * Detail: `{ value: string }`
37
+ */
38
+ export default class extends Controller {
39
+ static targets = ["input", "slot"]
40
+ static values = {
41
+ length: { type: Number, default: 6 },
42
+ pattern: { type: String, default: "\\d" },
43
+ }
44
+
45
+ /**
46
+ * Binds event listeners and syncs slots from any pre-filled value.
47
+ */
48
+ connect() {
49
+ this._handleInput = this._handleInput.bind(this)
50
+ this._handleKeydown = this._handleKeydown.bind(this)
51
+ this._handleFocus = this._handleFocus.bind(this)
52
+ this._handleBlur = this._handleBlur.bind(this)
53
+ this._handleClick = this._handleClick.bind(this)
54
+
55
+ this.inputTarget.addEventListener("input", this._handleInput)
56
+ this.inputTarget.addEventListener("keydown", this._handleKeydown)
57
+ this.inputTarget.addEventListener("focus", this._handleFocus)
58
+ this.inputTarget.addEventListener("blur", this._handleBlur)
59
+ this.element.addEventListener("click", this._handleClick)
60
+
61
+ this._syncSlots()
62
+ }
63
+
64
+ /**
65
+ * Removes event listeners.
66
+ */
67
+ disconnect() {
68
+ this.inputTarget.removeEventListener("input", this._handleInput)
69
+ this.inputTarget.removeEventListener("keydown", this._handleKeydown)
70
+ this.inputTarget.removeEventListener("focus", this._handleFocus)
71
+ this.inputTarget.removeEventListener("blur", this._handleBlur)
72
+ this.element.removeEventListener("click", this._handleClick)
73
+ }
74
+
75
+ /**
76
+ * Filters input against the pattern, syncs slots, and dispatches events.
77
+ *
78
+ * @private
79
+ */
80
+ _handleInput() {
81
+ const regex = new RegExp(this.patternValue)
82
+ const filtered = this.inputTarget.value
83
+ .split("")
84
+ .filter((char) => regex.test(char))
85
+ .join("")
86
+ .slice(0, this.lengthValue)
87
+
88
+ if (filtered !== this.inputTarget.value) {
89
+ this.inputTarget.value = filtered
90
+ }
91
+
92
+ this._syncSlots()
93
+ this._updateActiveSlot()
94
+
95
+ this.dispatch("change", { detail: { value: filtered } })
96
+
97
+ if (filtered.length === this.lengthValue) {
98
+ this.dispatch("complete", { detail: { value: filtered } })
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Re-syncs the active slot after keydown events that may change selection
104
+ * (arrow keys, backspace, delete, home, end).
105
+ *
106
+ * @private
107
+ */
108
+ _handleKeydown() {
109
+ requestAnimationFrame(() => this._updateActiveSlot())
110
+ }
111
+
112
+ /**
113
+ * Shows the caret on the active slot when the input receives focus.
114
+ *
115
+ * @private
116
+ */
117
+ _handleFocus() {
118
+ this._updateActiveSlot()
119
+ }
120
+
121
+ /**
122
+ * Clears all active states and hides all carets on blur.
123
+ *
124
+ * @private
125
+ */
126
+ _handleBlur() {
127
+ this._clearActiveSlots()
128
+ }
129
+
130
+ /**
131
+ * Focuses the transparent input when clicking anywhere on the component.
132
+ * Places the cursor at the end of the current value.
133
+ *
134
+ * @private
135
+ */
136
+ _handleClick() {
137
+ this.inputTarget.focus()
138
+ const len = this.inputTarget.value.length
139
+ this.inputTarget.setSelectionRange(len, len)
140
+ this._updateActiveSlot()
141
+ }
142
+
143
+ /**
144
+ * Distributes input value characters to visual slot elements.
145
+ * Each slot's `[data-slot="input-otp-slot-char"]` span gets its character.
146
+ *
147
+ * @private
148
+ */
149
+ _syncSlots() {
150
+ const value = this.inputTarget.value
151
+ this.slotTargets.forEach((slot, index) => {
152
+ const charEl = slot.querySelector("[data-slot='input-otp-slot-char']")
153
+ if (charEl) {
154
+ charEl.textContent = value[index] || ""
155
+ }
156
+ })
157
+ }
158
+
159
+ /**
160
+ * Sets `data-active="true"` on the slot matching the cursor position.
161
+ * Shows the blinking caret on the active empty slot.
162
+ *
163
+ * @private
164
+ */
165
+ _updateActiveSlot() {
166
+ if (document.activeElement !== this.inputTarget) return
167
+
168
+ const pos = Math.min(this.inputTarget.selectionStart ?? 0, this.lengthValue - 1)
169
+
170
+ this.slotTargets.forEach((slot, index) => {
171
+ const isActive = index === pos
172
+ const charEl = slot.querySelector("[data-slot='input-otp-slot-char']")
173
+ const caretEl = slot.querySelector("[data-slot='input-otp-caret']")
174
+ const hasChar = charEl && charEl.textContent !== ""
175
+
176
+ slot.dataset.active = isActive
177
+ if (caretEl) {
178
+ caretEl.hidden = !(isActive && !hasChar)
179
+ }
180
+ })
181
+ }
182
+
183
+ /**
184
+ * Removes all active states and hides all carets.
185
+ *
186
+ * @private
187
+ */
188
+ _clearActiveSlots() {
189
+ this.slotTargets.forEach((slot) => {
190
+ delete slot.dataset.active
191
+ const caretEl = slot.querySelector("[data-slot='input-otp-caret']")
192
+ if (caretEl) caretEl.hidden = true
193
+ })
194
+ }
195
+ }
@@ -0,0 +1,22 @@
1
+ <%# locals: (length: 6, name: nil, id: nil, value: nil, pattern: "\\d", disabled: false, autocomplete: "one-time-code", aria_label: nil, css_classes: "", **component_options) %>
2
+ <%= content_tag :div,
3
+ class: Kiso::Themes::InputOtp.render(class: css_classes),
4
+ data: kiso_prepare_options(component_options, slot: "input-otp",
5
+ controller: "kiso--input-otp",
6
+ kiso__input_otp_length_value: length,
7
+ kiso__input_otp_pattern_value: pattern),
8
+ **component_options do %>
9
+ <%= tag.input(type: :text,
10
+ id: id,
11
+ inputmode: (pattern == "\\d" ? :numeric : :text),
12
+ autocomplete: autocomplete,
13
+ maxlength: length,
14
+ name: name,
15
+ value: value,
16
+ disabled: disabled,
17
+ spellcheck: false,
18
+ aria: { label: aria_label },
19
+ class: "absolute inset-0 z-10 w-full h-full opacity-0 disabled:cursor-not-allowed",
20
+ data: { kiso__input_otp_target: "input" }) %>
21
+ <%= yield %>
22
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <%# locals: (variant: :outline, size: :md, disabled: false, css_classes: "", **component_options) %>
2
+ <%= content_tag :div,
3
+ class: Kiso::Themes::SelectNativeWrapper.render,
4
+ data: {slot: "select-native-wrapper"} do %>
5
+ <% component_options[:disabled] = true if disabled %>
6
+ <%= tag.select(
7
+ class: Kiso::Themes::SelectNative.render(variant: variant, size: size, class: css_classes),
8
+ data: kiso_prepare_options(component_options, slot: "select-native"),
9
+ **component_options) { yield } %>
10
+ <%= content_tag :span,
11
+ class: Kiso::Themes::SelectNativeIcon.render,
12
+ aria: {hidden: true},
13
+ data: {slot: "select-native-icon"} do %>
14
+ <%= kiso_component_icon(:chevron_down) %>
15
+ <% end %>
16
+ <% end %>
@@ -7,5 +7,5 @@
7
7
  aria: { label: "Toggle sidebar", expanded: "false", controls: "dashboard-sidebar" },
8
8
  type: "button",
9
9
  **component_options do %>
10
- <%= kiso_component_icon(:menu) %>
10
+ <%= capture { yield }.presence || kiso_component_icon(:menu) %>
11
11
  <% end %>
@@ -7,5 +7,5 @@
7
7
  aria: { label: "Toggle sidebar", expanded: "false", controls: "dashboard-sidebar" },
8
8
  type: "button",
9
9
  **component_options do %>
10
- <%= kiso_component_icon(:menu) %>
10
+ <%= capture { yield }.presence || kiso_component_icon(:menu) %>
11
11
  <% end %>
@@ -0,0 +1,7 @@
1
+ <%# locals: (css_classes: "", **component_options) %>
2
+ <%= content_tag :div,
3
+ class: Kiso::Themes::InputOtpGroup.render(class: css_classes),
4
+ data: kiso_prepare_options(component_options, slot: "input-otp-group"),
5
+ **component_options do %>
6
+ <%= yield %>
7
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%# locals: (css_classes: "", **component_options) %>
2
+ <%= content_tag :div,
3
+ role: "separator",
4
+ class: Kiso::Themes::InputOtpSeparator.render(class: css_classes),
5
+ data: kiso_prepare_options(component_options, slot: "input-otp-separator"),
6
+ **component_options do %>
7
+ <%= capture { yield }.presence || kiso_component_icon(:minus) %>
8
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <%# locals: (size: :md, css_classes: "", **component_options) %>
2
+ <%= content_tag :div,
3
+ class: Kiso::Themes::InputOtpSlot.render(size: size, class: css_classes),
4
+ aria: { hidden: "true" },
5
+ data: kiso_prepare_options(component_options, slot: "input-otp-slot",
6
+ kiso__input_otp_target: "slot") do %>
7
+ <span data-slot="input-otp-slot-char"></span>
8
+ <div data-slot="input-otp-caret" class="pointer-events-none absolute inset-0 flex items-center justify-center" hidden>
9
+ <div class="bg-foreground h-4 w-px animate-caret-blink"></div>
10
+ </div>
11
+ <% end %>
@@ -45,6 +45,7 @@ module Kiso
45
45
  moon: "moon",
46
46
  monitor: "monitor",
47
47
  menu: "menu",
48
+ minus: "minus",
48
49
  panel_left_close: "panel-left-close",
49
50
  panel_left_open: "panel-left-open"
50
51
  }
data/lib/kiso/engine.rb CHANGED
@@ -51,6 +51,51 @@ module Kiso
51
51
  end
52
52
  end
53
53
 
54
+ # Filters tailwindcss-rails engine CSS stubs from Propshaft's :app stylesheet
55
+ # resolution so they are never served to the browser.
56
+ #
57
+ # Background: tailwindcss-rails generates intermediate stub files in the host
58
+ # app at app/assets/builds/tailwind/<engine_name>.css. Each stub contains a
59
+ # single @import with an absolute filesystem path:
60
+ #
61
+ # @import "/path/to/gems/kiso-0.2.0/app/assets/tailwind/kiso/engine.css";
62
+ #
63
+ # The Tailwind CLI resolves this @import at build time and inlines the engine
64
+ # CSS into the compiled output (app/assets/builds/tailwind.css). The stubs are
65
+ # build-time intermediates — they should never reach the browser.
66
+ #
67
+ # The problem: Propshaft's stylesheet_link_tag :app (the Rails 8.1 default)
68
+ # globs app/assets/**/*.css and generates a <link> tag for every match. This
69
+ # picks up the stubs alongside the compiled tailwind.css. The browser then
70
+ # receives a stub, encounters the @import with a filesystem path, interprets
71
+ # it as a URL, and gets a 404.
72
+ #
73
+ # Propshaft's excluded_paths config only removes top-level directories from
74
+ # the load path — it can't exclude files within a load path directory. The
75
+ # stubs live inside app/assets/builds/ (a single load path entry), so
76
+ # excluded_paths can't help.
77
+ #
78
+ # This fix prepends a filter onto Propshaft::Helper#app_stylesheets_paths to
79
+ # reject any asset whose logical path starts with "tailwind/" — these are
80
+ # always engine stubs generated by tailwindcss-rails, never user CSS.
81
+ # The compiled output is "tailwind.css" (no slash), so it passes through.
82
+ #
83
+ # This affects ALL engine stubs, not just Kiso's, because the problem is
84
+ # systemic to how tailwindcss-rails engine bundling interacts with Propshaft's
85
+ # :app resolution.
86
+ #
87
+ # See: propshaft-1.3.1 lib/propshaft/helper.rb#app_stylesheets_paths
88
+ # See: tailwindcss-rails lib/tailwindcss/engines.rb (stub generation)
89
+ # See: tailwindcss-rails lib/tailwindcss/engine.rb (excludes app/assets/tailwind
90
+ # but not app/assets/builds/tailwind)
91
+ initializer "kiso.filter_tailwind_stubs" do
92
+ ActiveSupport.on_load(:action_view) do
93
+ if defined?(Propshaft::Helper)
94
+ Propshaft::Helper.prepend(Kiso::PropshaftTailwindStubFilter)
95
+ end
96
+ end
97
+ end
98
+
54
99
  # Registers Kiso's component previews with Lookbook when available.
55
100
  initializer "kiso.lookbook", after: :load_config_initializers do
56
101
  if defined?(Lookbook)
@@ -0,0 +1,13 @@
1
+ module Kiso
2
+ # Filters tailwindcss-rails engine CSS stubs from Propshaft's :app stylesheet
3
+ # resolution. Prepended onto Propshaft::Helper by the engine initializer.
4
+ #
5
+ # Engine stubs have logical paths like "tailwind/kiso.css" — always under the
6
+ # "tailwind/" prefix. The compiled output is "tailwind.css" (no slash) and
7
+ # passes through unaffected.
8
+ module PropshaftTailwindStubFilter
9
+ def app_stylesheets_paths
10
+ super.reject { |path| path.start_with?("tailwind/") }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ module Kiso
2
+ module Themes
3
+ # OTP/PIN code input container. Positions a transparent real +<input>+
4
+ # over visual slot divs managed by the +kiso--input-otp+ Stimulus controller.
5
+ #
6
+ # @example
7
+ # InputOtp.render
8
+ InputOtp = ClassVariants.build(
9
+ base: "relative text-foreground flex items-center gap-2 has-disabled:opacity-50"
10
+ )
11
+
12
+ # Groups adjacent slots visually (e.g., first 3 digits, last 3 digits).
13
+ InputOtpGroup = ClassVariants.build(
14
+ base: "flex items-center"
15
+ )
16
+
17
+ # Individual character display slot with connected borders.
18
+ # Content and active state managed by +kiso--input-otp+ controller.
19
+ #
20
+ # @example
21
+ # InputOtpSlot.render(size: :md)
22
+ InputOtpSlot = ClassVariants.build(
23
+ base: "border-accented relative flex items-center justify-center " \
24
+ "border-y border-r shadow-xs transition-all outline-none " \
25
+ "first:rounded-l-md first:border-l last:rounded-r-md " \
26
+ "data-[active=true]:z-10 data-[active=true]:border-primary " \
27
+ "data-[active=true]:ring-[3px] data-[active=true]:ring-primary/50 " \
28
+ "aria-invalid:border-error " \
29
+ "data-[active=true]:aria-invalid:border-error " \
30
+ "data-[active=true]:aria-invalid:ring-error/20",
31
+ variants: {
32
+ size: {
33
+ sm: "size-8 text-xs",
34
+ md: "size-9 text-sm",
35
+ lg: "size-10 text-base"
36
+ }
37
+ },
38
+ defaults: {size: :md}
39
+ )
40
+
41
+ # Separator between groups (renders a minus icon by default).
42
+ InputOtpSeparator = ClassVariants.build(
43
+ base: "flex items-center text-muted-foreground [&>svg]:size-4"
44
+ )
45
+ end
46
+ end
@@ -0,0 +1,49 @@
1
+ module Kiso
2
+ module Themes
3
+ # Wrapper for the select native element and its chevron icon overlay.
4
+ #
5
+ # @example
6
+ # SelectNativeWrapper.render
7
+ SelectNativeWrapper = ClassVariants.build(
8
+ base: "relative w-full has-[select:disabled]:opacity-50"
9
+ )
10
+
11
+ # Native HTML `<select>` element with appearance-none and Kiso styling.
12
+ #
13
+ # @example
14
+ # SelectNative.render(variant: :outline, size: :md)
15
+ #
16
+ # Variants:
17
+ # - +variant+ — :outline (default), :soft, :ghost
18
+ # - +size+ — :sm, :md (default), :lg
19
+ SelectNative = ClassVariants.build(
20
+ base: "text-foreground w-full min-w-0 appearance-none rounded-md pr-9 outline-none " \
21
+ "transition-[color,box-shadow] " \
22
+ "disabled:pointer-events-none disabled:cursor-not-allowed " \
23
+ "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary " \
24
+ "aria-invalid:ring-error aria-invalid:focus-visible:ring-error",
25
+ variants: {
26
+ variant: {
27
+ outline: "bg-background ring ring-inset ring-accented shadow-xs",
28
+ soft: "bg-elevated/50 hover:bg-elevated focus:bg-elevated",
29
+ ghost: "bg-transparent hover:bg-elevated focus:bg-elevated"
30
+ },
31
+ size: {
32
+ sm: "h-8 px-2.5 py-1 text-sm",
33
+ md: "h-9 px-3 py-1 text-base md:text-sm",
34
+ lg: "h-10 px-3 py-2 text-base"
35
+ }
36
+ },
37
+ defaults: {variant: :outline, size: :md}
38
+ )
39
+
40
+ # Chevron icon overlay positioned inside the select native wrapper.
41
+ #
42
+ # @example
43
+ # SelectNativeIcon.render
44
+ SelectNativeIcon = ClassVariants.build(
45
+ base: "text-muted-foreground pointer-events-none absolute top-1/2 right-3.5 " \
46
+ "size-4 -translate-y-1/2 opacity-50 select-none"
47
+ )
48
+ end
49
+ end
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.1.1.pre"
5
+ VERSION = "0.2.1.pre"
6
6
  end
data/lib/kiso.rb CHANGED
@@ -5,6 +5,7 @@ require "tailwind_merge"
5
5
  require "kiso/version"
6
6
  require "kiso/configuration"
7
7
  require "kiso/theme_overrides"
8
+ require "kiso/propshaft_tailwind_stub_filter"
8
9
  require "kiso/engine"
9
10
  require "kiso/themes/shared"
10
11
  require "kiso/themes/badge"
@@ -23,6 +24,7 @@ require "kiso/themes/field_set"
23
24
  require "kiso/themes/input"
24
25
  require "kiso/themes/textarea"
25
26
  require "kiso/themes/input_group"
27
+ require "kiso/themes/input_otp"
26
28
  require "kiso/themes/checkbox"
27
29
  require "kiso/themes/radio_group"
28
30
  require "kiso/themes/switch"
@@ -30,6 +32,7 @@ require "kiso/themes/breadcrumb"
30
32
  require "kiso/themes/toggle"
31
33
  require "kiso/themes/toggle_group"
32
34
  require "kiso/themes/select"
35
+ require "kiso/themes/select_native"
33
36
  require "kiso/themes/popover"
34
37
  require "kiso/themes/combobox"
35
38
  require "kiso/themes/command"
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.1.1.pre
4
+ version: 0.2.1.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Clarke
@@ -84,6 +84,7 @@ files:
84
84
  - app/assets/tailwind/kiso/color-mode.css
85
85
  - app/assets/tailwind/kiso/dashboard.css
86
86
  - app/assets/tailwind/kiso/engine.css
87
+ - app/assets/tailwind/kiso/input-otp.css
87
88
  - app/assets/tailwind/kiso/radio-group.css
88
89
  - app/helpers/kiso/component_helper.rb
89
90
  - app/helpers/kiso/icon_helper.rb
@@ -94,6 +95,7 @@ files:
94
95
  - app/javascript/controllers/kiso/dropdown_menu_controller.js
95
96
  - app/javascript/controllers/kiso/index.d.ts
96
97
  - app/javascript/controllers/kiso/index.js
98
+ - app/javascript/controllers/kiso/input_otp_controller.js
97
99
  - app/javascript/controllers/kiso/popover_controller.js
98
100
  - app/javascript/controllers/kiso/select_controller.js
99
101
  - app/javascript/controllers/kiso/sidebar_controller.js
@@ -128,6 +130,7 @@ files:
128
130
  - app/views/kiso/components/_field_set.html.erb
129
131
  - app/views/kiso/components/_input.html.erb
130
132
  - app/views/kiso/components/_input_group.html.erb
133
+ - app/views/kiso/components/_input_otp.html.erb
131
134
  - app/views/kiso/components/_kbd.html.erb
132
135
  - app/views/kiso/components/_label.html.erb
133
136
  - app/views/kiso/components/_nav.html.erb
@@ -135,6 +138,7 @@ files:
135
138
  - app/views/kiso/components/_popover.html.erb
136
139
  - app/views/kiso/components/_radio_group.html.erb
137
140
  - app/views/kiso/components/_select.html.erb
141
+ - app/views/kiso/components/_select_native.html.erb
138
142
  - app/views/kiso/components/_separator.html.erb
139
143
  - app/views/kiso/components/_stats_card.html.erb
140
144
  - app/views/kiso/components/_stats_grid.html.erb
@@ -214,6 +218,9 @@ files:
214
218
  - app/views/kiso/components/field/_title.html.erb
215
219
  - app/views/kiso/components/field_set/_legend.html.erb
216
220
  - app/views/kiso/components/input_group/_addon.html.erb
221
+ - app/views/kiso/components/input_otp/_group.html.erb
222
+ - app/views/kiso/components/input_otp/_separator.html.erb
223
+ - app/views/kiso/components/input_otp/_slot.html.erb
217
224
  - app/views/kiso/components/kbd/_group.html.erb
218
225
  - app/views/kiso/components/nav/_item.html.erb
219
226
  - app/views/kiso/components/nav/_section.html.erb
@@ -261,6 +268,7 @@ files:
261
268
  - lib/kiso/cli/make.rb
262
269
  - lib/kiso/configuration.rb
263
270
  - lib/kiso/engine.rb
271
+ - lib/kiso/propshaft_tailwind_stub_filter.rb
264
272
  - lib/kiso/theme_overrides.rb
265
273
  - lib/kiso/themes/alert.rb
266
274
  - lib/kiso/themes/avatar.rb
@@ -281,6 +289,7 @@ files:
281
289
  - lib/kiso/themes/field_set.rb
282
290
  - lib/kiso/themes/input.rb
283
291
  - lib/kiso/themes/input_group.rb
292
+ - lib/kiso/themes/input_otp.rb
284
293
  - lib/kiso/themes/kbd.rb
285
294
  - lib/kiso/themes/label.rb
286
295
  - lib/kiso/themes/nav.rb
@@ -288,6 +297,7 @@ files:
288
297
  - lib/kiso/themes/popover.rb
289
298
  - lib/kiso/themes/radio_group.rb
290
299
  - lib/kiso/themes/select.rb
300
+ - lib/kiso/themes/select_native.rb
291
301
  - lib/kiso/themes/separator.rb
292
302
  - lib/kiso/themes/shared.rb
293
303
  - lib/kiso/themes/stats_card.rb