Skip to content

Commit 0969a00

Browse files
committed
feat(ui-text-input,ui-select): add contentSpacing prop for even wrapped-tag padding in multiple Select
INSTUI-5079
1 parent 86ef310 commit 0969a00

9 files changed

Lines changed: 128 additions & 37 deletions

File tree

packages/ui-select/src/Select/v2/README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ type: example
340340
341341
#### Highlighting and selecting options
342342
343-
To mark an option as "highlighted", use the option's `isHighlighted` prop. Note that only one highlighted option is permitted. Similarly, use `isSelected` to mark an option or multiple options as "selected". When allowing multiple selections, it's best to render a [Tag](Tag) with [AccessibleContent](AccessibleContent) for each selected option via the `renderBeforeInput` prop.
343+
To mark an option as "highlighted", use the option's `isHighlighted` prop. Note that only one highlighted option is permitted. Similarly, use `isSelected` to mark an option or multiple options as "selected". When allowing multiple selections, it's best to render a [Tag](Tag) with [AccessibleContent](AccessibleContent) for each selected option via the `renderBeforeInput` prop. Set `renderBeforeInputElementGap="even"` so wrapped tags are evenly spaced even as they wrap to multiple rows.
344344
345345
> **Accessibility:** set an explicit `aria-label` on the `Select` (matching `renderLabel`) and list the selected options in the `assistiveText` (e.g. `"Alaska Selected, …"`), as the example below does. This keeps the combobox's accessible name limited to the field label, while screen reader users still hear which options are selected. Each pill's `"Remove …"` label is only announced when that pill receives focus.
346346
@@ -491,9 +491,6 @@ type: example
491491
{getOptionById(id).label}
492492
</AccessibleContent>
493493
}
494-
margin={
495-
index > 0 ? 'xxx-small xx-small xxx-small 0' : '0 xx-small 0 0'
496-
}
497494
onClick={(e) => dismissTag(e, id)}
498495
/>
499496
))
@@ -518,6 +515,7 @@ type: example
518515
renderLabel="Multiple Select"
519516
aria-label="Multiple Select"
520517
assistiveText={assistiveText}
518+
renderBeforeInputElementGap="even"
521519
inputValue={inputValue}
522520
isShowingOptions={isShowingOptions}
523521
inputRef={(el) => {
@@ -531,6 +529,7 @@ type: example
531529
onRequestSelectOption={handleSelectOption}
532530
onKeyDown={handleKeyDown}
533531
renderBeforeInput={selectedOptionId.length > 0 ? renderTags() : null}
532+
htmlSize={2}
534533
>
535534
{filteredOptions.length > 0 ? (
536535
filteredOptions.map((option, index) => {

packages/ui-select/src/Select/v2/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ class Select extends Component<SelectProps> {
703703
isInline,
704704
width,
705705
htmlSize,
706+
renderBeforeInputElementGap,
706707
messages,
707708
renderBeforeInput,
708709
renderAfterInput,
@@ -756,6 +757,7 @@ class Select extends Component<SelectProps> {
756757
size,
757758
width,
758759
htmlSize,
760+
renderBeforeInputElementGap,
759761
messages,
760762
value: inputValue,
761763
inputRef: utils.createChainedFunction(ref, this.handleInputRef),

packages/ui-select/src/Select/v2/props.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,14 @@ type PropsFromTextInput = {
248248
*/
249249
shouldNotWrap?: boolean
250250

251+
/**
252+
* Whether to apply a gap between the elements rendered via `renderBeforeInput`
253+
* (a CSS flex container), e.g. tags.
254+
* - `'default'`: no gap is applied between the elements.
255+
* - `'even'`: wrapped rows are evenly spaced with a vertical and horizontal gap.
256+
*/
257+
renderBeforeInputElementGap?: 'default' | 'even'
258+
251259
/**
252260
* In `stacked` mode the input is below the label.
253261
*
@@ -325,6 +333,7 @@ const allowedProps: AllowedPropKeys = [
325333
'renderAfterInput',
326334
'children',
327335
'shouldNotWrap',
336+
'renderBeforeInputElementGap',
328337
'scrollToHighlightedOption',
329338
'layout'
330339
]

packages/ui-text-input/src/TextInput/v2/README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ type: example
9797

9898
TextInput accepts focusable and non-focusable content before and/or after
9999
the input text. A common use case is adding an icon or avatar to the input.
100-
Focusable content will be focused separately from the input itself.
100+
Focusable content will be focused separately from the input itself. When
101+
rendering multiple elements before the input (such as tags), set
102+
`renderBeforeInputElementGap="even"` to evenly space them, including as they
103+
wrap to multiple rows.
101104

102105
```js
103106
---
@@ -121,33 +124,29 @@ type: example
121124
renderLabel="What are Paula Panda's favorite ice cream flavors?"
122125
value={this.state.value}
123126
onChange={this.handleChange}
127+
renderBeforeInputElementGap="even"
124128
renderBeforeInput={
125129
<>
126130
{this.state.value !== '' && (
127131
<Tag
128132
text={this.state.value}
129-
margin="general.space2xs general.spaceXs general.space2xs none"
130133
onClick={() => console.log(this.state.value)}
131134
/>
132135
)}
133136
<Tag
134137
text="Rocky road"
135-
margin="general.space2xs general.spaceXs general.space2xs none"
136138
onClick={() => console.log('Rocky road')}
137139
/>
138140
<Tag
139141
text="Vanilla"
140-
margin="general.space2xs general.spaceXs general.space2xs none"
141142
onClick={() => console.log('Vanilla')}
142143
/>
143144
<Tag
144145
text="Coffee"
145-
margin="general.space2xs general.spaceXs general.space2xs none"
146146
onClick={() => console.log('Coffee')}
147147
/>
148148
<Tag
149149
text="Strawberry"
150-
margin="general.space2xs general.spaceXs general.space2xs none"
151150
onClick={() => console.log('Strawberry')}
152151
/>
153152
</>

packages/ui-text-input/src/TextInput/v2/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class TextInput extends Component<TextInputProps> {
6161
isRequired: false,
6262
display: 'block',
6363
shouldNotWrap: false,
64+
renderBeforeInputElementGap: 'default',
6465
size: 'medium',
6566
textAlign: 'start',
6667
messages: []

packages/ui-text-input/src/TextInput/v2/props.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ type TextInputOwnProps = {
121121
*/
122122
shouldNotWrap?: boolean
123123

124+
/**
125+
* Whether to apply a gap between the elements rendered via `renderBeforeInput`
126+
* (a CSS flex container), e.g. tags.
127+
* - `'default'`: no gap is applied between the elements.
128+
* - `'even'`: wrapped rows are evenly spaced with a vertical and horizontal gap.
129+
*/
130+
renderBeforeInputElementGap?: 'default' | 'even'
131+
124132
/**
125133
* Html placeholder text to display when the input has no value. This should be hint text, not a label replacement.
126134
*/
@@ -218,6 +226,7 @@ const allowedProps: AllowedPropKeys = [
218226
'htmlSize',
219227
'display',
220228
'shouldNotWrap',
229+
'renderBeforeInputElementGap',
221230
'placeholder',
222231
'isRequired',
223232
'elementRef',

packages/ui-text-input/src/TextInput/v2/styles.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,49 @@ const generateStyle = (
4343
sharedTokens: SharedTokens,
4444
state: TextInputStyleProps
4545
): TextInputStyle => {
46-
const { size, textAlign, shouldNotWrap } = props
46+
const { size, textAlign, shouldNotWrap, renderBeforeInputElementGap } = props
4747
const { interaction, success, invalid, beforeElementExists } = state
4848

49+
const isEvenSpacing = renderBeforeInputElementGap === 'even'
50+
51+
const contentGap = {
52+
small: sharedTokens.spacing.general.spaceXs,
53+
medium: sharedTokens.spacing.general.spaceXs,
54+
large: sharedTokens.spacing.general.spaceSm
55+
}[size!]
56+
57+
const contentPaddingBlock = {
58+
small: sharedTokens.spacing.general.space2xs,
59+
medium: sharedTokens.spacing.general.space2xs,
60+
large: sharedTokens.spacing.general.spaceXs
61+
}[size!]
62+
63+
const heightTokens = {
64+
small: componentTheme.heightSm,
65+
medium: componentTheme.heightMd,
66+
large: componentTheme.heightLg
67+
}
68+
69+
const inputHeightStyle = (sizeHeight: string) =>
70+
isEvenSpacing
71+
? { height: 'auto' }
72+
: {
73+
height: `calc(${sizeHeight} - (2 * ${componentTheme.borderWidth}))`,
74+
lineHeight: `calc(${sizeHeight} - (2 * ${componentTheme.borderWidth}))`
75+
}
76+
4977
const sizeVariants = {
5078
small: {
5179
fontSize: componentTheme.fontSizeSm,
52-
height: `calc(${componentTheme.heightSm} - (2 * ${componentTheme.borderWidth}))`,
53-
lineHeight: `calc(${componentTheme.heightSm} - (2 * ${componentTheme.borderWidth}))`
80+
...inputHeightStyle(componentTheme.heightSm)
5481
},
5582
medium: {
5683
fontSize: componentTheme.fontSizeMd,
57-
height: `calc(${componentTheme.heightMd} - (2 * ${componentTheme.borderWidth}))`,
58-
lineHeight: `calc(${componentTheme.heightMd} - (2 * ${componentTheme.borderWidth}))`
84+
...inputHeightStyle(componentTheme.heightMd)
5985
},
6086
large: {
6187
fontSize: componentTheme.fontSizeLg,
62-
height: `calc(${componentTheme.heightLg} - (2 * ${componentTheme.borderWidth}))`,
63-
lineHeight: `calc(${componentTheme.heightLg} - (2 * ${componentTheme.borderWidth}))`
88+
...inputHeightStyle(componentTheme.heightLg)
6489
}
6590
}
6691
const paddingHorizontalVariants = {
@@ -194,6 +219,11 @@ const generateStyle = (
194219
// left padding of the `renderBeforeInput` element
195220
...(beforeElementExists && {
196221
paddingInlineStart: paddingHorizontalVariants[size!]
222+
}),
223+
...(isEvenSpacing && {
224+
minHeight: heightTokens[size!],
225+
paddingBlock: contentPaddingBlock,
226+
gap: contentGap
197227
})
198228
},
199229
inputLayout: {
@@ -206,10 +236,12 @@ const generateStyle = (
206236
flexDirection: 'row'
207237
},
208238
beforeElement: {
209-
...(interaction === 'disabled'
239+
label: 'textInput__beforeElement',
240+
...(interaction === 'disabled' && !isEvenSpacing
210241
? { opacity: 0.5 }
211242
: { display: 'contents' }),
212-
label: 'textInput__beforeElement'
243+
...(interaction === 'disabled' &&
244+
isEvenSpacing && { '& > *': { opacity: 0.5 } })
213245
},
214246
afterElement: {
215247
...(interaction === 'disabled' && { opacity: 0.5 }),

pnpm-lock.yaml

Lines changed: 12 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)