Skip to content

Commit 4149193

Browse files
feat(ui): add headless input variant
1 parent 7731dc3 commit 4149193

9 files changed

Lines changed: 71 additions & 5 deletions

File tree

‎.changeset/quiet-pans-smile.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

‎packages/swingset/src/lib/registry.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import {
6565
import {
6666
Default,
6767
Disabled as InputDisabled,
68+
Headless as InputHeadless,
6869
Invalid,
6970
meta as inputMeta,
7071
Sizes as InputSizes,
@@ -261,7 +262,14 @@ const bannerModule: StoryModule = {
261262

262263
const buttonModule: StoryModule = { meta: buttonMeta, Primary, Sizes, Disabled };
263264

264-
const inputModule: StoryModule = { meta: inputMeta, Default, Sizes: InputSizes, Disabled: InputDisabled, Invalid };
265+
const inputModule: StoryModule = {
266+
meta: inputMeta,
267+
Default,
268+
Sizes: InputSizes,
269+
Disabled: InputDisabled,
270+
Invalid,
271+
Headless: InputHeadless,
272+
};
265273

266274
const popoverComponentModule: StoryModule = {
267275
meta: popoverComponentMeta,

‎packages/swingset/src/stories/input.mdx‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,12 @@ The snippet below reflects the props selected in the table above — change a pr
4949
name='Invalid'
5050
storyModule={InputStories}
5151
/>
52+
53+
### Headless
54+
55+
The headless variant keeps the input behavior and sizing while allowing a parent composition to provide the field chrome.
56+
57+
<Story
58+
name='Headless'
59+
storyModule={InputStories}
60+
/>

‎packages/swingset/src/stories/input.stories.tsx‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ export const meta: StoryMeta = {
1414
styles: {
1515
_variants: {
1616
size: { sm: {}, md: {}, lg: {} },
17+
variant: { default: {}, headless: {} },
1718
},
1819
_defaultVariants: {
1920
size: 'md',
21+
variant: 'default',
2022
},
2123
},
2224
};
@@ -75,3 +77,15 @@ export function Invalid(props: Record<string, unknown>) {
7577
/>
7678
);
7779
}
80+
81+
export function Headless(props: Record<string, unknown>) {
82+
return (
83+
<div style={{ border: '1px solid currentColor', borderRadius: 8 }}>
84+
<Input
85+
{...knobsAsProps(props)}
86+
variant='headless'
87+
placeholder='Parent provides the field chrome'
88+
/>
89+
</div>
90+
);
91+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export { Input } from './input';
2-
export type { InputProps } from './input';
2+
export type { InputProps, InputVariant } from './input';

‎packages/ui/src/mosaic/components/input/input.styles.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ export const styles = stylex.create({
2323
color: colorVars['--cl-color-input-placeholder'],
2424
},
2525
},
26+
headless: {
27+
borderRadius: 0,
28+
borderStyle: 'none',
29+
borderWidth: 0,
30+
outline: 'none',
31+
backgroundColor: 'transparent',
32+
boxShadow: 'none',
33+
},
2634
});
2735

2836
export const sizes = stylex.create({

‎packages/ui/src/mosaic/components/input/input.test.tsx‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ describe('Mosaic Input', () => {
2424
expect(screen.getByRole('textbox', { name: 'Name' })).toHaveAttribute('data-size', size);
2525
});
2626

27+
it('removes field chrome with the headless variant', () => {
28+
render(
29+
<Input
30+
variant='headless'
31+
aria-label='Search'
32+
/>,
33+
);
34+
35+
const input = screen.getByRole('textbox', { name: 'Search' });
36+
expect(input).toHaveClass('cl-input');
37+
expect(input).toHaveAttribute('data-variant', 'headless');
38+
});
39+
2740
it('reflects and forwards the disabled state', () => {
2841
render(
2942
<Input

‎packages/ui/src/mosaic/components/input/input.tsx‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ import { reset } from '../../utils/reset.styles';
99
import { useOptionalFieldControlProps } from '../field/field.context';
1010
import { sizes, styles } from './input.styles';
1111

12+
export type InputVariant = 'default' | 'headless';
13+
1214
export interface InputProps extends Omit<MosaicComponentProps<'input'>, 'size'> {
1315
size?: 'sm' | 'md' | 'lg';
16+
/** Removes field chrome so a parent composition can provide it. @default 'default' */
17+
variant?: InputVariant;
1418
}
1519

1620
export const Input = React.forwardRef<HTMLInputElement, InputProps>(function MosaicInput(
1721
{
1822
size = 'md',
23+
variant = 'default',
1924
disabled: disabledProp,
2025
required: requiredProp,
2126
render,
@@ -52,8 +57,15 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(function Mos
5257
'aria-labelledby': fieldProps?.['aria-labelledby'] ?? ariaLabelledBy,
5358
'aria-describedby': fieldProps?.['aria-describedby'] ?? ariaDescribedBy,
5459
...mergeStyleProps(
55-
themeProps('input', { size, disabled }),
56-
stylex.props(reset.base, inputStyles.base, styles.base, sizes[size], disabled && inputStyles.disabled),
60+
themeProps('input', { size, variant, disabled }),
61+
stylex.props(
62+
reset.base,
63+
styles.base,
64+
sizes[size],
65+
variant === 'default' && inputStyles.base,
66+
variant === 'headless' && styles.headless,
67+
variant === 'default' && disabled && inputStyles.disabled,
68+
),
5769
className,
5870
style,
5971
),

‎packages/ui/src/mosaic/styles/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export type { HeadingProps } from '../components/heading';
4949
export { Icon, IconFrame } from '../components/icon';
5050
export type { IconFrameProps, IconProps } from '../components/icon';
5151
export { Input } from '../components/input';
52-
export type { InputProps } from '../components/input';
52+
export type { InputProps, InputVariant } from '../components/input';
5353
export { Item } from '../components/item';
5454
export type { ItemProps } from '../components/item';
5555
export { Menu } from '../components/menu';

0 commit comments

Comments
 (0)