forked from tremorlabs/tremor
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add number input and email/url types to text input (tremorlabs#575
) (tremorlabs#577) number input Co-authored-by: RubensRafael <rubensrafael2@live.com> Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com> Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com> Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev> Co-authored-by: Severin Landolt <sev.landolt@gmail.com> Co-authored-by: gitstart <gitstart@users.noreply.github.com> curve type Co-authored-by: Thomas McInnis <thomasmcinnis@icloud.com> Co-authored-by: tryanmac <88759292+tryanmac@users.noreply.github.com> Co-authored-by: GitStart <1501599+gitstart@users.noreply.github.com>
- Loading branch information
1 parent
b041b1d
commit e424884
Showing
14 changed files
with
516 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* eslint-disable max-len */ | ||
import React from "react"; | ||
|
||
const MinusIcon = ({ ...props }) => ( | ||
<svg | ||
{...props} | ||
xmlns="http://www.w3.org/2000/svg" | ||
fill="none" | ||
viewBox="0 0 24 24" | ||
stroke="currentColor" | ||
strokeWidth="2.5" | ||
> | ||
<path strokeLinecap="round" strokeLinejoin="round" d="M20 12H4" /> | ||
</svg> | ||
); | ||
|
||
export default MinusIcon; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* eslint-disable max-len */ | ||
import React from "react"; | ||
|
||
const PlusIcon = ({ ...props }) => ( | ||
<svg | ||
{...props} | ||
xmlns="http://www.w3.org/2000/svg" | ||
fill="none" | ||
viewBox="0 0 24 24" | ||
stroke="currentColor" | ||
strokeWidth="2.5" | ||
> | ||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /> | ||
</svg> | ||
); | ||
|
||
export default PlusIcon; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
"use client"; | ||
import React, { ReactNode, useRef, useState } from "react"; | ||
import { border, mergeRefs, sizing, spacing, tremorTwMerge } from "lib"; | ||
import { ExclamationFilledIcon } from "assets"; | ||
import { getSelectButtonColors, hasValue } from "components/input-elements/selectUtils"; | ||
|
||
export interface BaseInputProps extends React.InputHTMLAttributes<HTMLInputElement> { | ||
type?: "text" | "password" | "email" | "url" | "number"; | ||
defaultValue?: string | number; | ||
value?: string | number; | ||
icon?: React.ElementType | React.JSXElementConstructor<any>; | ||
error?: boolean; | ||
errorMessage?: string; | ||
disabled?: boolean; | ||
stepper?: ReactNode; | ||
makeInputClassName: (className: string) => string; | ||
} | ||
|
||
const BaseInput = React.forwardRef<HTMLInputElement, BaseInputProps>((props, ref) => { | ||
const { | ||
value, | ||
defaultValue, | ||
type, | ||
placeholder = "Type...", | ||
icon, | ||
error = false, | ||
errorMessage, | ||
disabled = false, | ||
stepper, | ||
makeInputClassName, | ||
className, | ||
...other | ||
} = props; | ||
const [isFocused, setIsFocused] = useState(false); | ||
|
||
const Icon = icon; | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
|
||
const hasSelection = hasValue(value || defaultValue); | ||
|
||
const handleFocusChange = (isFocused: boolean) => { | ||
if (isFocused === false) { | ||
inputRef.current?.blur(); | ||
} else { | ||
inputRef.current?.focus(); | ||
} | ||
setIsFocused(isFocused); | ||
}; | ||
|
||
return ( | ||
<> | ||
<div | ||
className={tremorTwMerge( | ||
makeInputClassName("root"), | ||
// common | ||
"relative w-full flex items-center min-w-[10rem] outline-none rounded-tremor-default", | ||
// light | ||
"shadow-tremor-input", | ||
// dark | ||
"dark:shadow-dark-tremor-input", | ||
getSelectButtonColors(hasSelection, disabled, error), | ||
isFocused && | ||
tremorTwMerge( | ||
// common | ||
"ring-2 transition duration-100", | ||
// light | ||
"border-tremor-brand-subtle ring-tremor-brand-muted", | ||
// light | ||
"dark:border-dark-tremor-brand-subtle dark:ring-dark-tremor-brand-muted", | ||
), | ||
border.sm.all, | ||
className, | ||
)} | ||
onClick={() => { | ||
if (!disabled) { | ||
handleFocusChange(true); | ||
} | ||
}} | ||
onFocus={() => { | ||
handleFocusChange(true); | ||
}} | ||
onBlur={() => { | ||
handleFocusChange(false); | ||
}} | ||
> | ||
{Icon ? ( | ||
<Icon | ||
className={tremorTwMerge( | ||
makeInputClassName("icon"), | ||
// common | ||
"shrink-0", | ||
// light | ||
"text-tremor-content-subtle", | ||
// light | ||
"dark:text-dark-tremor-content-subtle", | ||
sizing.lg.height, | ||
sizing.lg.width, | ||
spacing.xl.marginLeft, | ||
)} | ||
/> | ||
) : null} | ||
<input | ||
ref={mergeRefs([inputRef, ref])} | ||
defaultValue={defaultValue} | ||
value={value} | ||
type={type} | ||
className={tremorTwMerge( | ||
makeInputClassName("input"), | ||
// common | ||
"w-full focus:outline-none focus:ring-0 border-none bg-transparent text-tremor-default", | ||
// light | ||
"text-tremor-content-emphasis", | ||
// dark | ||
"dark:text-dark-tremor-content-emphasis", | ||
"[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none", | ||
Icon ? spacing.lg.paddingLeft : spacing.twoXl.paddingLeft, | ||
error ? spacing.lg.paddingRight : spacing.twoXl.paddingRight, | ||
spacing.sm.paddingY, | ||
disabled | ||
? "placeholder:text-tremor-content-subtle dark:placeholder:text-dark-tremor-content-subtle" | ||
: "placeholder:text-tremor-content dark:placeholder:text-dark-tremor-content", | ||
)} | ||
placeholder={placeholder} | ||
disabled={disabled} | ||
data-testid="base-input" | ||
{...other} | ||
/> | ||
{error ? ( | ||
<ExclamationFilledIcon | ||
className={tremorTwMerge( | ||
makeInputClassName("errorIcon"), | ||
"text-rose-500 shrink-0", | ||
spacing.md.marginRight, | ||
sizing.lg.height, | ||
sizing.lg.width, | ||
)} | ||
/> | ||
) : null} | ||
{stepper ?? null} | ||
</div> | ||
{errorMessage ? ( | ||
<p | ||
className={tremorTwMerge( | ||
makeInputClassName("errorMessage"), | ||
"text-sm text-rose-500 mt-1", | ||
)} | ||
> | ||
{errorMessage} | ||
</p> | ||
) : null} | ||
</> | ||
); | ||
}); | ||
|
||
BaseInput.displayName = "BaseInput"; | ||
|
||
export default BaseInput; |
132 changes: 132 additions & 0 deletions
132
src/components/input-elements/NumberInput/NumberInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import React, { useRef } from "react"; | ||
import { makeClassName, mergeRefs, tremorTwMerge } from "lib"; | ||
import { PlusIcon, MinusIcon } from "assets"; | ||
import BaseInput, { BaseInputProps } from "../BaseInput"; | ||
|
||
export interface NumberInputProps | ||
extends Omit<BaseInputProps, "type" | "stepper" | "onSubmit" | "makeInputClassName"> { | ||
step?: string; | ||
enableStepper?: boolean; | ||
onSubmit?: (value: number) => void; | ||
onValueChange?: (value: number) => void; | ||
} | ||
|
||
const baseArrowClasses = | ||
"flex mx-auto text-tremor-content-subtle dark:text-dark-tremor-content-subtle"; | ||
|
||
const enabledArrowClasses = | ||
"cursor-pointer hover:text-tremor-content dark:hover:text-dark-tremor-content"; | ||
|
||
const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>((props, ref) => { | ||
const { onSubmit, enableStepper = true, disabled, onValueChange, onChange, ...other } = props; | ||
|
||
const inputRef = useRef<HTMLInputElement>(null); | ||
|
||
const [isArrowDownPressed, setIsArrowDownPressed] = React.useState(false); | ||
const handleArrowDownPress = React.useCallback(() => { | ||
setIsArrowDownPressed(true); | ||
}, []); | ||
const handleArrowDownRelease = React.useCallback(() => { | ||
setIsArrowDownPressed(false); | ||
}, []); | ||
|
||
const [isArrowUpPressed, setIsArrowUpPressed] = React.useState(false); | ||
const handleArrowUpPress = React.useCallback(() => { | ||
setIsArrowUpPressed(true); | ||
}, []); | ||
const handleArrowUpRelease = React.useCallback(() => { | ||
setIsArrowUpPressed(false); | ||
}, []); | ||
|
||
return ( | ||
<BaseInput | ||
type="number" | ||
ref={mergeRefs([inputRef, ref])} | ||
disabled={disabled} | ||
makeInputClassName={makeClassName("NumberInput")} | ||
onKeyDown={(e) => { | ||
if (e.key === "Enter" && !e.ctrlKey && !e.altKey && !e.shiftKey) { | ||
const value = inputRef.current?.value; | ||
onSubmit?.(parseFloat(value ?? "")); | ||
} | ||
if (e.key === "ArrowDown") { | ||
handleArrowDownPress(); | ||
} | ||
if (e.key === "ArrowUp") { | ||
handleArrowUpPress(); | ||
} | ||
}} | ||
onKeyUp={(e) => { | ||
if (e.key === "ArrowDown") { | ||
handleArrowDownRelease(); | ||
} | ||
if (e.key === "ArrowUp") { | ||
handleArrowUpRelease(); | ||
} | ||
}} | ||
onChange={(e) => { | ||
if (disabled) return; | ||
|
||
onValueChange?.(parseFloat(e.target.value)); | ||
onChange?.(e); | ||
}} | ||
stepper={ | ||
enableStepper ? ( | ||
<div className={tremorTwMerge("flex justify-center align-middle")}> | ||
<div | ||
tabIndex={-1} | ||
onClick={(e) => e.preventDefault()} | ||
onMouseDown={(e) => e.preventDefault()} | ||
onTouchStart={(e) => e.preventDefault()} | ||
onMouseUp={() => { | ||
if (disabled) return; | ||
inputRef.current?.stepDown(); | ||
onValueChange?.(parseFloat(inputRef.current?.value ?? "")); | ||
}} | ||
className={tremorTwMerge( | ||
!disabled && enabledArrowClasses, | ||
baseArrowClasses, | ||
"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border", | ||
)} | ||
> | ||
<MinusIcon | ||
data-testid="step-down" | ||
className={`${ | ||
isArrowDownPressed ? "scale-95" : "" | ||
} h-4 w-4 duration-75 transition group-active:scale-95`} | ||
/> | ||
</div> | ||
<div | ||
tabIndex={-1} | ||
onClick={(e) => e.preventDefault()} | ||
onMouseDown={(e) => e.preventDefault()} | ||
onTouchStart={(e) => e.preventDefault()} | ||
onMouseUp={() => { | ||
if (disabled) return; | ||
inputRef.current?.stepUp(); | ||
onValueChange?.(parseFloat(inputRef.current?.value ?? "")); | ||
}} | ||
className={tremorTwMerge( | ||
!disabled && enabledArrowClasses, | ||
baseArrowClasses, | ||
"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border", | ||
)} | ||
> | ||
<PlusIcon | ||
data-testid="step-up" | ||
className={`${ | ||
isArrowUpPressed ? "scale-95" : "" | ||
} h-4 w-4 duration-75 transition group-active:scale-95`} | ||
/> | ||
</div> | ||
</div> | ||
) : null | ||
} | ||
{...other} | ||
/> | ||
); | ||
}); | ||
|
||
NumberInput.displayName = "NumberInput"; | ||
|
||
export default NumberInput; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as NumberInput } from "./NumberInput"; | ||
export type { NumberInputProps } from "./NumberInput"; |
Oops, something went wrong.