Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PinInput component #522

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions apps/docs/src/examples/pin-input.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
.pin-input {
display: flex;
flex-direction: column;
gap: 8px;
}

.pin-input__label {
color: hsl(240 6% 10%);
font-size: 14px;
font-weight: 500;
user-select: none;
}

.pin-input__description {
color: hsl(240 5% 26%);
font-size: 12px;
user-select: none;
}

.pin-input__error-message {
color: hsl(0 72% 51%);
font-size: 12px;
user-select: none;
}

.pin-input__control {
display: flex;
gap: 8px;
}

.pin-input__input {
width: 40px;
height: 40px;
border-radius: 6px;
font-size: 16px;
outline: none;
background-color: white;
text-align: center;
border: 1px solid hsl(240 6% 90%);
color: hsl(240 4% 16%);
transition:
border-color 250ms,
color 250ms;
}

.pin-input__input:hover {
border-color: hsl(240 5% 65%);
}

.pin-input__input:focus {
outline: 2px solid hsl(200 98% 39%);
outline-offset: 2px;
}

.pin-input__input::placeholder {
color: hsl(240 4% 46%);
}

.pin-input__input[data-invalid] {
border-color: hsl(0 72% 51%);
color: hsl(0 72% 51%);
}

[data-kb-theme="dark"] .pin-input__label {
color: hsl(240 5% 84%);
}

[data-kb-theme="dark"] .pin-input__input {
background-color: hsl(240 4% 16%);
border: 1px solid hsl(240 5% 34%);
color: hsl(0 100% 100% / 0.9);
}

[data-kb-theme="dark"] .pin-input__input:hover {
border-color: hsl(240 4% 46%);
}

[data-kb-theme="dark"] .pin-input__input::placeholder {
color: hsl(0 100% 100% / 0.5);
}

[data-kb-theme="dark"] .pin-input__input[data-invalid] {
border-color: hsl(0 72% 51%);
color: hsl(0 72% 51%);
}

[data-kb-theme="dark"] .pin-input__description {
color: hsl(240 5% 65%);
}
185 changes: 185 additions & 0 deletions apps/docs/src/examples/pin-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { Index, createSignal } from "solid-js";
import { PinInput } from "../../../../packages/core/src/pin-input";

import style from "./pin-input.module.css";

export function BasicExample() {
return (
<PinInput class={style["pin-input"]}>
<PinInput.Label class={style["pin-input__label"]}>PIN</PinInput.Label>
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
</PinInput>
);
}

export function DefaultValueExample() {
return (
<PinInput class={style["pin-input"]} defaultValue={["9", "9", "9"]}>
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
</PinInput>
);
}

export function ControlledExample() {
const [value, setValue] = createSignal([]);

return (
<>
<PinInput class={style["pin-input"]} value={value()} onChange={setValue}>
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
</PinInput>
<p class="not-prose text-sm mt-4">PIN code is: {value().join("")}</p>
</>
);
}

export function PlaceholderExample() {
return (
<PinInput class={style["pin-input"]} placeholder="*">
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
</PinInput>
);
}

export function BlurOnCompleteExample() {
return (
<PinInput class={style["pin-input"]} blurOnComplete>
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
</PinInput>
);
}

export function OTPExample() {
return (
<PinInput class={style["pin-input"]} otp>
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
</PinInput>
);
}

export function MaskExample() {
return (
<PinInput class={style["pin-input"]} mask>
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
</PinInput>
);
}

export function TypeExample() {
return (
<PinInput class={style["pin-input"]} type="alphanumeric">
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
</PinInput>
);
}

export function DescriptionExample() {
return (
<PinInput class={style["pin-input"]}>
<PinInput.Label class={style["pin-input__label"]}>PIN</PinInput.Label>
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
<PinInput.Description class={style["pin-input__description"]}>
Enter your 3 digit PIN code.
</PinInput.Description>
</PinInput>
);
}

export function ErrorMessageExample() {
const [value, setValue] = createSignal([]);

return (
<PinInput
class={style["pin-input"]}
value={value()}
onChange={setValue}
validationState={
value().every((element) => element === "9") ? "valid" : "invalid"
}
>
<PinInput.Label class={style["pin-input__label"]}>PIN</PinInput.Label>
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
<PinInput.ErrorMessage class={style["pin-input__error-message"]}>
Incorrect PIN. Please try again.
</PinInput.ErrorMessage>
</PinInput>
);
}

export function HTMLFormExample() {
let formRef: HTMLFormElement | undefined;

const onSubmit = (e: SubmitEvent) => {
e.preventDefault();
e.stopPropagation();

const formData = new FormData(formRef);

alert(JSON.stringify(Object.fromEntries(formData), null, 2));
};

return (
<form
ref={formRef}
onSubmit={onSubmit}
class="flex flex-col items-center space-y-6"
>
<PinInput class={style["pin-input"]} name="PIN">
<PinInput.Control class={style["pin-input__control"]}>
<Index each={Array(3)}>
{(_) => <PinInput.Input class={style["pin-input__input"]} />}
</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput>
<div class="flex space-x-2">
<button type="reset" class="kb-button">
Reset
</button>
<button type="submit" class="kb-button-primary">
Submit
</button>
</div>
</form>
);
}
5 changes: 5 additions & 0 deletions apps/docs/src/routes/docs/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ const CORE_NAV_SECTIONS: NavSection[] = [
title: "Pagination",
href: "/docs/core/components/pagination",
},
{
title: "Pin Input",
href: "/docs/core/components/pin-input",
status: "new",
},
{
title: "Popover",
href: "/docs/core/components/popover",
Expand Down
Loading
Loading