first commit
linter / quality (push) Canceled after 0s
tests / ci (8.3) (push) Canceled after 0s
tests / ci (8.4) (push) Canceled after 0s
tests / ci (8.5) (push) Canceled after 0s

This commit is contained in:
salam committed 2026-10-06 18:53:03 +02:00
commit e687b4437b
418 files changed
+33577

No files matched your search

@@ -0,0 +1,71 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { cn } from '@/lib/utils';
import { setInputOTPContext } from './context';
let {
value = $bindable(''),
maxlength = 6,
disabled = false,
id,
class: className = '',
children,
...rest
}: {
value?: string;
maxlength?: number;
disabled?: boolean;
id?: string;
class?: string;
children?: Snippet;
[key: string]: unknown;
} = $props();
let inputRef: HTMLInputElement;
let isFocused = $state(false);
const activeIndex = $derived(Math.min(value.length, maxlength - 1));
setInputOTPContext({
value: () => value,
activeIndex: () => (isFocused ? activeIndex : -1),
isFocused: () => isFocused,
focus: () => inputRef?.focus(),
});
function handleInput(e: Event) {
const target = e.target as HTMLInputElement;
value = target.value.replace(/[^0-9]/g, '').slice(0, maxlength);
}
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Backspace' && value.length > 0) {
e.preventDefault();
value = value.slice(0, -1);
}
}
</script>
<div
data-slot="input-otp"
class={cn('relative flex items-center gap-2 has-disabled:opacity-50', className)}
role="group"
{...rest}
>
<input
bind:this={inputRef}
{id}
{disabled}
type="text"
inputmode="numeric"
autocomplete="one-time-code"
{value}
oninput={handleInput}
onkeydown={handleKeyDown}
onfocus={() => (isFocused = true)}
onblur={() => (isFocused = false)}
class="absolute inset-0 z-10 h-full w-full cursor-text opacity-0 disabled:cursor-not-allowed"
/>
<div class="pointer-events-none">
{@render children?.()}
</div>
</div>
@@ -0,0 +1,16 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { cn } from '@/lib/utils';
let {
class: className = '',
children,
}: {
class?: string;
children?: Snippet;
} = $props();
</script>
<div data-slot="input-otp-group" class={cn('flex items-center', className)}>
{@render children?.()}
</div>
@@ -0,0 +1,7 @@
<script lang="ts">
import Minus from 'lucide-svelte/icons/minus';
</script>
<div data-slot="input-otp-separator" role="separator">
<Minus />
</div>
@@ -0,0 +1,34 @@
<script lang="ts">
import { cn } from '@/lib/utils';
import { getInputOTPContext } from './context';
let {
index,
class: className = '',
}: {
index: number;
class?: string;
} = $props();
const ctx = getInputOTPContext();
const char = $derived(ctx.value()[index]);
const isActive = $derived(ctx.isFocused() && ctx.activeIndex() === index);
const hasFakeCaret = $derived(isActive && !char);
</script>
<div
data-slot="input-otp-slot"
data-active={isActive}
class={cn(
'data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]',
className,
)}
>
{char ?? ''}
{#if hasFakeCaret}
<div class="pointer-events-none absolute inset-0 flex items-center justify-center">
<div class="animate-caret-blink bg-foreground h-4 w-px duration-1000"></div>
</div>
{/if}
</div>
@@ -0,0 +1,18 @@
import { getContext, setContext } from 'svelte';
export const INPUT_OTP_CONTEXT = Symbol('input-otp');
export type InputOTPContext = {
value: () => string;
activeIndex: () => number;
isFocused: () => boolean;
focus: () => void;
};
export function setInputOTPContext(ctx: InputOTPContext): void {
setContext(INPUT_OTP_CONTEXT, ctx);
}
export function getInputOTPContext(): InputOTPContext {
return getContext<InputOTPContext>(INPUT_OTP_CONTEXT);
}
@@ -0,0 +1,4 @@
export { default as InputOTP } from './InputOTP.svelte';
export { default as InputOTPGroup } from './InputOTPGroup.svelte';
export { default as InputOTPSeparator } from './InputOTPSeparator.svelte';
export { default as InputOTPSlot } from './InputOTPSlot.svelte';