mirror of https://github.com/acid-info/lsd.git
feat: implements number input with PR comment feedback
This commit is contained in:
parent
a7bfa14d9d
commit
4e09092f8f
|
@ -32,6 +32,8 @@ import { TagStyles } from '../Tag/Tag.styles'
|
||||||
import { TextFieldStyles } from '../TextField/TextField.styles'
|
import { TextFieldStyles } from '../TextField/TextField.styles'
|
||||||
import { defaultThemes, Theme, withTheme } from '../Theme'
|
import { defaultThemes, Theme, withTheme } from '../Theme'
|
||||||
import { TypographyStyles } from '../Typography/Typography.styles'
|
import { TypographyStyles } from '../Typography/Typography.styles'
|
||||||
|
import { TableFooterStyles } from '../TableFooter/TableFooter.styles'
|
||||||
|
import { NumberInputStyles } from '../NumberInput/NumberInput.styles'
|
||||||
|
|
||||||
const componentStyles: Array<ReturnType<typeof withTheme> | SerializedStyles> =
|
const componentStyles: Array<ReturnType<typeof withTheme> | SerializedStyles> =
|
||||||
[
|
[
|
||||||
|
@ -66,6 +68,7 @@ const componentStyles: Array<ReturnType<typeof withTheme> | SerializedStyles> =
|
||||||
TableBodyStyles,
|
TableBodyStyles,
|
||||||
TableItemStyles,
|
TableItemStyles,
|
||||||
TableRowStyles,
|
TableRowStyles,
|
||||||
|
NumberInputStyles,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const CSSBaseline: React.FC<{ theme?: Theme }> = ({
|
export const CSSBaseline: React.FC<{ theme?: Theme }> = ({
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
export const numberInputClasses = {
|
||||||
|
root: `lsd-number-input`,
|
||||||
|
label: 'lsd-number-input__label',
|
||||||
|
|
||||||
|
mainContainer: `lsd-number-input__main-container`,
|
||||||
|
|
||||||
|
inputContainer: `lsd-number-input__input-container`,
|
||||||
|
input: `lsd-number-input__input`,
|
||||||
|
|
||||||
|
errorIcon: `lsd-number-input__error-icon`,
|
||||||
|
plusMinusIcons: `lsd-number-input__plus-minus-icons`,
|
||||||
|
|
||||||
|
supportingText: 'lsd-number-input__supporting-text',
|
||||||
|
|
||||||
|
disabled: `lsd-number-input--disabled`,
|
||||||
|
error: 'lsd-number-input--error',
|
||||||
|
|
||||||
|
large: `lsd-number-input--large`,
|
||||||
|
medium: `lsd-number-input--medium`,
|
||||||
|
small: `lsd-number-input--small`,
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Meta, Story } from '@storybook/react'
|
||||||
|
import { NumberInput, NumberInputProps } from './NumberInput'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'NumberInput',
|
||||||
|
component: NumberInput,
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
type: {
|
||||||
|
name: 'enum',
|
||||||
|
value: ['small', 'medium', 'large'],
|
||||||
|
},
|
||||||
|
defaultValue: 'large',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Meta
|
||||||
|
|
||||||
|
export const Root: Story<NumberInputProps> = ({ ...args }) => {
|
||||||
|
return <NumberInput {...args} />
|
||||||
|
}
|
||||||
|
|
||||||
|
Root.args = {
|
||||||
|
id: 'label',
|
||||||
|
size: 'large',
|
||||||
|
supportingText: 'Supporting text',
|
||||||
|
disabled: false,
|
||||||
|
value: undefined,
|
||||||
|
onChange: undefined,
|
||||||
|
error: false,
|
||||||
|
errorIcon: false,
|
||||||
|
min: -10,
|
||||||
|
max: 10,
|
||||||
|
step: 1,
|
||||||
|
label: 'Label',
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { css } from '@emotion/react'
|
||||||
|
import { numberInputClasses } from './NumberInput.classes'
|
||||||
|
|
||||||
|
export const NumberInputStyles = css`
|
||||||
|
.${numberInputClasses.root} {
|
||||||
|
width: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.mainContainer}:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.error} {
|
||||||
|
.${numberInputClasses.mainContainer} {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.label} {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.plusMinusIcons} {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.inputContainer} {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid rgb(var(--lsd-border-primary));
|
||||||
|
border-left: 0px;
|
||||||
|
border-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.errorIcon} {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.inputContainer} {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.disabled} {
|
||||||
|
opacity: 0.34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.mainContainer} {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.input} {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(var(--lsd-text-primary));
|
||||||
|
background: none;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.input}::-webkit-inner-spin-button {
|
||||||
|
display: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.input}:hover {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.supportingText} {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.large} {
|
||||||
|
.${numberInputClasses.label} {
|
||||||
|
margin: 0 0 6px 18px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.inputContainer} {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.input} {
|
||||||
|
width: 62px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.plusMinusIcons} {
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.supportingText} {
|
||||||
|
margin: 6px 18px 0 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.medium} {
|
||||||
|
.${numberInputClasses.label} {
|
||||||
|
margin: 0 0 6px 14px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.inputContainer} {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.input} {
|
||||||
|
width: 58px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.plusMinusIcons} {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.supportingText} {
|
||||||
|
margin: 6px 14px 0 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.${numberInputClasses.small} {
|
||||||
|
.${numberInputClasses.label} {
|
||||||
|
margin: 0 0 6px 12px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.inputContainer} {
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.input} {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.plusMinusIcons} {
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
.${numberInputClasses.supportingText} {
|
||||||
|
margin: 6px 12px 0 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,158 @@
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import React, { useRef } from 'react'
|
||||||
|
import { useInput } from '../../utils/useInput'
|
||||||
|
import { AddIcon, ErrorIcon, RemoveIcon } from '../Icons'
|
||||||
|
import { Typography } from '../Typography'
|
||||||
|
import { numberInputClasses } from './NumberInput.classes'
|
||||||
|
import { IconButton } from '../IconButton'
|
||||||
|
import {
|
||||||
|
CommonProps,
|
||||||
|
omitCommonProps,
|
||||||
|
useCommonProps,
|
||||||
|
} from '../../utils/useCommonProps'
|
||||||
|
|
||||||
|
export type NumberInputProps = CommonProps &
|
||||||
|
Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'value'> &
|
||||||
|
Pick<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
|
||||||
|
label?: React.ReactNode
|
||||||
|
size?: 'large' | 'medium' | 'small'
|
||||||
|
error?: boolean
|
||||||
|
errorIcon?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
supportingText?: string
|
||||||
|
value?: string
|
||||||
|
defaultValue?: string
|
||||||
|
placeholder?: string
|
||||||
|
icon?: React.ReactNode
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
step?: number
|
||||||
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NumberInput: React.FC<NumberInputProps> & {
|
||||||
|
classes: typeof numberInputClasses
|
||||||
|
} = ({
|
||||||
|
label,
|
||||||
|
size = 'large',
|
||||||
|
error = false,
|
||||||
|
errorIcon = false,
|
||||||
|
supportingText,
|
||||||
|
value,
|
||||||
|
placeholder,
|
||||||
|
defaultValue,
|
||||||
|
disabled,
|
||||||
|
onChange,
|
||||||
|
icon,
|
||||||
|
inputProps = {},
|
||||||
|
id = 'number-input',
|
||||||
|
min = Number.MIN_SAFE_INTEGER,
|
||||||
|
max = Number.MAX_SAFE_INTEGER,
|
||||||
|
step = 1,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const ref = useRef<HTMLInputElement>(null)
|
||||||
|
const commonProps = useCommonProps(props)
|
||||||
|
const input = useInput({ defaultValue, value, onChange, ref })
|
||||||
|
|
||||||
|
const handleIncrement = () => {
|
||||||
|
if (disabled) return
|
||||||
|
const newValue = Math.min(max, Number(input.value || '0') + step)
|
||||||
|
input.setValue(newValue.toString())
|
||||||
|
|
||||||
|
const fakeEvent = {
|
||||||
|
target: { value: newValue.toString() },
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onChange) {
|
||||||
|
input.onChange(fakeEvent as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDecrement = () => {
|
||||||
|
if (disabled) return
|
||||||
|
const newValue = Math.max(min, Number(input.value || '0') - step)
|
||||||
|
input.setValue(newValue.toString())
|
||||||
|
|
||||||
|
const fakeEvent = {
|
||||||
|
target: { value: newValue.toString() },
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onChange) {
|
||||||
|
input.onChange(fakeEvent as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
aria-disabled={disabled ? 'true' : 'false'}
|
||||||
|
{...omitCommonProps(props)}
|
||||||
|
className={clsx(
|
||||||
|
props.className,
|
||||||
|
commonProps.className,
|
||||||
|
numberInputClasses.root,
|
||||||
|
numberInputClasses[size],
|
||||||
|
disabled && numberInputClasses.disabled,
|
||||||
|
error && numberInputClasses.error,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label && (
|
||||||
|
<Typography
|
||||||
|
htmlFor={id}
|
||||||
|
className={numberInputClasses.label}
|
||||||
|
variant="label2"
|
||||||
|
component="label"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={numberInputClasses.mainContainer}>
|
||||||
|
<IconButton
|
||||||
|
onClick={handleDecrement}
|
||||||
|
className={numberInputClasses.plusMinusIcons}
|
||||||
|
>
|
||||||
|
<RemoveIcon color="primary" />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<div className={numberInputClasses.inputContainer}>
|
||||||
|
<input
|
||||||
|
id={id}
|
||||||
|
type="number"
|
||||||
|
placeholder={placeholder}
|
||||||
|
ref={ref}
|
||||||
|
className={clsx(inputProps.className, numberInputClasses.input)}
|
||||||
|
value={input.value || ''}
|
||||||
|
onChange={input.onChange}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
|
disabled={disabled}
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
{error && !!errorIcon && (
|
||||||
|
<span className={numberInputClasses.errorIcon}>
|
||||||
|
<ErrorIcon color="primary" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
onClick={handleIncrement}
|
||||||
|
className={numberInputClasses.plusMinusIcons}
|
||||||
|
>
|
||||||
|
<AddIcon color="primary" />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
{supportingText && (
|
||||||
|
<div className={clsx(numberInputClasses.supportingText)}>
|
||||||
|
<Typography variant={'label2'} component="p">
|
||||||
|
{supportingText}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInput.classes = numberInputClasses
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './NumberInput'
|
|
@ -30,3 +30,4 @@ export * from './components/Tag'
|
||||||
export * from './components/TextField'
|
export * from './components/TextField'
|
||||||
export * from './components/Theme'
|
export * from './components/Theme'
|
||||||
export * from './components/Typography'
|
export * from './components/Typography'
|
||||||
|
export * from './components/NumberInput'
|
||||||
|
|
Loading…
Reference in New Issue