Skip to content

Commit

Permalink
next: Input OTP component (#1375)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Oct 27, 2024
1 parent ee4734e commit 385b243
Show file tree
Hide file tree
Showing 26 changed files with 669 additions and 11 deletions.
36 changes: 26 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sites/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"acorn": "^8.13.0",
"acorn-typescript": "^1.4.13",
"autoprefixer": "^10.4.19",
"bits-ui": "1.0.0-next.28",
"bits-ui": "1.0.0-next.30",
"clsx": "^2.1.1",
"concurrently": "^9.0.1",
"d3-scale": "^4.0.2",
Expand Down
64 changes: 64 additions & 0 deletions sites/docs/src/__registry__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,38 @@ export const Index = {
files: ["../lib/registry/default/example/input-file.svelte"],
raw: () => import("../lib/registry/default/example/input-file.svelte?raw").then((m) => m.default),
},
"input-otp-demo": {
name: "input-otp-demo",
type: "registry:example",
registryDependencies: ["input-otp"],
component: () => import("../lib/registry/default/example/input-otp-demo.svelte").then((m) => m.default),
files: ["../lib/registry/default/example/input-otp-demo.svelte"],
raw: () => import("../lib/registry/default/example/input-otp-demo.svelte?raw").then((m) => m.default),
},
"input-otp-form": {
name: "input-otp-form",
type: "registry:example",
registryDependencies: ["input-otp","form"],
component: () => import("../lib/registry/default/example/input-otp-form.svelte").then((m) => m.default),
files: ["../lib/registry/default/example/input-otp-form.svelte"],
raw: () => import("../lib/registry/default/example/input-otp-form.svelte?raw").then((m) => m.default),
},
"input-otp-pattern": {
name: "input-otp-pattern",
type: "registry:example",
registryDependencies: ["input-otp"],
component: () => import("../lib/registry/default/example/input-otp-pattern.svelte").then((m) => m.default),
files: ["../lib/registry/default/example/input-otp-pattern.svelte"],
raw: () => import("../lib/registry/default/example/input-otp-pattern.svelte?raw").then((m) => m.default),
},
"input-otp-separator": {
name: "input-otp-separator",
type: "registry:example",
registryDependencies: ["input-otp"],
component: () => import("../lib/registry/default/example/input-otp-separator.svelte").then((m) => m.default),
files: ["../lib/registry/default/example/input-otp-separator.svelte"],
raw: () => import("../lib/registry/default/example/input-otp-separator.svelte?raw").then((m) => m.default),
},
"input-with-button": {
name: "input-with-button",
type: "registry:example",
Expand Down Expand Up @@ -1564,6 +1596,38 @@ export const Index = {
files: ["../lib/registry/new-york/example/input-file.svelte"],
raw: () => import("../lib/registry/new-york/example/input-file.svelte?raw").then((m) => m.default),
},
"input-otp-demo": {
name: "input-otp-demo",
type: "registry:example",
registryDependencies: ["input-otp"],
component: () => import("../lib/registry/new-york/example/input-otp-demo.svelte").then((m) => m.default),
files: ["../lib/registry/new-york/example/input-otp-demo.svelte"],
raw: () => import("../lib/registry/new-york/example/input-otp-demo.svelte?raw").then((m) => m.default),
},
"input-otp-form": {
name: "input-otp-form",
type: "registry:example",
registryDependencies: ["input-otp","form"],
component: () => import("../lib/registry/new-york/example/input-otp-form.svelte").then((m) => m.default),
files: ["../lib/registry/new-york/example/input-otp-form.svelte"],
raw: () => import("../lib/registry/new-york/example/input-otp-form.svelte?raw").then((m) => m.default),
},
"input-otp-pattern": {
name: "input-otp-pattern",
type: "registry:example",
registryDependencies: ["input-otp"],
component: () => import("../lib/registry/new-york/example/input-otp-pattern.svelte").then((m) => m.default),
files: ["../lib/registry/new-york/example/input-otp-pattern.svelte"],
raw: () => import("../lib/registry/new-york/example/input-otp-pattern.svelte?raw").then((m) => m.default),
},
"input-otp-separator": {
name: "input-otp-separator",
type: "registry:example",
registryDependencies: ["input-otp"],
component: () => import("../lib/registry/new-york/example/input-otp-separator.svelte").then((m) => m.default),
files: ["../lib/registry/new-york/example/input-otp-separator.svelte"],
raw: () => import("../lib/registry/new-york/example/input-otp-separator.svelte?raw").then((m) => m.default),
},
"input-with-button": {
name: "input-with-button",
type: "registry:example",
Expand Down
129 changes: 129 additions & 0 deletions sites/docs/src/content/components/input-otp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
title: Input OTP
description: Accessible one-time password component with copy paste functionality.
component: true
links:
source: https://github.com/huntabyte/shadcn-svelte/tree/main/sites/docs/src/lib/registry/default/ui/input-otp
doc: https://next.bits-ui.com/docs/components/pin-input
api: https://next.bits-ui.com/docs/components/pin-input#api-reference
---

<script>
import { ComponentPreview, PMAddComp, PMInstall, Step, Steps, InstallTabs } from '$lib/components/docs';
</script>

<ComponentPreview name="input-otp-demo">

<div></div>

</ComponentPreview>

## About

Input OTP is built on top of Bits UI's [PinInput](https://next.bits-ui.com/docs/components/pin-input) which is inspired by [@guilherme_rodz](https://twitter.com/guilherme_rodz)'s Input OTP component.

## Installation

<InstallTabs>
{#snippet cli()}
<PMAddComp name="input-otp" />
{/snippet}
{#snippet manual()}
<Steps>
<Step>

Install `bits-ui`:

</Step>
<PMInstall command="bits-ui -D" />
<Step>Copy and paste the component source files linked at the top of this page into your project.</Step>
</Steps>
{/snippet}
</InstallTabs>

## Usage

```svelte
<script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp";
</script>
<InputOTP.Root maxlength={6}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells.slice(0, 3) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(0, 3) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root>
```

## Examples

### Pattern

Use the `pattern` prop to define a custom pattern for the OTP input.

<ComponentPreview name="input-otp-pattern">

<div></div>

</ComponentPreview>

```svelte showLineNumbers {3,6}
<script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "bits-ui";
</script>
<InputOTP.Root maxlength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
<!-- ... -->
</InputOTP.Root>
```

### Separator

You can use the `InputOTP.Separator` component to add a separator between the groups of cells.

<ComponentPreview name="input-otp-separator">

<div></div>

</ComponentPreview>

```svelte showLineNumbers
<script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp";
</script>
<InputOTP.Root maxlength={4}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells.slice(0, 2) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(2, 4) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root>
```

### Form

<ComponentPreview name="input-otp-form">

<div></div>

</ComponentPreview>
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
let component: Promise<Component> = $state() as Promise<Component>;
$effect(() => {
console.log(Index[$config.style][name]);
component = Index[$config.style][name]?.component() as Promise<Component>;
});
</script>
Expand Down
6 changes: 6 additions & 0 deletions sites/docs/src/lib/config/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,12 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/input",
items: [],
},
{
title: "Input OTP",
href: "/docs/components/input-otp",
items: [],
label: "New",
},
{
title: "Label",
href: "/docs/components/label",
Expand Down
19 changes: 19 additions & 0 deletions sites/docs/src/lib/registry/default/example/input-otp-demo.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import * as InputOTP from "$lib/registry/default/ui/input-otp/index.js";
</script>

<InputOTP.Root maxlength={6}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells.slice(0, 3) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(3, 6) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root>
Loading

0 comments on commit 385b243

Please sign in to comment.