tremorlabs / tremor

React components to build charts and dashboards
https://tremor.so
Apache License 2.0
15.66k stars 452 forks source link

Feature request - Inputs #179

Closed eykrehbein closed 1 year ago

eykrehbein commented 1 year ago

Would be great to have simple Input components, for Text, Email, Password, etc.

Possible Features:

Something like: https://tailwindui.com/components/application-ui/forms/input-groups

What are your thoughts on this? Would be willing to implement this, after we discussed whether this is a wanted feature and what the specs should be. :)

severinlandolt commented 1 year ago

Heya @eykrehbein thank you so much for reaching out! We get this feedback a lot lately and are planning to include a simple input component in one of the next releases. Happy coding :)

angelhodar commented 1 year ago

@severinlandolt Just in case its helpful, here I share the custom inputs I have made for my project:

TextInput

import { useFormContext } from 'react-hook-form'
import { parseFormInputErrors } from 'utils'

interface TextInputProps {
  name: string
  label?: string
  type?: string
  defaultValue?: string
  description?: string
  placeholder?: string
  disabled?: boolean
}

const TextInput = (props: TextInputProps) => {
  const {
    label,
    name,
    type,
    defaultValue,
    description,
    placeholder,
    disabled,
  } = props
  const { register, formState } = useFormContext()
  const { isError, errorMessage } = parseFormInputErrors(name, formState.errors)

  return (
    <div className="w-full text-left">
      <label htmlFor={name} className="block text-sm font-medium text-gray-700">
        {label}
      </label>
      <div className="mt-1">
        <input
          className="shadow-sm focus:ring-cyan-500 focus:border-cyan-500 block w-full sm:text-sm border-gray-300 rounded-md"
          type={type || 'text'}
          defaultValue={defaultValue || ''}
          placeholder={placeholder}
          disabled={disabled}
          {...register(name)}
        />
        <span
          className={`text-sm ${isError ? 'text-red-500' : 'text-gray-500'}`}
        >
          {isError ? errorMessage : description}
        </span>
      </div>
    </div>
  )
}

export default TextInput

SelectInput

import { useFormContext } from 'react-hook-form'
import { parseFormInputErrors } from 'utils'

interface OptionProps {
  title: string
  value: string
}

interface SelectInputProps {
  name: string
  label?: string
  options: OptionProps[]
  defaultValue?: any
  onChange?: (value: string) => void
}

const SelectInput = (props: SelectInputProps) => {
  const { label, name, options, onChange } = props
  const { register, formState } = useFormContext()
  const { isError, errorMessage } = parseFormInputErrors(name, formState.errors)

  const { onChange: onFormValueChange, ...rest } = register(name)

  const onValueChange = (e: any) => {
    onFormValueChange(e)
    if (onChange) onChange(e.target.value)
  }

  return (
    <div>
      <label
        htmlFor={name}
        className="block text-sm font-medium text-gray-700 text-left"
      >
        {label}
      </label>
      <div className="mt-1">
        <select
          className="shadow-sm focus:ring-cyan-500 focus:border-cyan-500 block w-full sm:text-sm border-gray-300 rounded-md"
          id={name}
          onChange={onValueChange}
          {...rest}
        >
          {options.map((option: OptionProps, i: number) => (
            <option key={i} value={option.value}>
              {option.title}
            </option>
          ))}
        </select>
      </div>
      <span className={`text-sm ${isError ? 'text-red-500' : 'text-gray-500'}`}>
        {isError ? errorMessage : ''}
      </span>
    </div>
  )
}

export default SelectInput

DateInput (native)

import { useFormContext } from 'react-hook-form'
import { parseFormInputErrors } from 'utils'

interface DateInputProps {
  label: string
  name: string
  description?: string
}

const DateInput = (props: DateInputProps) => {
  const { label, name, description = '' } = props
  const { register, formState } = useFormContext()
  const { isError, errorMessage } = parseFormInputErrors(name, formState.errors)

  return (
    <div className="w-full text-left">
      <label htmlFor={name} className="block text-sm font-medium text-gray-700">
        {label}
      </label>
      <div className="mt-1">
        <input
          className="shadow-sm focus:ring-cyan-500 focus:border-cyan-500 block w-full sm:text-sm border-gray-300 rounded-md"
          type="date"
          {...register(name)}
        />
        <span
          className={`text-sm ${isError ? 'text-red-500' : 'text-gray-500'}`}
        >
          {isError ? errorMessage : description}
        </span>
      </div>
    </div>
  )
}

export default DateInput
severinlandolt commented 1 year ago

@angelhodar Thank you so much for sharing! Out of curiosity, is your project public? Would be super interesting to see what you have built 🤩

angelhodar commented 1 year ago

@severinlandolt Sadly its for an internal project for my company. If you need help just reach me and I will try to do as much as I can!

In fact watching the code I pasted I have noticed that the validation error can be encapsulated in an InputError component that process the error type and message. And I dont know if this components work outside of a FormProvider from react-hook-form that I use for validation and form handling, I havent tested them outside of a form

christopherkindl commented 1 year ago

Hi @angelhodar,

many thanks for sharing this! We are working hard on the input element already and will try to consider this logic.

github-actions[bot] commented 1 year ago

:tada: This issue has been resolved in version 1.3.0-beta-feat-1.3.0.2 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

github-actions[bot] commented 1 year ago

:tada: This issue has been resolved in version 1.3.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

dhruvik7 commented 1 year ago

number input too!

y-nk commented 1 year ago

@angelhodar thanks a lot, these includes label which is nice :) I'm sad to see tremor doesn't include them (but honestly it's not that hard to wrap, it's just bothering to do)