Skip to content

Commit

Permalink
Input API alignment and styling fixes (#21935)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecraig12345 authored Mar 29, 2022
1 parent d88aa37 commit 3e9421d
Show file tree
Hide file tree
Showing 17 changed files with 206 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ storiesOf('Input Converged', module)
.addStory('Size: large', () => <Input size="large" placeholder="Placeholder" />)
.addStory('Inline', () => (
<p>
Some text with <Input inline placeholder="hello" style={{ width: '75px' }} /> inline input
Some text with <Input placeholder="hello" style={{ width: '75px' }} /> inline input
</p>
))
.addStory(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "BREAKING CHANGE: Remove `inline` prop and use `display: inline-flex` by default",
"packageName": "@fluentui/react-input",
"email": "[email protected]",
"dependentChangeType": "patch"
}
16 changes: 8 additions & 8 deletions packages/react-input/Spec-styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ Notes:

### Varying by field size

| Style | Application | small | medium | large |
| ----------------------------- | ------------------------------ | ------------------- | ---------------- | ------------- |
| height | root `minHeight` | 24px | 32px | 40px |
| left/right padding | root | sNudge | mNudge | m |
| left/right padding in content | input | xxs | xxs | sNudge |
| content size | root, input (doesn't inherit) | caption1 (base.200) | body1 (base.300) | base.400 |
| ~~"icon" size~~ | n/a (icons not built in) | ~~16Regular~~ | ~~20Regular~~ | ~~24Regular~~ |
| spacing within root | root `display: flex`, flex gap | xxs | xxs | sNudge |
| Style | Application | small | medium | large |
| ----------------------------- | ------------------------------ | ------------------- | ---------------- | --------- |
| height | root `minHeight` | 24px | 32px | 40px |
| left/right padding | root | sNudge | mNudge | m |
| left/right padding in content | input | xxs | xxs | sNudge |
| content size | root, input (doesn't inherit) | caption1 (base.200) | body1 (base.300) | base.400 |
| "icon" size | contentBefore/after `> svg` | 16Regular | 20Regular | 24Regular |
| spacing within root | root `display: flex`, flex gap | xxs | xxs | sNudge |

## Appearance colors and strokes

Expand Down
5 changes: 2 additions & 3 deletions packages/react-input/etc/react-input.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ export type InputOnChangeData = {
export type InputProps = Omit<ComponentProps<Partial<InputSlots>, 'input'>, 'children' | 'defaultValue' | 'onChange' | 'size' | 'type' | 'value'> & {
children?: never;
size?: 'small' | 'medium' | 'large';
inline?: boolean;
appearance?: 'outline' | 'underline' | 'filledDarker' | 'filledLighter';
defaultValue?: string;
value?: string;
onChange?: (ev: React_2.FormEvent<HTMLInputElement>, data: InputOnChangeData) => void;
onChange?: (ev: React_2.ChangeEvent<HTMLInputElement>, data: InputOnChangeData) => void;
type?: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url' | 'date' | 'datetime-local' | 'month' | 'number' | 'time' | 'week';
};

Expand All @@ -46,7 +45,7 @@ export type InputSlots = {
};

// @public
export type InputState = Required<Pick<InputProps, 'appearance' | 'inline' | 'size'>> & ComponentState<InputSlots>;
export type InputState = Required<Pick<InputProps, 'appearance' | 'size'>> & ComponentState<InputSlots>;

// @public
export const renderInput_unstable: (state: InputState) => JSX.Element;
Expand Down
10 changes: 2 additions & 8 deletions packages/react-input/src/components/Input/Input.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ export type InputProps = Omit<
// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/size
size?: 'small' | 'medium' | 'large';

/**
* If true, the input will have inline display, allowing it be used within text content.
* If false (the default), the input will have block display.
*/
inline?: boolean;

/**
* Controls the colors and borders of the input.
* @default 'outline'
Expand All @@ -75,7 +69,7 @@ export type InputProps = Omit<
/**
* Called when the user changes the input's value.
*/
onChange?: (ev: React.FormEvent<HTMLInputElement>, data: InputOnChangeData) => void;
onChange?: (ev: React.ChangeEvent<HTMLInputElement>, data: InputOnChangeData) => void;

/**
* An input can have different text-based [types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#input_types)
Expand Down Expand Up @@ -106,7 +100,7 @@ export type InputProps = Omit<
/**
* State used in rendering Input.
*/
export type InputState = Required<Pick<InputProps, 'appearance' | 'inline' | 'size'>> & ComponentState<InputSlots>;
export type InputState = Required<Pick<InputProps, 'appearance' | 'size'>> & ComponentState<InputSlots>;

/**
* Data passed to the `onChange` callback when a user changes the input's value.
Expand Down
3 changes: 1 addition & 2 deletions packages/react-input/src/components/Input/useInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { InputProps, InputState } from './Input.types';
* @param ref - reference to `<input>` element of Input
*/
export const useInput_unstable = (props: InputProps, ref: React.Ref<HTMLInputElement>): InputState => {
const { size = 'medium', appearance = 'outline', inline = false, onChange } = props;
const { size = 'medium', appearance = 'outline', onChange } = props;

const [value, setValue] = useControllableState({
state: props.value,
Expand All @@ -34,7 +34,6 @@ export const useInput_unstable = (props: InputProps, ref: React.Ref<HTMLInputEle
const state: InputState = {
size,
appearance,
inline,
components: {
root: 'span',
input: 'input',
Expand Down
18 changes: 12 additions & 6 deletions packages/react-input/src/components/Input/useInputStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const fieldHeights = {

const useRootStyles = makeStyles({
base: {
display: 'flex',
display: 'inline-flex',
alignItems: 'center',
flexWrap: 'nowrap',
...shorthands.gap(horizontalSpacing.xxs),
Expand Down Expand Up @@ -129,9 +129,6 @@ const useRootStyles = makeStyles({
...contentSizes[400],
...shorthands.gap(horizontalSpacing.sNudge),
},
inline: {
display: 'inline-flex',
},
outline: {
backgroundColor: tokens.colorNeutralBackground1,
...shorthands.border('1px', 'solid', tokens.colorNeutralStroke1),
Expand Down Expand Up @@ -241,6 +238,16 @@ const useContentStyles = makeStyles({
disabled: {
color: tokens.colorNeutralForegroundDisabled,
},
// Ensure resizable icons show up with the proper font size
small: {
'> svg': { fontSize: '16px' },
},
medium: {
'> svg': { fontSize: '20px' },
},
large: {
'> svg': { fontSize: '24px' },
},
});

/**
Expand All @@ -264,7 +271,6 @@ export const useInputStyles_unstable = (state: InputState): InputState => {
!disabled && appearance === 'outline' && rootStyles.outlineInteractive,
!disabled && appearance === 'underline' && rootStyles.underlineInteractive,
!disabled && filled && rootStyles.filledInteractive,
state.inline && rootStyles.inline,
filled && rootStyles.filled,
disabled && rootStyles.disabled,
state.root.className,
Expand All @@ -278,7 +284,7 @@ export const useInputStyles_unstable = (state: InputState): InputState => {
state.input.className,
);

const contentClasses = [contentStyles.base, disabled && contentStyles.disabled];
const contentClasses = [contentStyles.base, disabled && contentStyles.disabled, contentStyles[size]];
if (state.contentBefore) {
state.contentBefore.className = mergeClasses(
inputClassNames.contentBefore,
Expand Down
18 changes: 0 additions & 18 deletions packages/react-input/src/stories/Input.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as React from 'react';
import { Meta } from '@storybook/react';
import { Input } from '../index';

Expand All @@ -24,23 +23,6 @@ const meta: Meta = {
},
},
},
decorators: [
(Story, context) => (
<div
style={{
...(context.viewMode === 'docs' && {
// docs mode has buttons on the bottom right which cover the input
// if it's allowed to be full width
maxWidth: '500px',
// and the corners of the rounded box clip the example
padding: '24px',
}),
}}
>
<Story />
</div>
),
],
};

export default meta;
29 changes: 21 additions & 8 deletions packages/react-input/src/stories/InputAppearance.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,28 @@ import { Input } from '../index';

const useStyles = makeStyles({
root: {
'& label': { display: 'block', paddingBottom: '2px' },
// filledLighter and filledDarker appearances depend on particular background colors,
// so the story includes wrapper divs around the example of each appearance
'> div': { ...shorthands.padding('20px'), ...shorthands.borderRadius('20px') },
'> div:not(:first-child)': { paddingTop: '10px' },
display: 'flex',
flexDirection: 'column',
...shorthands.gap('20px'),
maxWidth: '400px',
'> div': {
// Stack the label above the field (with 2px gap per the design system)
display: 'flex',
flexDirection: 'column',
...shorthands.gap('2px'),
// Align the examples horizontally to all match the extra padding on filled examples (below)
paddingLeft: '20px',
paddingRight: '20px',
},
},
// filledLighter and filledDarker appearances depend on particular background colors
filledLighter: {
backgroundColor: tokens.colorNeutralBackground2,
...shorthands.borderRadius('20px'),
...shorthands.padding('20px'),
},
filledDarker: { backgroundColor: tokens.colorNeutralBackground1 },
// ideally should match doc site, #faf9f8
filledLighter: { backgroundColor: tokens.colorNeutralBackground2 },
// By default this will match the example background, so don't add padding above
filledDarker: { backgroundColor: tokens.colorNeutralBackground1, paddingBottom: '20px' },
});

export const Appearance = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import * as React from 'react';
import { Label } from '@fluentui/react-label';
import { useId } from '@fluentui/react-utilities';
import { makeStyles } from '@griffel/react';
import { makeStyles, shorthands } from '@griffel/react';
import { SearchRegular, DismissRegular } from '@fluentui/react-icons';
import { Input } from '../index';

const useStyles = makeStyles({
root: {
'& label': { display: 'block', paddingBottom: '2px' },
'& label:not(:first-child)': { paddingTop: '20px' },
// Icons default to 1em; we want them a bit larger
'& svg': { fontSize: '20px' },
display: 'flex',
flexDirection: 'column',
...shorthands.gap('20px'),
// Prevent the example from taking the full width of the page (optional)
maxWidth: '400px',
// Stack the label above the field (with 2px gap per the design system)
'> div': { display: 'flex', flexDirection: 'column', ...shorthands.gap('2px') },
},
});

Expand All @@ -22,14 +25,20 @@ export const ContentBeforeAfter = () => {

return (
<div className={styles.root}>
<Label htmlFor={beforeId}>Content before</Label>
<Input contentBefore={<SearchRegular />} id={beforeId} />
<div>
<Label htmlFor={beforeId}>Content before</Label>
<Input contentBefore={<SearchRegular />} id={beforeId} />
</div>

<Label htmlFor={afterId}>Content after</Label>
<Input contentAfter={<DismissRegular />} id={afterId} />
<div>
<Label htmlFor={afterId}>Content after</Label>
<Input contentAfter={<DismissRegular />} id={afterId} />
</div>

<Label htmlFor={bothId}>Content before and after</Label>
<Input contentBefore={<SearchRegular />} contentAfter={<DismissRegular />} id={bothId} />
<div>
<Label htmlFor={bothId}>Content before and after</Label>
<Input contentBefore={<SearchRegular />} contentAfter={<DismissRegular />} id={bothId} />
</div>
</div>
);
};
Expand Down
21 changes: 15 additions & 6 deletions packages/react-input/src/stories/InputControlled.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import * as React from 'react';
import { Label } from '@fluentui/react-label';
import { useId } from '@fluentui/react-utilities';
import { makeStyles, shorthands } from '@griffel/react';
import { Input } from '../index';
import type { InputProps } from '../index';

const labelStyle = { display: 'block', paddingBottom: '2px' };
const useStyles = makeStyles({
root: {
display: 'flex',
flexDirection: 'column',
// Use 2px gap below the label (per the design system)
...shorthands.gap('2px'),
// Prevent the example from taking the full width of the page (optional)
maxWidth: '400px',
},
});

export const Controlled = () => {
const inputId = useId('input');
const [value, setValue] = React.useState('initial value');
const styles = useStyles();

const onChange: InputProps['onChange'] = (ev, data) => {
// The controlled input pattern can be used for other purposes besides validation,
Expand All @@ -19,12 +30,10 @@ export const Controlled = () => {
};

return (
<>
<Label htmlFor={inputId} style={labelStyle}>
Controlled input limiting the value to 20 characters
</Label>
<div className={styles.root}>
<Label htmlFor={inputId}>Controlled input limiting the value to 20 characters</Label>
<Input value={value} onChange={onChange} id={inputId} />
</>
</div>
);
};

Expand Down
21 changes: 17 additions & 4 deletions packages/react-input/src/stories/InputDefault.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@ import * as React from 'react';
import { ArgTypes } from '@storybook/api';
import { Label } from '@fluentui/react-label';
import { useId } from '@fluentui/react-utilities';
import { makeStyles, shorthands } from '@griffel/react';
import { Input } from '../index';
import type { InputProps } from '../index';

const labelStyle = { display: 'block', paddingBottom: '2px' };
const useStyles = makeStyles({
root: {
// Stack the label above the field
display: 'flex',
flexDirection: 'column',
// Use 2px gap below the label (per the design system)
...shorthands.gap('2px'),
// Prevent the example from taking the full width of the page (optional)
maxWidth: '400px',
},
});

export const Default = (props: InputProps) => {
const inputId = useId('input');
const styles = useStyles();

return (
<>
<Label htmlFor={inputId} size={props.size} disabled={props.disabled} style={labelStyle}>
<div className={styles.root}>
<Label htmlFor={inputId} size={props.size} disabled={props.disabled}>
Sample input
</Label>
<Input id={inputId} {...props} />
</>
</div>
);
};

Expand Down
21 changes: 17 additions & 4 deletions packages/react-input/src/stories/InputDisabled.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import * as React from 'react';
import { Label } from '@fluentui/react-label';
import { useId } from '@fluentui/react-utilities';
import { makeStyles, shorthands } from '@griffel/react';
import { Input } from '../index';

const labelStyle = { display: 'block', paddingBottom: '2px' };
const useStyles = makeStyles({
root: {
// Stack the label above the field
display: 'flex',
flexDirection: 'column',
// Use 2px gap below the label (per the design system)
...shorthands.gap('2px'),
// Prevent the example from taking the full width of the page (optional)
maxWidth: '400px',
},
});

export const Disabled = () => {
const inputId = useId('input');
const styles = useStyles();

return (
<>
<Label disabled htmlFor={inputId} style={labelStyle}>
<div className={styles.root}>
<Label disabled htmlFor={inputId}>
Disabled input
</Label>
<Input disabled id={inputId} defaultValue="disabled value" />
</>
</div>
);
};

Expand Down
Loading

0 comments on commit 3e9421d

Please sign in to comment.