Input-otp
Basic
// Package inputotp holds the site's example gsx components for
// ui/input-otp. "input-otp" can't be a Go package name (hyphen), so the
// directory drops it — same selectbox/switchctl-style workaround as
// select/switch, not a Go-keyword issue this time. The registered example
// key stays the hyphenated "input-otp" (see inputotp.go).
package inputotp
import "github.com/gsxhq/gsxui/ui"
// Basic mirrors shadcn's own input-otp-demo.tsx: a 6-digit code split into
// two 3-slot groups by one InputOTPSeparator. maxlength="6" falls through
// via attrs onto the real hidden input (see ui/input-otp.gsx's own doc
// comment — no explicit Go param for it, same convention as ui/input.gsx).
// No InputOTPSlot here takes an index: ui/input-otp.js stamps data-index
// 0..5 positionally in DOM order at mount, spanning the separator boundary
// — the deliberate API departure from shadcn's own index prop, see
// ui/input-otp.gsx's own ADAPT doc comment.
component Basic() {
<ui.InputOTP maxlength="6" aria-invalid="true">
<ui.InputOTPGroup>
<ui.InputOTPSlot aria-invalid="true"/>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
</ui.InputOTPGroup>
<ui.InputOTPSeparator/>
<ui.InputOTPGroup>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
</ui.InputOTPGroup>
</ui.InputOTP>
}
Pattern
Digits only. data-gsxui-input-otp-pattern takes an unanchored per-character class, not a whole-string-anchored regex — see ui/input-otp.gsx's own doc comment for why.
package inputotp
import "github.com/gsxhq/gsxui/ui"
// Pattern mirrors shadcn's own input-otp-pattern.tsx, which feeds the
// input-otp library's REGEXP_ONLY_DIGITS_AND_CHARS to both native
// validation and live keystroke filtering with a single anchored regex.
// This port needs two DIFFERENT attributes for those two different jobs —
// see ui/input-otp.gsx's own DECISION doc comment: the native HTML5
// pattern attribute stays whole-string-anchored ("[0-9]*", validity only,
// browser-native), while data-gsxui-input-otp-pattern is the UNANCHORED
// per-character class ui/input-otp.js actually tests one keystroke at a
// time against ("[0-9]", not "[0-9]*"). Copying shadcn's own anchored
// pattern constant into the per-character attribute would reject every
// character typed.
component Pattern() {
<div class="flex flex-col gap-2">
<ui.InputOTP maxlength="6" pattern="[0-9]*" data-gsxui-input-otp-pattern="[0-9]">
<ui.InputOTPGroup>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
</ui.InputOTPGroup>
</ui.InputOTP>
<p class="text-sm text-muted-foreground">
Digits only. data-gsxui-input-otp-pattern takes an unanchored per-character class, not a whole-string-anchored
regex — see ui/input-otp.gsx's own doc comment for why.
</p>
</div>
}
Separator
package inputotp
import "github.com/gsxhq/gsxui/ui"
// Separator mirrors shadcn's own input-otp-separator.tsx: three 2-slot
// groups split by two InputOTPSeparators — the strongest proof case for
// DOM-order index stamping (ui/input-otp.js walks every slot in source
// order across all three group boundaries and stamps data-index 0..5
// positionally at mount; see ui/input-otp.gsx's own ADAPT doc comment).
component Separator() {
<ui.InputOTP maxlength="6">
<ui.InputOTPGroup>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
</ui.InputOTPGroup>
<ui.InputOTPSeparator/>
<ui.InputOTPGroup>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
</ui.InputOTPGroup>
<ui.InputOTPSeparator/>
<ui.InputOTPGroup>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
</ui.InputOTPGroup>
</ui.InputOTP>
}
RTL
package inputotp
import "github.com/gsxhq/gsxui/ui"
// Rtl wraps Basic's own 6-digit/2-group OTP layout in an Arabic dir="rtl"
// form context (a label plus the surrounding div), but does NOT force
// dir="rtl" onto the OTP widget itself: ui/input-otp.gsx pins dir="ltr" on
// both InputOTP's own root and InputOTPGroup by design (see its own ADAPT
// doc comments) so the digit slots always read left-to-right regardless of
// document direction, matching shadcn's own input-otp behavior under RTL.
// The label and page chrome flip to RTL; the OTP slots themselves don't.
component Rtl() {
<div dir="rtl" lang="ar">
<label class="mb-2 block text-sm font-medium">رمز التحقق</label>
<ui.InputOTP maxlength="6" aria-invalid="true">
<ui.InputOTPGroup>
<ui.InputOTPSlot aria-invalid="true"/>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
</ui.InputOTPGroup>
<ui.InputOTPSeparator/>
<ui.InputOTPGroup>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
<ui.InputOTPSlot/>
</ui.InputOTPGroup>
</ui.InputOTP>
</div>
}