diff --git a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx index a68261c..26fd936 100644 --- a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx +++ b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx @@ -32,6 +32,8 @@ import { TagStyles } from '../Tag/Tag.styles' import { TextFieldStyles } from '../TextField/TextField.styles' import { defaultThemes, Theme, withTheme } from '../Theme' import { TypographyStyles } from '../Typography/Typography.styles' +import { TableFooterStyles } from '../TableFooter/TableFooter.styles' +import { NumberInputStyles } from '../NumberInput/NumberInput.styles' const componentStyles: Array | SerializedStyles> = [ @@ -66,6 +68,7 @@ const componentStyles: Array | SerializedStyles> = TableBodyStyles, TableItemStyles, TableRowStyles, + NumberInputStyles, ] export const CSSBaseline: React.FC<{ theme?: Theme }> = ({ diff --git a/packages/lsd-react/src/components/NumberInput/NumberInput.classes.ts b/packages/lsd-react/src/components/NumberInput/NumberInput.classes.ts new file mode 100644 index 0000000..40f0df7 --- /dev/null +++ b/packages/lsd-react/src/components/NumberInput/NumberInput.classes.ts @@ -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`, +} diff --git a/packages/lsd-react/src/components/NumberInput/NumberInput.stories.tsx b/packages/lsd-react/src/components/NumberInput/NumberInput.stories.tsx new file mode 100644 index 0000000..3b7ee64 --- /dev/null +++ b/packages/lsd-react/src/components/NumberInput/NumberInput.stories.tsx @@ -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 = ({ ...args }) => { + return +} + +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', +} diff --git a/packages/lsd-react/src/components/NumberInput/NumberInput.styles.ts b/packages/lsd-react/src/components/NumberInput/NumberInput.styles.ts new file mode 100644 index 0000000..3470e0e --- /dev/null +++ b/packages/lsd-react/src/components/NumberInput/NumberInput.styles.ts @@ -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; + } + } +` diff --git a/packages/lsd-react/src/components/NumberInput/NumberInput.tsx b/packages/lsd-react/src/components/NumberInput/NumberInput.tsx new file mode 100644 index 0000000..86d42fc --- /dev/null +++ b/packages/lsd-react/src/components/NumberInput/NumberInput.tsx @@ -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, 'onChange' | 'value'> & + Pick, '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 + } + +export const NumberInput: React.FC & { + 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(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 ( +
+ {label && ( + + {label} + + )} + +
+ + + + +
+ + {error && !!errorIcon && ( + + + + )} +
+ + + + +
+ {supportingText && ( +
+ + {supportingText} + +
+ )} +
+ ) +} + +NumberInput.classes = numberInputClasses diff --git a/packages/lsd-react/src/components/NumberInput/index.ts b/packages/lsd-react/src/components/NumberInput/index.ts new file mode 100644 index 0000000..89e782f --- /dev/null +++ b/packages/lsd-react/src/components/NumberInput/index.ts @@ -0,0 +1 @@ +export * from './NumberInput' diff --git a/packages/lsd-react/src/index.ts b/packages/lsd-react/src/index.ts index 31605ee..44b2071 100644 --- a/packages/lsd-react/src/index.ts +++ b/packages/lsd-react/src/index.ts @@ -30,3 +30,4 @@ export * from './components/Tag' export * from './components/TextField' export * from './components/Theme' export * from './components/Typography' +export * from './components/NumberInput'