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 +4 -4
- data/CHANGELOG.md +19 -1
- data/README.md +2 -4
- data/app/assets/tailwind/kiso/dashboard.css +18 -0
- data/app/assets/tailwind/kiso/engine.css +14 -0
- data/app/assets/tailwind/kiso/input-otp.css +10 -0
- data/app/javascript/controllers/kiso/index.js +3 -0
- data/app/javascript/controllers/kiso/input_otp_controller.js +195 -0
- data/app/views/kiso/components/_input_otp.html.erb +22 -0
- data/app/views/kiso/components/_select_native.html.erb +16 -0
- data/app/views/kiso/components/dashboard_navbar/_toggle.html.erb +1 -1
- data/app/views/kiso/components/dashboard_sidebar/_toggle.html.erb +1 -1
- data/app/views/kiso/components/input_otp/_group.html.erb +7 -0
- data/app/views/kiso/components/input_otp/_separator.html.erb +8 -0
- data/app/views/kiso/components/input_otp/_slot.html.erb +11 -0
- data/lib/kiso/configuration.rb +1 -0
- data/lib/kiso/engine.rb +45 -0
- data/lib/kiso/propshaft_tailwind_stub_filter.rb +13 -0
- data/lib/kiso/themes/input_otp.rb +46 -0
- data/lib/kiso/themes/select_native.rb +49 -0
- data/lib/kiso/version.rb +1 -1
- data/lib/kiso.rb +3 -0
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc81659a56fbdc6091f37b314d096fa6eeca6a430284f594a2666e7646b49bed
|
|
4
|
+
data.tar.gz: 75d5f93f6d15684a044b340c1159fca84d0ac8285e16bf859c6805ceef35da4a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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,
|
|
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
|
+
}
|
|
@@ -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 %>
|
|
@@ -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 %>
|
data/lib/kiso/configuration.rb
CHANGED
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
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.
|
|
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
|