Range Input
Installation
Add the following Soul components
The range-input component uses the input and button components. Make sure you have added them to your project.
Install the following dependencies
npm install lucide-react
Copy and paste the following code into your project
form/range-input/index.tsx
'use client';import { ArrowRight } from 'lucide-react';import { ReactNode, useEffect, useState } from 'react';import { Input } from '@/vibes/soul/form/input';import { Button } from '@/vibes/soul/primitives/button';export interface RangeInputProps { applyLabel?: string; colorScheme?: 'light' | 'dark'; disabled?: boolean; max?: number; maxLabel?: string; maxName?: string; maxPlaceholder?: string; maxPrepend?: ReactNode; maxStep?: number; min?: number; minLabel?: string; minName?: string; minPlaceholder?: string; minPrepend?: ReactNode; minStep?: number; onChange?: (value: { min: number | null; max: number | null }) => void; value?: { min: number | null; max: number | null };}const clamp = (value: number, min: number | null, max?: number | null) => Math.min(Math.max(value, min ?? -Infinity), max ?? Infinity);export function RangeInput({ applyLabel = 'Apply', colorScheme = 'light', disabled = false, max = 100, maxLabel = 'Max', maxName = 'max', maxPlaceholder = 'Max', maxPrepend = null, maxStep = 1, min = 0, minLabel = 'Min', minName = 'min', minPlaceholder = 'Min', minPrepend = null, minStep = 1, onChange, value,}: RangeInputProps) { const [state, setState] = useState({ min: value?.min?.toString() ?? '', max: value?.max?.toString() ?? '', }); useEffect(() => { setState({ min: value?.min?.toString() ?? '', max: value?.max?.toString() ?? '' }); }, [value]); const parsedMinState = parseInt(state.min, 10); const parsedMaxState = parseInt(state.max, 10); const minStateAsNumber = Number.isNaN(parsedMinState) ? null : parsedMinState; const maxStateAsNumber = Number.isNaN(parsedMaxState) ? null : parsedMaxState; return ( <div className="flex w-full items-center gap-2"> <Input className="flex-1" colorScheme={colorScheme} disabled={disabled} label={minLabel} max={maxStateAsNumber ?? max} min={min} name={minName} onBlur={(e) => { const clamped = clamp( e.currentTarget.valueAsNumber, min, e.currentTarget.max === '' ? null : parseInt(e.currentTarget.max, 10), ); const nextValue = Number.isNaN(clamped) ? null : clamped; setState((prev) => ({ ...prev, min: nextValue?.toString() ?? '' })); }} onChange={(e) => { const nextValue = e.currentTarget.value; setState((prev) => ({ ...prev, min: nextValue })); }} placeholder={minPlaceholder} prepend={minPrepend} step={minStep} type="number" value={state.min} /> <Input className="flex-1" colorScheme={colorScheme} disabled={disabled} label={maxLabel} max={max} min={minStateAsNumber ?? min} name={maxName} onBlur={(e) => { const clamped = clamp( e.currentTarget.valueAsNumber, e.currentTarget.min === '' ? null : parseInt(e.currentTarget.min, 10), max, ); const nextValue = Number.isNaN(clamped) ? null : clamped; setState((prev) => ({ ...prev, max: nextValue?.toString() ?? '' })); }} onChange={(e) => { const nextValue = e.currentTarget.value; setState((prev) => ({ ...prev, max: nextValue })); }} placeholder={maxPlaceholder} prepend={maxPrepend} step={maxStep} type="number" value={state.max} /> <Button className="shrink-0" disabled={disabled || (state.min === state.max && state.min !== '' && state.max !== '')} onClick={() => onChange?.({ min: state.min === '' ? null : Number(state.min), max: state.max === '' ? null : Number(state.max), }) } shape="circle" size="small" variant="secondary" > <span className="sr-only">{applyLabel}</span> <ArrowRight size={20} strokeWidth={1} /> </Button> </div> );}
Usage
import { RangeInput } from '@/vibes/soul/form/range-input';function Usage() { return ( <RangeInput min={0} max={100} /> );}
API Reference
RangeInputProps
Prop | Type | Default |
---|---|---|
applyLabel | string | 'Apply ' |
colorScheme | 'light' | 'dark' | 'light' |
disabled | boolean | false |
max | number | 100 |
maxLabel | string | 'Max' |
maxName | string | 'max' |
maxPlaceholder | string | 'Max' |
maxPrepend | ReactNode | null |
maxStep | number | 1 |
min | number | 0 |
minLabel | string | 'Min' |
minName | string | 'min' |
minPlaceholder | string | 'Min' |
minPrepend | ReactNode | null |
minStep | number | 1 |
onChange | (value: { min: number | null; max: number | null }) => void | |
value | { min: number | null; max: number | null } |