kiso 0.1.1.pre → 0.2.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -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/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 +2 -0
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a28e88755cea2967813e27293413e587cef78cd5466240e16034df5c512b887d
|
|
4
|
+
data.tar.gz: 6f8cee28fae6476f598f384ef4898726499f39e0ed99e4a785f64ef08f517205
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3caa5d60dd0339c04dfd2c7cd03695dcac54d9c9c91fe6b082b9fb6adc79bca73a8e7d9c0de9c91d50bba095f1dd738032d2be04cc77b558ba6ba9e45144e7d9
|
|
7
|
+
data.tar.gz: 97b58647c61a6460cb72a9633bef14037026fd7c1be9d22b073e98f3de7c28d830b8f24f1fc628bc145d91b996954f7d7c22f49cc7a54ac9c35e420b3c877f1f
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0.pre] - 2026-03-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- 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.
|
|
15
|
+
- SelectNative component — styled native `<select>` with chevron icon overlay. Variant × size axes matching Input (outline/soft/ghost, sm/md/lg). No JavaScript required.
|
|
16
|
+
- 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`).
|
|
17
|
+
- Custom toggle icon override — sidebar toggle and collapse buttons accept a block to replace the default icon.
|
|
18
|
+
- 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.
|
|
19
|
+
|
|
10
20
|
## [0.1.1.pre] - 2026-03-03
|
|
11
21
|
|
|
12
22
|
### Added
|
|
@@ -45,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
45
55
|
- Lookbook component previews
|
|
46
56
|
- Bridgetown documentation site
|
|
47
57
|
|
|
48
|
-
[Unreleased]: https://github.com/steveclarke/kiso/compare/v0.
|
|
58
|
+
[Unreleased]: https://github.com/steveclarke/kiso/compare/v0.2.0.pre...HEAD
|
|
59
|
+
[0.2.0.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.2.0.pre
|
|
49
60
|
[0.1.1.pre]: https://github.com/steveclarke/kiso/releases/tag/v0.1.1.pre
|
|
50
61
|
[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
|
@@ -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
|
@@ -23,6 +23,7 @@ require "kiso/themes/field_set"
|
|
|
23
23
|
require "kiso/themes/input"
|
|
24
24
|
require "kiso/themes/textarea"
|
|
25
25
|
require "kiso/themes/input_group"
|
|
26
|
+
require "kiso/themes/input_otp"
|
|
26
27
|
require "kiso/themes/checkbox"
|
|
27
28
|
require "kiso/themes/radio_group"
|
|
28
29
|
require "kiso/themes/switch"
|
|
@@ -30,6 +31,7 @@ require "kiso/themes/breadcrumb"
|
|
|
30
31
|
require "kiso/themes/toggle"
|
|
31
32
|
require "kiso/themes/toggle_group"
|
|
32
33
|
require "kiso/themes/select"
|
|
34
|
+
require "kiso/themes/select_native"
|
|
33
35
|
require "kiso/themes/popover"
|
|
34
36
|
require "kiso/themes/combobox"
|
|
35
37
|
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.0.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
|
|
@@ -281,6 +288,7 @@ files:
|
|
|
281
288
|
- lib/kiso/themes/field_set.rb
|
|
282
289
|
- lib/kiso/themes/input.rb
|
|
283
290
|
- lib/kiso/themes/input_group.rb
|
|
291
|
+
- lib/kiso/themes/input_otp.rb
|
|
284
292
|
- lib/kiso/themes/kbd.rb
|
|
285
293
|
- lib/kiso/themes/label.rb
|
|
286
294
|
- lib/kiso/themes/nav.rb
|
|
@@ -288,6 +296,7 @@ files:
|
|
|
288
296
|
- lib/kiso/themes/popover.rb
|
|
289
297
|
- lib/kiso/themes/radio_group.rb
|
|
290
298
|
- lib/kiso/themes/select.rb
|
|
299
|
+
- lib/kiso/themes/select_native.rb
|
|
291
300
|
- lib/kiso/themes/separator.rb
|
|
292
301
|
- lib/kiso/themes/shared.rb
|
|
293
302
|
- lib/kiso/themes/stats_card.rb
|