Skip to content

Commit

Permalink
[OutlinedInput] Fix offscreen label strikethrough (#17680)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored Jan 21, 2020
1 parent 784f5fc commit c29c8ad
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 55 deletions.
3 changes: 2 additions & 1 deletion docs/pages/api/outlined-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">inputComponent</span> | <span class="prop-type">elementType</span> | <span class="prop-default">'input'</span> | The component used for the native input. Either a string to use a DOM element or a component. |
| <span class="prop-name">inputProps</span> | <span class="prop-type">object</span> | | [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes) applied to the `input` element. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">ref</span> | | Pass a ref to the `input` element. |
| <span class="prop-name">labelWidth</span> | <span class="prop-type">number</span> | <span class="prop-default">0</span> | The width of the label. |
| <span class="prop-name">label</span> | <span class="prop-type">node</span> | | The label of the input. It is only used for layout. The actual labelling is handled by `InputLabel`. If specified `labelWidth` is ignored. |
| <span class="prop-name">labelWidth</span> | <span class="prop-type">number</span> | <span class="prop-default">0</span> | The width of the label. Is ignored if `label` is provided. Prefer `label` if the input label appears with a strike through. |
| <span class="prop-name">margin</span> | <span class="prop-type">'dense'<br>&#124;&nbsp;'none'</span> | | If `dense`, will adjust vertical spacing. This is normally obtained via context from FormControl. |
| <span class="prop-name">multiline</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, a textarea element will be rendered. |
| <span class="prop-name">name</span> | <span class="prop-type">string</span> | | Name attribute of the `input` element. |
Expand Down
3 changes: 2 additions & 1 deletion docs/pages/api/select.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">IconComponent</span> | <span class="prop-type">elementType</span> | <span class="prop-default">ArrowDropDownIcon</span> | The icon that displays the arrow. |
| <span class="prop-name">input</span> | <span class="prop-type">element</span> | | An `Input` element; does not have to be a material-ui specific `Input`. |
| <span class="prop-name">inputProps</span> | <span class="prop-type">object</span> | | [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes) applied to the `input` element. When `native` is `true`, the attributes are applied on the `select` element. |
| <span class="prop-name">label</span> | <span class="prop-type">node</span> | | See [OutlinedLabel#label](/api/outlined-input/#props) |
| <span class="prop-name">labelId</span> | <span class="prop-type">string</span> | | The idea of an element that acts as an additional label. The Select will be labelled by the additional label and the selected value. |
| <span class="prop-name">labelWidth</span> | <span class="prop-type">number</span> | <span class="prop-default">0</span> | The label width to be used on OutlinedInput. This prop is required when the `variant` prop is `outlined`. |
| <span class="prop-name">labelWidth</span> | <span class="prop-type">number</span> | <span class="prop-default">0</span> | See OutlinedLabel#label |
| <span class="prop-name">MenuProps</span> | <span class="prop-type">object</span> | | Props applied to the [`Menu`](/api/menu/) element. |
| <span class="prop-name">multiple</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, `value` must be an array and the menu will support multiple selections. |
| <span class="prop-name">native</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the component will be using a native `select` element. |
Expand Down
17 changes: 2 additions & 15 deletions docs/src/pages/components/text-fields/ComposedTextField.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,9 @@ const useStyles = makeStyles(theme => ({
}));

export default function ComposedTextField() {
const [labelWidth, setLabelWidth] = React.useState(0);
const [name, setName] = React.useState('Composed TextField');
const labelRef = React.useRef(null);
const classes = useStyles();

React.useEffect(() => {
setLabelWidth(labelRef.current.offsetWidth);
}, []);

const handleChange = event => {
setName(event.target.value);
};
Expand Down Expand Up @@ -61,15 +55,8 @@ export default function ComposedTextField() {
<FormHelperText id="component-error-text">Error</FormHelperText>
</FormControl>
<FormControl variant="outlined">
<InputLabel ref={labelRef} htmlFor="component-outlined">
Name
</InputLabel>
<OutlinedInput
id="component-outlined"
value={name}
onChange={handleChange}
labelWidth={labelWidth}
/>
<InputLabel htmlFor="component-outlined">Name</InputLabel>
<OutlinedInput id="component-outlined" value={name} onChange={handleChange} label="Name" />
</FormControl>
<FormControl variant="filled">
<InputLabel htmlFor="component-filled">Name</InputLabel>
Expand Down
17 changes: 2 additions & 15 deletions docs/src/pages/components/text-fields/ComposedTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,9 @@ const useStyles = makeStyles((theme: Theme) =>
);

export default function ComposedTextField() {
const [labelWidth, setLabelWidth] = React.useState(0);
const [name, setName] = React.useState('Composed TextField');
const labelRef = React.useRef<HTMLLabelElement>(null);
const classes = useStyles();

React.useEffect(() => {
setLabelWidth(labelRef.current!.offsetWidth);
}, []);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value);
};
Expand Down Expand Up @@ -63,15 +57,8 @@ export default function ComposedTextField() {
<FormHelperText id="component-error-text">Error</FormHelperText>
</FormControl>
<FormControl variant="outlined">
<InputLabel ref={labelRef} htmlFor="component-outlined">
Name
</InputLabel>
<OutlinedInput
id="component-outlined"
value={name}
onChange={handleChange}
labelWidth={labelWidth}
/>
<InputLabel htmlFor="component-outlined">Name</InputLabel>
<OutlinedInput id="component-outlined" value={name} onChange={handleChange} label="Name" />
</FormControl>
<FormControl variant="filled">
<InputLabel htmlFor="component-filled">Name</InputLabel>
Expand Down
59 changes: 56 additions & 3 deletions packages/material-ui/src/OutlinedInput/NotchedOutline.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const styles = theme => {
left: 0,
margin: 0,
padding: 0,
paddingLeft: 8,
pointerEvents: 'none',
borderRadius: 'inherit',
borderStyle: 'solid',
Expand All @@ -28,16 +29,40 @@ export const styles = theme => {
easing: theme.transitions.easing.easeOut,
}),
},
/* Styles applied to the legend element. */
/* Styles applied to the legend element when `labelWidth` is provided. */
legend: {
textAlign: 'left',
padding: 0,
lineHeight: '11px',
lineHeight: '11px', // sync with `height` in `legend` styles
transition: theme.transitions.create('width', {
duration: theme.transitions.duration.shorter,
easing: theme.transitions.easing.easeOut,
}),
},
/* Styles applied to the legend element. */
legendLabelled: {
textAlign: 'left',
padding: 0,
height: 11, // sync with `lineHeight` in `legend` styles
fontSize: '0.75rem',
visibility: 'hidden',
maxWidth: 0.01,
transition: theme.transitions.create('max-width', {
duration: theme.transitions.duration.shorter,
easing: theme.transitions.easing.easeOut,
}),
'& span': {
paddingLeft: 4,
paddingRight: 6,
},
},
legendNotched: {
maxWidth: 1000,
transition: theme.transitions.create('max-width', {
duration: theme.transitions.duration.shorter,
easing: theme.transitions.easing.easeOut,
}),
},
};
};

Expand All @@ -49,14 +74,38 @@ const NotchedOutline = React.forwardRef(function NotchedOutline(props, ref) {
children,
classes,
className,
label,
labelWidth: labelWidthProp,
notched,
style,
...other
} = props;
const theme = useTheme();
const align = theme.direction === 'rtl' ? 'right' : 'left';
const labelWidth = labelWidthProp > 0 ? labelWidthProp * 0.75 + 8 : 0;

if (label !== undefined) {
return (
<fieldset
aria-hidden
className={clsx(classes.root, className)}
ref={ref}
style={style}
{...other}
>
<legend
className={clsx(classes.legendLabelled, {
[classes.legendNotched]: notched,
})}
>
{/* Use the nominal use case of the legend, avoid rendering artefacts. */}
{/* eslint-disable-next-line react/no-danger */}
{label ? <span>{label}</span> : <span dangerouslySetInnerHTML={{ __html: '&#8203;' }} />}
</legend>
</fieldset>
);
}

const labelWidth = labelWidthProp > 0 ? labelWidthProp * 0.75 + 8 : 0.01;

return (
<fieldset
Expand Down Expand Up @@ -100,6 +149,10 @@ NotchedOutline.propTypes = {
* @ignore
*/
className: PropTypes.string,
/**
* The label.
*/
label: PropTypes.node,
/**
* The width of the label.
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/material-ui/src/OutlinedInput/OutlinedInput.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { StandardProps } from '..';
import { InputBaseProps } from '../InputBase';

export interface OutlinedInputProps extends StandardProps<InputBaseProps, OutlinedInputClassKey> {
label?: React.ReactNode;
notched?: boolean;
labelWidth: number;
labelWidth?: number;
}

export type OutlinedInputClassKey =
Expand Down
10 changes: 9 additions & 1 deletion packages/material-ui/src/OutlinedInput/OutlinedInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const OutlinedInput = React.forwardRef(function OutlinedInput(props, ref) {
classes,
fullWidth = false,
inputComponent = 'input',
label,
labelWidth = 0,
multiline = false,
notched,
Expand All @@ -115,6 +116,7 @@ const OutlinedInput = React.forwardRef(function OutlinedInput(props, ref) {
renderSuffix={state => (
<NotchedOutline
className={classes.notchedOutline}
label={label}
labelWidth={labelWidth}
notched={
typeof notched !== 'undefined'
Expand Down Expand Up @@ -201,7 +203,13 @@ OutlinedInput.propTypes = {
*/
inputRef: refType,
/**
* The width of the label.
* The label of the input. It is only used for layout. The actual labelling
* is handled by `InputLabel`. If specified `labelWidth` is ignored.
*/
label: PropTypes.node,
/**
* The width of the label. Is ignored if `label` is provided. Prefer `label`
* if the input label appears with a strike through.
*/
labelWidth: PropTypes.number,
/**
Expand Down
1 change: 1 addition & 0 deletions packages/material-ui/src/Select/Select.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface SelectProps
displayEmpty?: boolean;
IconComponent?: React.ElementType;
input?: React.ReactNode;
label?: React.ReactNode;
labelId?: string;
labelWidth?: number;
MenuProps?: Partial<MenuProps>;
Expand Down
10 changes: 7 additions & 3 deletions packages/material-ui/src/Select/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const Select = React.forwardRef(function Select(props, ref) {
id,
input,
inputProps,
label,
labelId,
labelWidth = 0,
MenuProps,
Expand Down Expand Up @@ -53,7 +54,7 @@ const Select = React.forwardRef(function Select(props, ref) {
input ||
{
standard: <Input />,
outlined: <OutlinedInput labelWidth={labelWidth} />,
outlined: <OutlinedInput label={label} labelWidth={labelWidth} />,
filled: <FilledInput />,
}[variant];

Expand Down Expand Up @@ -141,14 +142,17 @@ Select.propTypes = {
* When `native` is `true`, the attributes are applied on the `select` element.
*/
inputProps: PropTypes.object,
/**
* See [OutlinedLabel#label](/api/outlined-input/#props)
*/
label: PropTypes.node,
/**
* The idea of an element that acts as an additional label. The Select will
* be labelled by the additional label and the selected value.
*/
labelId: PropTypes.string,
/**
* The label width to be used on OutlinedInput.
* This prop is required when the `variant` prop is `outlined`.
* See OutlinedLabel#label
*/
labelWidth: PropTypes.number,
/**
Expand Down
22 changes: 9 additions & 13 deletions packages/material-ui/src/TextField/TextField.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { refType } from '@material-ui/utils';
Expand Down Expand Up @@ -93,16 +92,6 @@ const TextField = React.forwardRef(function TextField(props, ref) {
...other
} = props;

const [labelWidth, setLabelWidth] = React.useState(0);
const labelRef = React.useRef(null);
React.useEffect(() => {
if (variant === 'outlined') {
// #StrictMode ready
const labelNode = ReactDOM.findDOMNode(labelRef.current);
setLabelWidth(labelNode != null ? labelNode.offsetWidth : 0);
}
}, [variant, required, label]);

if (process.env.NODE_ENV !== 'production') {
if (select && !children) {
console.error(
Expand All @@ -118,7 +107,14 @@ const TextField = React.forwardRef(function TextField(props, ref) {
InputMore.notched = InputLabelProps.shrink;
}

InputMore.labelWidth = labelWidth;
InputMore.label = label ? (
<React.Fragment>
{label}
{required && '\u00a0*'}
</React.Fragment>
) : (
label
);
}
if (select) {
// unset defaults from textbox inputs
Expand Down Expand Up @@ -170,7 +166,7 @@ const TextField = React.forwardRef(function TextField(props, ref) {
{...other}
>
{label && (
<InputLabel htmlFor={id} ref={labelRef} id={inputLabelId} {...InputLabelProps}>
<InputLabel htmlFor={id} id={inputLabelId} {...InputLabelProps}>
{label}
</InputLabel>
)}
Expand Down
14 changes: 12 additions & 2 deletions packages/material-ui/src/TextField/TextField.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,19 @@ describe('<TextField />', () => {

describe('with an outline', () => {
it('should set outline props', () => {
const wrapper = mount(<TextField variant="outlined" />);
const { container, getAllByTestId } = render(
<TextField
InputProps={{ classes: { notchedOutline: 'notch' } }}
label={<div data-testid="label">label</div>}
required
variant="outlined"
/>,
);

expect(wrapper.find(OutlinedInput).props()).to.have.property('labelWidth', 0);
const [, fakeLabel] = getAllByTestId('label');
const notch = container.querySelector('.notch legend');
expect(notch).to.contain(fakeLabel);
expect(notch).to.have.text('label\u00a0*');
});

it('should set shrink prop on outline from label', () => {
Expand Down

0 comments on commit c29c8ad

Please sign in to comment.