Skip to content

Commit

Permalink
fix(ui-select,ui-text-input): fix long before elements overflowing in…
Browse files Browse the repository at this point in the history
… TextInput, Select, SimpleSelect

Closes: INSTUI-4344

When adding lots of elements to renderBeforeInput for example Tags it was overflowing the input
field. Also when there are more than one line of elements keep the input field in the same line
if there is enough space
TEST PLAN:
Add lots of elements to renderBeforeInput to Select,SimpleSelect,TextInput. They should wrap,
not overflow
  • Loading branch information
matyasf committed Dec 6, 2024
1 parent ef3e930 commit ee9cafd
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 95 deletions.
14 changes: 3 additions & 11 deletions packages/ui-form-field/src/FormFieldLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { Component } from 'react'
import { hasVisibleChildren } from '@instructure/ui-a11y-utils'
import { ScreenReaderContent } from '@instructure/ui-a11y-content'
import { Grid } from '@instructure/ui-grid'
import { logError as error } from '@instructure/console'
import {
omitProps,
pickProps,
Expand Down Expand Up @@ -67,16 +66,7 @@ class FormFieldLayout extends Component<FormFieldLayoutProps> {

constructor(props: FormFieldLayoutProps) {
super(props)

this._messagesId = props.messagesId || props.deterministicId!()

error(
typeof props.width !== 'undefined' ||
!props.inline ||
props.layout !== 'inline',
`[FormFieldLayout] The 'inline' prop is true, and the 'layout' is set to 'inline'.
This will cause a layout issue in Internet Explorer 11 unless you also add a value for the 'width' prop.`
)
}

private _messagesId: string
Expand Down Expand Up @@ -212,7 +202,9 @@ class FormFieldLayout extends Component<FormFieldLayoutProps> {
elementRef={this.handleInputContainerRef}
>
{hasNewErrorMsg && (
<div css={styles?.groupErrorMessage}>{this.renderVisibleMessages()}</div>
<div css={styles?.groupErrorMessage}>
{this.renderVisibleMessages()}
</div>
)}
{children}
</Grid.Col>
Expand Down
3 changes: 2 additions & 1 deletion packages/ui-react-utils/src/omitProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
* Return an object with the remaining props after the given props are omitted.
*
* Automatically excludes the following props:
* 'theme', 'children', 'className', 'style', 'styles', 'makeStyles', 'themeOverride', 'deterministicId'
* `theme`, `children`, `className`, `style`, `styles`, `makeStyles`,
* `themeOverride`, `deterministicId`
* @module omitProps
* @param props The object to process
* @param propsToOmit list disallowed prop keys or an object whose
Expand Down
8 changes: 6 additions & 2 deletions packages/ui-select/src/Select/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,9 @@ To mark an option as "highlighted", use the option's `isHighlighted` prop. Note
{this.getOptionById(id).label}
</AccessibleContent>
}
margin={index > 0 ? 'xxx-small 0 xxx-small xx-small' : 'xxx-small 0'}
margin={
index > 0 ? 'xxx-small xx-small xxx-small 0' : '0 xx-small 0 0'
}
onClick={(e) => this.dismissTag(e, id)}
/>
))
Expand Down Expand Up @@ -1079,7 +1081,9 @@ To mark an option as "highlighted", use the option's `isHighlighted` prop. Note
{this.getOptionById(id).label}
</AccessibleContent>
}
margin={index > 0 ? 'xxx-small 0 xxx-small xx-small' : 'xxx-small 0'}
margin={
index > 0 ? 'xxx-small xx-small xxx-small 0' : '0 xx-small 0 0'
}
onClick={(e) => dismissTag(e, id)}
/>
))
Expand Down
13 changes: 7 additions & 6 deletions packages/ui-text-input/src/TextInput/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Focusable content will be focused separately from the input itself.
value={this.state.value}
onChange={this.handleChange}
renderBeforeInput={
<View display="block" padding="xxx-small 0">
<>
{this.state.value !== '' && (
<Tag
text={this.state.value}
Expand Down Expand Up @@ -233,7 +233,7 @@ Focusable content will be focused separately from the input itself.
margin="xxx-small xxx-small xxx-small none"
onClick={() => console.log('Strawberry')}
/>
</View>
</>
}
renderAfterInput={() => (
<Avatar name="Paula Panda" src={avatarSquare} size="x-small" />
Expand All @@ -260,7 +260,7 @@ Focusable content will be focused separately from the input itself.
value={value}
onChange={handleChange}
renderBeforeInput={
<View display="block" padding="xxx-small 0">
<>
{value !== '' && (
<Tag
text={value}
Expand Down Expand Up @@ -288,7 +288,7 @@ Focusable content will be focused separately from the input itself.
margin="xxx-small xxx-small xxx-small none"
onClick={() => console.log('Strawberry')}
/>
</View>
</>
}
renderAfterInput={() => (
<Avatar name="Paula Panda" src={avatarSquare} size="x-small" />
Expand Down Expand Up @@ -346,7 +346,7 @@ type: example
<TextInput
renderLabel="I will wrap"
renderBeforeInput={
<div>
<>
<Tag
text="English 101"
margin="xx-small xxx-small"
Expand All @@ -355,7 +355,7 @@ type: example
text="History 205"
margin="xx-small xxx-small"
/>
</div>
</>
}
renderAfterInput={<Avatar name="Paula Panda" src={avatarSquare} size="x-small" />}
/>
Expand All @@ -374,6 +374,7 @@ type: embed
<Figure.Item>Left align text (exceptions may apply)</Figure.Item>
<Figure.Item>Place labels on top or to the left (inline)</Figure.Item>
<Figure.Item>Make placeholder text different than the label</Figure.Item>
<Figure.Item>Use React fragments for <code>renderBeforeInput</code>. This will nicely float the text input box to the remaining space</Figure.Item>
</Figure>
<Figure recommendation="no" title="Don't">
<Figure.Item>Place labels to the right of the input</Figure.Item>
Expand Down
55 changes: 15 additions & 40 deletions packages/ui-text-input/src/TextInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ class TextInput extends Component<TextInputProps, TextInputState> {
super(props)
this.state = {
hasFocus: false,
beforeElementHasWidth: undefined,
afterElementHasWidth: undefined
}
this._defaultId = props.deterministicId!()
Expand All @@ -87,7 +86,6 @@ class TextInput extends Component<TextInputProps, TextInputState> {
ref: Element | null = null

private _input: HTMLInputElement | null = null
private _beforeElement: HTMLSpanElement | null = null
private _afterElement: HTMLSpanElement | null = null

private readonly _defaultId: string
Expand All @@ -112,13 +110,10 @@ class TextInput extends Component<TextInputProps, TextInputState> {
'focus',
this.handleFocus
)

this.setState({
beforeElementHasWidth: this.getElementHasWidth(this._beforeElement),
afterElementHasWidth: this.getElementHasWidth(this._afterElement)
})
}

this.props.makeStyles?.(this.makeStyleProps())
}

Expand All @@ -129,11 +124,6 @@ class TextInput extends Component<TextInputProps, TextInputState> {
}

componentDidUpdate(prevProps: TextInputProps) {
if (prevProps.renderBeforeInput !== this.props.renderBeforeInput) {
this.setState({
beforeElementHasWidth: this.getElementHasWidth(this._beforeElement)
})
}
if (prevProps.renderAfterInput !== this.props.renderAfterInput) {
this.setState({
afterElementHasWidth: this.getElementHasWidth(this._afterElement)
Expand All @@ -154,13 +144,13 @@ class TextInput extends Component<TextInputProps, TextInputState> {

makeStyleProps = (): TextInputStyleProps => {
const { interaction } = this
const { hasFocus, beforeElementHasWidth, afterElementHasWidth } = this.state
const { hasFocus, afterElementHasWidth } = this.state
return {
disabled: interaction === 'disabled',
invalid: this.invalid,
focused: hasFocus,
beforeElementHasWidth,
afterElementHasWidth
afterElementHasWidth: afterElementHasWidth,
beforeElementExists: this.props.renderBeforeInput != undefined
}
}

Expand Down Expand Up @@ -282,7 +272,7 @@ class TextInput extends Component<TextInputProps, TextInputState> {
)
}

getElementHasWidth(element: HTMLSpanElement | null) {
getElementHasWidth(element: Element | null) {
if (!element) {
return undefined
}
Expand Down Expand Up @@ -360,39 +350,24 @@ class TextInput extends Component<TextInputProps, TextInputState> {
>
<span css={styles?.facade}>
{renderBeforeOrAfter ? (
<div>
<span css={styles?.layout}>
{beforeElement && (
<span css={styles?.layout}>
{beforeElement}
{/* The input and content after input should not wrap,
so they're in their own flex container */}
<span css={styles?.inputLayout}>
{this.renderInput()}
{afterElement && (
<span
css={styles?.beforeElement}
css={styles?.afterElement}
ref={(e) => {
this._beforeElement = e
this._afterElement = e
}}
>
{beforeElement}
{afterElement}
</span>
)}
<span css={styles?.innerWrapper}>
{/*
The input and content after input should not wrap,
so they're in their own flex container
*/}
<span css={styles?.inputLayout}>
<span css={styles?.innerWrapper}>{this.renderInput()}</span>
{afterElement && (
<span
css={styles?.afterElement}
ref={(e) => {
this._afterElement = e
}}
>
{afterElement}
</span>
)}
</span>
</span>
</span>
</div>
</span>
) : (
/* If no prepended or appended content, don't render Flex layout */
this.renderInput()
Expand Down
7 changes: 2 additions & 5 deletions packages/ui-text-input/src/TextInput/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,9 @@ type TextInputStyle = ComponentStyle<
| 'textInput'
| 'facade'
| 'layout'
| 'beforeElement'
| 'innerWrapper'
| 'inputLayout'
| 'afterElement'
| 'requiredInvalid'
| 'inputLayout'
>

const propTypes: PropValidators<PropKeys> = {
Expand Down Expand Up @@ -254,16 +252,15 @@ const allowedProps: AllowedPropKeys = [

type TextInputState = {
hasFocus: boolean
beforeElementHasWidth?: boolean
afterElementHasWidth?: boolean
}

type TextInputStyleProps = {
disabled: boolean
invalid: boolean
focused: TextInputState['hasFocus']
beforeElementHasWidth: TextInputState['beforeElementHasWidth']
afterElementHasWidth: TextInputState['afterElementHasWidth']
beforeElementExists: boolean
}

export type {
Expand Down
42 changes: 12 additions & 30 deletions packages/ui-text-input/src/TextInput/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ const generateStyle = (
disabled,
invalid,
focused,
beforeElementHasWidth,
afterElementHasWidth
afterElementHasWidth,
beforeElementExists
} = state

const sizeVariants = {
Expand Down Expand Up @@ -103,15 +103,14 @@ const generateStyle = (

const inputStyle = {
all: 'initial',

'&::-ms-clear': {
display: 'none'
},
width: '100%',
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
appearance: 'none',
margin: 0,
width: '100%',
display: 'block',
boxSizing: 'border-box',
outline: 'none',
Expand Down Expand Up @@ -151,11 +150,6 @@ const generateStyle = (
flexDirection: 'row'
}

const flexItemBase = {
...viewBase,
flexShrink: 0
}

return {
requiredInvalid: {
color: componentTheme.requiredInvalidColor
Expand Down Expand Up @@ -198,30 +192,17 @@ const generateStyle = (
},
layout: {
label: 'textInput__layout',
...flexBase,
...(!shouldNotWrap && { flexWrap: 'wrap' })
},
beforeElement: {
display: 'inline-flex',
...viewBase,
display: 'flex',
alignItems: 'center',
label: 'textInput__beforeElement',
...flexItemBase,
paddingInlineStart: componentTheme.padding,
// we only override the padding once the width is calculated,
// it needs the padding on render
...(beforeElementHasWidth === false && {
paddingInlineStart: 0
})
},
innerWrapper: {
label: 'textInput__innerWrapper',
...flexItemBase,
minWidth: '0.0625rem',
flexShrink: 1,
flexGrow: 1
justifyContent: 'flex-start',
flexDirection: 'row',
...(!shouldNotWrap && { flexWrap: 'wrap' }),
...(beforeElementExists && { paddingInlineStart: componentTheme.padding })
},
inputLayout: {
label: 'textInput__inputLayout',
flexGrow: 1,
...flexBase
},
afterElement: {
Expand All @@ -231,7 +212,8 @@ const generateStyle = (
marginTop: '-1px',
marginBottom: '-1px',
label: 'textInput__afterElement',
...flexItemBase,
...viewBase,
flexShrink: 0,
paddingInlineEnd: componentTheme.padding,
// we only override the padding once the width is calculated,
// it needs the padding on render
Expand Down

0 comments on commit ee9cafd

Please sign in to comment.