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: Create a new component NumberField #4864

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

thought7878
Copy link

@thought7878 thought7878 commented Sep 16, 2024

Anatomy of NumberField

The NumberField component is a flexible and accessible input field for numerical values, built with React and integrated with shadcn/ui. It offers customizable increment and decrement buttons, supports various layouts, and includes built-in error handling and validation. This component is designed to be highly adaptable, making it suitable for a wide range of applications where numerical input is required.

This component set includes the following sub-components:

<NumberField name="..." value="..." onChange={...} className="...">
  <NumberFieldLabel>{/* label */}</NumberFieldLabel>
  <NumberFieldGroup>
    <NumberFieldIncrement>{/* increment icon */}</NumberFieldIncrement>
    <NumberFieldInput className="..." />
    <NumberFieldDecrement>{/* decrement icon */}</NumberFieldDecrement>
  </NumberFieldGroup>
  <NumberFieldError />
</NumberField>
introduction

Features

Key features include:

  1. Customizable button placement (inside or outside the input)
  2. Flexible label positioning
  3. Internationalization support
  4. Accessibility features using React Aria
  5. Integration with shadcn/ui components and styling
  6. Compound component structure for easy customization

Examples

Default

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldLabel,
} from "@/components/ui/number-field"

export function NumberFieldDemo() {
  return (
    <NumberField>
      <NumberFieldLabel>Amount</NumberFieldLabel>
      <NumberFieldGroup>
        <NumberFieldIncrement />
        <NumberFieldInput />
        <NumberFieldDecrement />
      </NumberFieldGroup>
    </NumberField>
  )
}
image

Form

"use client"

import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"

import { toast } from "@/components/hooks/use-toast"
import { Button } from "@/components/ui/button"
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form"
import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
} from "@/components/ui/number-field"

const formSchema = z.object({
  amount: z
    .number({
      required_error: "Amount is required.",
    })
    .min(0, { message: "Amount must be greater than 0." })
    .max(10, { message: "Amount must be less than 11." }),
})

export function NumberFieldForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
  })

  function onSubmit(data: z.infer<typeof formSchema>) {
    toast({
      title: "You submitted the following values:",
      description: (
        <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
          <code className="text-white">{JSON.stringify(data, null, 2)}</code>
        </pre>
      ),
    })
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
        <FormField
          control={form.control}
          name="amount"
          render={({ field: { onChange, value } }) => (
            <FormItem>
              <FormLabel>Amount</FormLabel>
              <FormControl>
                <NumberField onChange={onChange} value={value}>
                  <NumberFieldGroup>
                    <NumberFieldDecrement />
                    <NumberFieldInput />
                    <NumberFieldIncrement />
                  </NumberFieldGroup>
                </NumberField>
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  )
}
image

Disabled

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldLabel,
} from "@/components/ui/number-field"

export function NumberFieldDisabled() {
  return (
    <NumberField isDisabled>
      <NumberFieldLabel>Amount</NumberFieldLabel>
      <NumberFieldGroup>
        <NumberFieldIncrement />
        <NumberFieldInput />
        <NumberFieldDecrement />
      </NumberFieldGroup>
    </NumberField>
  )
}
image

Button position

The button can be located inside or outside, the default is inside. You can also customize it as needed

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldLabel,
} from "@/components/ui/number-field"

export function NumberFieldBtnPosition() {
  return (
    <NumberField btnPosition="outside">
      <NumberFieldLabel>Amount</NumberFieldLabel>
      <NumberFieldGroup>
        <NumberFieldIncrement />
        <NumberFieldInput />
        <NumberFieldDecrement />
      </NumberFieldGroup>
    </NumberField>
  )
}
image

Label position

The label can be located at the top or left, the default is the top

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldLabel,
} from "@/components/ui/number-field"

export function NumberFieldLabelPosition() {
  return (
    <NumberField labelPosition="left">
      <NumberFieldLabel>Amount</NumberFieldLabel>
      <NumberFieldGroup>
        <NumberFieldIncrement />
        <NumberFieldInput />
        <NumberFieldDecrement />
      </NumberFieldGroup>
    </NumberField>
  )
}
image

Number formatting

Supported formats include Decimals, Percentages, Currency values, Units

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldLabel,
} from "@/components/ui/number-field"

export function NumberFieldNumberFormatting() {
  return (
    <NumberField
      label="Currency"
      defaultValue={1888}
      formatOptions={{
        style: "currency",
        currency: "EUR",
        currencyDisplay: "code",
        currencySign: "accounting",
      }}
    >
      <NumberFieldLabel>Amount</NumberFieldLabel>
      <NumberFieldGroup>
        <NumberFieldIncrement />
        <NumberFieldInput />
        <NumberFieldDecrement />
      </NumberFieldGroup>
    </NumberField>
  )
}
image

Copy link

vercel bot commented Sep 16, 2024

@thought7878 is attempting to deploy a commit to the shadcn-pro Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant