Pin Input
For pin or verification codes with auto-focus transfer and masking options.
import { PinInput, type PinInputProps } from '~/components/ui/pin-input'
export const Demo = (props: PinInputProps) => {
return (
<PinInput placeholder="0" onValueComplete={(e) => alert(e.valueAsString)} {...props}>
Pin Input
</PinInput>
)
}
import { PinInput, type PinInputProps } from '~/components/ui/pin-input'
export const Demo = (props: PinInputProps) => {
return (
<PinInput placeholder="0" onValueComplete={(e) => alert(e.valueAsString)} {...props}>
Pin Input
</PinInput>
)
}
Usage
import { PinInput } from '~/components/ui/pin-input'
Installation
npx @park-ui/cli components add pin-input
1
Add Styled Primitive
Copy the code snippet below into ~/components/ui/styled/pin-input.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { PinInput } from '@ark-ui/react/pin-input'
import { type PinInputVariantProps, pinInput } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(pinInput)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, PinInput.RootProviderBaseProps>, PinInputVariantProps>
>(PinInput.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, PinInput.RootBaseProps>, PinInputVariantProps>
>(PinInput.Root, 'root', { forwardProps: ['mask'] })
export const Control = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, PinInput.ControlBaseProps>
>(PinInput.Control, 'control')
export const Input = withContext<
HTMLInputElement,
Assign<HTMLStyledProps<'input'>, PinInput.InputBaseProps>
>(PinInput.Input, 'input')
export const Label = withContext<
HTMLLabelElement,
Assign<HTMLStyledProps<'label'>, PinInput.LabelBaseProps>
>(PinInput.Label, 'label')
export {
PinInputContext as Context,
PinInputHiddenInput as HiddenInput,
} from '@ark-ui/react/pin-input'
import { type Assign, PinInput } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type PinInputVariantProps, pinInput } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(pinInput)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, PinInput.RootProviderBaseProps>, PinInputVariantProps>
>(PinInput.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, PinInput.RootBaseProps>, PinInputVariantProps>
>(PinInput.Root, 'root', { forwardProps: ['mask'] })
export const Control = withContext<Assign<HTMLStyledProps<'div'>, PinInput.ControlBaseProps>>(
PinInput.Control,
'control',
)
export const Input = withContext<Assign<HTMLStyledProps<'input'>, PinInput.InputBaseProps>>(
PinInput.Input,
'input',
)
export const Label = withContext<Assign<HTMLStyledProps<'label'>, PinInput.LabelBaseProps>>(
PinInput.Label,
'label',
)
export {
PinInputContext as Context,
PinInputHiddenInput as HiddenInput,
} from '@ark-ui/solid'
No snippet found
2
Add Composition
Copy the composition snippet below into ~/components/ui/pin-input.tsx
import { forwardRef } from 'react'
import { Input } from './input'
import * as StyledPinInput from './styled/pin-input'
export interface PinInputProps extends StyledPinInput.RootProps {
/**
* The number of inputs to render.
* @default 4
*/
length?: number
}
export const PinInput = forwardRef<HTMLDivElement, PinInputProps>((props, ref) => {
const { children, length = 4, ...rootProps } = props
return (
<StyledPinInput.Root ref={ref} {...rootProps}>
{children && <StyledPinInput.Label>{children}</StyledPinInput.Label>}
<StyledPinInput.Control>
{Array.from({ length }, (_, index) => index).map((id, index) => (
<StyledPinInput.Input key={id} index={index} asChild>
<Input size={rootProps.size} />
</StyledPinInput.Input>
))}
</StyledPinInput.Control>
<StyledPinInput.HiddenInput />
</StyledPinInput.Root>
)
})
PinInput.displayName = 'PinInput'
import { Index, Show, children } from 'solid-js'
import { Input } from './input'
import * as StyledPinInput from './styled/pin-input'
export interface PinInputProps extends StyledPinInput.RootProps {
/**
* The number of inputs to render.
* @default 4
*/
length?: number
}
export const PinInput = (props: PinInputProps) => {
const getChildren = children(() => props.children)
return (
<StyledPinInput.Root {...props}>
<Show when={getChildren()}>
<StyledPinInput.Label>{getChildren()}</StyledPinInput.Label>
</Show>
<StyledPinInput.Control>
<Index each={Array.from({ length: props.length ?? 4 }, (_, index) => index)}>
{(index) => (
<StyledPinInput.Input
index={index()}
asChild={(inputProps) => <Input {...inputProps()} size={props.size} />}
/>
)}
</Index>
</StyledPinInput.Control>
<StyledPinInput.HiddenInput />
</StyledPinInput.Root>
)
}
3
Integrate Recipe
If you're not using @park-ui/preset
, add the following recipe to yourpanda.config.ts
:
import { pinInputAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const pinInput = defineSlotRecipe({
className: 'pinInput',
slots: pinInputAnatomy.keys(),
base: {
root: {
display: 'flex',
flexDirection: 'column',
gap: '1.5',
},
control: {
display: 'flex',
gap: '2',
},
label: {
color: 'fg.default',
fontWeight: 'medium',
},
input: {
px: '0!',
textAlign: 'center',
},
},
defaultVariants: {
size: 'md',
},
variants: {
size: {
xs: {
label: {
textStyle: 'sm',
},
input: {
width: '8',
},
},
sm: {
label: {
textStyle: 'sm',
},
input: {
width: '9',
},
},
md: {
label: {
textStyle: 'sm',
},
input: {
width: '10',
},
},
lg: {
label: {
textStyle: 'sm',
},
input: {
width: '11',
},
},
xl: {
label: {
textStyle: 'md',
},
input: {
width: '12',
},
},
'2xl': {
label: {
textStyle: 'md',
},
input: {
width: '16',
},
},
},
},
})