ui
ui copied to clipboard
feat(input-number): update docs
Input Number component wit min, max and steps
@dinogit is attempting to deploy a commit to the shadcn-pro Team on Vercel.
A member of the Team first needs to authorize it.
The latest updates on your projects. Learn more about Vercel for Git ↗︎
Name | Status | Preview | Comments | Updated (UTC) |
---|---|---|---|---|
ui | ✅ Ready (Inspect) | Visit Preview | 💬 Add feedback | Oct 24, 2023 11:24am |
This looks great. Thank you.
Some quick notes: I wonder if we can make the component composable. Something like:
<NumberField>
<NumberFieldInput />
<NumberFieldIncrement />
<NumberFieldDecrement />
</NumberField>
sure, will give it a try
Hey guys! I've recently been working on a <NumberInput />
component based on react-aria and following the “shadcn/ui” philosophy. react-aria provides incredible hooks for these kinds of components.
https://github.com/shadcn-ui/ui/assets/69400730/8022052c-ec21-497d-b3ef-8d0d4f2de9a2
I’m sharing the full source code here in case it could be helpful in any way.
Usage:
<NumberInput
label="% Example"
defaultValue={0.1}
minValue={0}
formatOptions={{
style: "percent",
notation: "compact",
}}
/>
Source:
"use client";
import * as React from "react";
import { useNumberFieldState } from "react-stately";
import {
type AriaNumberFieldProps,
useLocale,
useNumberField,
useButton,
type AriaButtonOptions,
} from "react-aria";
import { ChevronUp, ChevronDown } from "lucide-react";
import { Input } from "./input";
import { Label } from "./label";
import { cn } from "./utils/cn";
export const NumberInput = ({
className,
...props
}: {
className?: string;
} & AriaNumberFieldProps) => {
const { locale } = useLocale();
const state = useNumberFieldState({ ...props, locale });
const inputRef = React.useRef(null);
const {
labelProps,
groupProps,
inputProps,
incrementButtonProps,
decrementButtonProps,
} = useNumberField(props, state, inputRef);
return (
<div className={className}>
<Label {...labelProps}>{props.label}</Label>
<div className="grid h-10 grid-cols-[auto_2.3rem]" {...groupProps}>
<Input
{...inputProps}
className={cn(
inputProps.className,
"row-span-2 h-full rounded-r-none border-r-0",
)}
ref={inputRef}
/>
<AriaButton
className="rounded-tr-md border px-2 hover:bg-border"
{...incrementButtonProps}
>
<ChevronUp className="mx-auto" size="1em" />
</AriaButton>
<AriaButton
className="rounded-br-md border-x border-b px-2 hover:bg-border"
{...decrementButtonProps}
>
<ChevronDown className="mx-auto" size="1em" />
</AriaButton>
</div>
</div>
);
};
const AriaButton = ({
className,
children,
...props
}: {
className?: string;
children: React.ReactNode;
} & AriaButtonOptions<"button">) => {
const ref = React.useRef(null);
const { buttonProps } = useButton(props, ref);
return (
<button
{...buttonProps}
className={cn(buttonProps.className, className)}
ref={ref}
>
{children}
</button>
);
};
Hey everyone! I've been working on a solution for a personal project and thought I'd share.
Source:
'use client'
import * as React from 'react'
import { useLocale } from 'react-aria'
import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'
import { type NumberFieldState, useNumberFieldState, NumberFieldStateOptions } from 'react-stately'
import { Input, InputProps } from '@/src/components/ui/input'
import { Button, ButtonProps } from '@/src/components/ui/button'
import { cn } from '@/src/lib/utils'
import { ControllerRenderProps } from 'react-hook-form'
type NumberFieldContextValue = NumberFieldState
const NumberFieldContext = React.createContext<NumberFieldContextValue>(
{} as NumberFieldContextValue
)
const useNumberField = () => {
const numberFieldContext = React.useContext(NumberFieldContext)
if (!numberFieldContext) {
throw new Error('useNumberField should be used within <NumberField>')
}
return numberFieldContext
}
type NumberFieldProps = Partial<NumberFieldStateOptions> & ControllerRenderProps
const NumberField = React.forwardRef<HTMLDivElement, React.PropsWithChildren<NumberFieldProps>>(
({ children, ...props }, ref) => {
const { locale } = useLocale()
const state = useNumberFieldState({ ...props, locale })
return (
<NumberFieldContext.Provider value={state}>
<div ref={ref} className={cn('flex rounded-md gap-1')}>
{children}
</div>
</NumberFieldContext.Provider>
)
}
)
NumberField.displayName = 'NumberField'
const NumberFieldIncrement = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, ...props }, ref) => {
const state = useNumberField()
return (
<Button
variant={'outline'}
size={'icon'}
type="button"
className={cn('aspect-square', className)}
onClick={state.increment}
ref={ref}
{...props}
>
<ChevronUpIcon />
</Button>
)
}
)
NumberFieldIncrement.displayName = 'NumberFieldIncrement'
const NumberFieldDecrement = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, ...props }, ref) => {
const state = useNumberField()
return (
<Button
variant={'outline'}
size={'icon'}
type="button"
className={cn('aspect-square', className)}
onClick={state.decrement}
ref={ref}
{...props}
>
<ChevronDownIcon />
</Button>
)
}
)
NumberFieldDecrement.displayName = 'NumberFieldDecrement'
const NumberFieldInput = React.forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => {
const state = useNumberField()
return (
<Input
ref={ref}
type="number"
className={cn(
'[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none',
className
)}
value={state.inputValue}
onChange={(e) => state.setInputValue(e.target.value)}
{...props}
/>
)
}
)
NumberFieldInput.displayName = 'NumberFieldInput'
export { NumberField, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement }
Usage:
// ...
<FormField
control={form.control}
name="exampleNumberField"
render={({ field }) => (
<FormItem>
<FormLabel>Example number Field</FormLabel>
<FormControl>
<NumberField {...field}>
<NumberFieldInput />
<NumberFieldDecrement />
<NumberFieldIncrement />
</NumberField>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
// ...
Example:
https://github.com/shadcn-ui/ui/assets/149010961/cbd9f7b1-4d4c-4315-bd58-646810165fb4