refactor: move input and label to design system

correct variables according to design system spec

various cleanup
This commit is contained in:
Christian Andolf
2025-05-26 16:09:22 +02:00
parent 8b5e8aecae
commit f6be4f275e
22 changed files with 257 additions and 164 deletions

View File

@@ -0,0 +1,60 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Input } from './Input'
import { TextField } from 'react-aria-components'
const meta: Meta<typeof Input> = {
title: 'Components/Input',
component: ({ isInvalid, ...props }) => (
<TextField isInvalid={isInvalid}>
<Input {...props} />
</TextField>
),
argTypes: {},
}
export default meta
type Story = StoryObj<typeof Input>
export const Default: Story = {
args: {
label: 'Label',
name: 'foo',
required: false,
},
}
export const Filled: Story = {
args: {
label: 'Label',
name: 'foo',
value: 'Value',
},
}
export const Error: Story = {
args: {
label: 'Label',
name: 'foo',
// @ts-expect-error Input does not support this, but wrapping <TextField> does
isInvalid: true,
},
}
export const Disabled: Story = {
args: {
label: 'Label',
name: 'foo',
disabled: true,
},
}
export const DisabledFilled: Story = {
args: {
label: 'Label',
name: 'foo',
disabled: true,
value: 'Value',
},
}

View File

@@ -0,0 +1,46 @@
import { cx } from 'class-variance-authority'
import {
type ForwardedRef,
forwardRef,
useId,
useImperativeHandle,
useRef,
} from 'react'
import { Input as AriaInput, Label as AriaLabel } from 'react-aria-components'
import { Label } from '../Label'
import styles from './input.module.css'
import type { InputProps } from './types'
import { Typography } from '../Typography'
export const Input = forwardRef(function AriaInputWithLabelComponent(
{ label, ...props }: InputProps,
forwardedRef: ForwardedRef<HTMLInputElement>
) {
const ref = useRef<HTMLInputElement>(null)
// Unique id is required for multiple inputs of same name appearing multiple times
// on same page. This will inherited by parent label element.
// Shouldn't really be needed if we don't set id though.
const uniqueId = useId()
const inputId = `${uniqueId}-${props.name}`
useImperativeHandle(forwardedRef, () => ref.current as HTMLInputElement)
return (
<AriaLabel className={styles.container}>
<Typography variant="Body/Paragraph/mdRegular">
<AriaInput
{...props}
placeholder={props.placeholder}
className={cx(styles.input, props.className)}
ref={ref}
id={inputId}
/>
</Typography>
<Label required={props.required}>{label}</Label>
</AriaLabel>
)
})

View File

@@ -0,0 +1 @@
export { Input } from './Input'

View File

@@ -0,0 +1,60 @@
.container {
align-content: center;
background-color: var(--Surface-Primary-Default);
border: 1px solid var(--Border-Interactive-Default);
border-radius: var(--Corner-radius-md);
display: grid;
min-width: 0; /* allow shrinkage */
height: 60px;
padding: var(--Space-x1) var(--Space-x2);
box-sizing: border-box;
cursor: text;
&:has(.input:focus) {
outline-offset: -2px;
outline: 2px solid var(--Border-Interactive-Focus);
}
&:has(.input:disabled) {
background-color: var(--Surface-Primary-Disabled);
border: transparent;
cursor: unset;
}
&:has(.input[data-invalid='true'], .input[aria-invalid='true']) {
border-color: var(--Border-Interactive-Error);
&:focus-within {
outline-offset: -2px;
outline: 2px solid var(--Border-Interactive-Error);
}
}
}
.input {
background: none;
border: none;
color: var(--Text-Default);
height: 1px;
order: 2;
padding: 0;
transition: height 150ms ease;
&:focus,
&:placeholder-shown,
&[value]:not([value='']) {
height: 18px;
outline: none;
}
&:disabled {
color: var(--Text-Interactive-Disabled);
}
}
@media (hover: hover) {
.input:active:not(:disabled) {
height: 18px;
outline: none;
}
}

View File

@@ -0,0 +1,6 @@
import { ComponentProps } from 'react'
import { Input } from 'react-aria-components'
export interface InputProps extends ComponentProps<typeof Input> {
label: string
}