Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/input-size-variants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@launchpad-ui/components': minor
---

Add a `size` prop to `Input` with `small` (24px tall) and `medium` (32px tall, the default) variants, matching the corresponding `Button` sizes so inputs and buttons align when placed side by side. `small` uses 11px text to match `Button`'s small height.
17 changes: 13 additions & 4 deletions packages/components/src/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,25 @@ import { useLPContextProps } from './utils';

const inputStyles = cva(styles.base, {
variants: {
size: {
small: styles.small,
medium: null,
},
variant: {
default: styles._default,
minimal: styles.minimal,
},
},
defaultVariants: {
size: 'medium',
variant: 'default',
},
});

interface InputVariants extends VariantProps<typeof inputStyles> {}
interface InputProps extends AriaInputProps, InputVariants {
Comment thread
cursor[bot] marked this conversation as resolved.
// `size` is omitted from the underlying React Aria props (the native numeric
// `size` HTML attribute) so it can be redefined as the design-system size scale.
interface InputProps extends Omit<AriaInputProps, 'size'>, InputVariants {
ref?: Ref<HTMLInputElement>;
}

Expand All @@ -37,14 +44,16 @@ const InputContext = createContext<ContextValue<InputProps, HTMLInputElement>>(n
*/
const Input = ({ ref, ...props }: InputProps) => {
[props, ref] = useLPContextProps(props, ref, InputContext);
const { variant = 'default' } = props;
// Pull the style-only variants out so they aren't forwarded to the DOM
// `<input>` (React Aria's Input spreads unknown props through unfiltered).
const { size = 'medium', variant = 'default', ...rest } = props;

return (
<AriaInput
{...props}
{...rest}
ref={ref}
className={composeRenderProps(props.className, (className, renderProps) =>
inputStyles({ ...renderProps, variant, className }),
inputStyles({ ...renderProps, size, variant, className }),
)}
/>
);
Expand Down
8 changes: 7 additions & 1 deletion packages/components/src/styles/Input.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
composes: field from './base.module.css';
font: var(--lp-text-body-2-regular);
color: var(--lp-color-text-ui-primary-base);
padding: 5px var(--lp-spacing-300);
padding-block: 5px;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to use tokens here and the 3px below?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay somewhat fixed (there is no 5px token). Fixed height would probably work better / not require odd padding

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, agreed about fixed height -- less math to do when we come back to this...

padding-inline: var(--lp-spacing-300);
border-radius: var(--lp-border-radius-medium);
border-width: var(--lp-border-width-200);
border-style: solid;
Expand Down Expand Up @@ -30,6 +31,11 @@
}
}

.small {
font: var(--lp-text-small-1-regular);
padding-block: 3px;
}

._default {
border-color: var(--lp-color-border-field-base);
background-color: var(--lp-color-bg-field-base);
Expand Down
58 changes: 58 additions & 0 deletions packages/components/stories/Input.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from '@storybook/react-vite';

import { Input } from '../src/Input';

const meta: Meta<typeof Input> = {
title: 'Components/Forms/Input',
component: Input,
parameters: {
figma: {
design:
'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-34456&m=dev',
},
docs: {
description: {
component: `
An input allows a user to input text.

Usually composed within a [TextField](/docs/components-forms-textfield--docs) alongside a [Label](/docs/components-content-label--docs) for an accessible field.
`,
},
},
},
};

export default meta;

type Story = StoryObj<typeof Input>;

export const Default: Story = {
args: {
'aria-label': 'Example',
defaultValue: 'Value',
},
};

/**
* Use the `size` prop to match the height of adjacent controls such as [Button](/docs/components-buttons-button--docs).
*/
export const Sizes: Story = {
render: () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<Input aria-label="Small" size="small" defaultValue="Small" />
<Input aria-label="Medium" size="medium" defaultValue="Medium" />
</div>
),
};

/**
* The `minimal` variant removes the resting border for use in denser or inline layouts.
*/
export const Variants: Story = {
render: () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<Input aria-label="Default" variant="default" defaultValue="Default" />
<Input aria-label="Minimal" variant="minimal" defaultValue="Minimal" />
</div>
),
};
Loading