Skip to content

Commit efd3e55

Browse files
Merge pull request #1689 from davidlygagnon/fieldLevelTooltipToCombobox
Add Field level tooltip to Combobox / Deprecate Input assistiveText.fieldLevelHelpButton
2 parents c80fdda + 06880c2 commit efd3e55

13 files changed

+784
-255
lines changed

components/combobox/__docs__/storybook-stories.jsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { action } from '@storybook/addon-actions';
55
import { COMBOBOX } from '../../../utilities/constants';
66

77
import Base from '../__examples__/base';
8+
import BaseInlineHelpTooltip from '../__examples__/base-inline-help-tooltip';
89
import BaseMenuSubHeader from '../__examples__/base-menu-subheader';
910
import BaseMenuSeparator from '../__examples__/base-menu-separator';
1011
import BaseInheritMenuWidth from '../__examples__/base-inherit-menu-width.jsx';
@@ -42,6 +43,14 @@ storiesOf(COMBOBOX, module)
4243
.add('Base Pre-defined Options Only', () => (
4344
<PredefinedOptionsOnly action={action} />
4445
))
46+
.add('Base Inline Help', () => (
47+
<section>
48+
<h1 className="slds-text-title_caps slds-p-vertical_medium">
49+
Field Level Help Tooltip
50+
</h1>
51+
<BaseInlineHelpTooltip action={action} />
52+
</section>
53+
))
4554
.add('Inline Single Selection', () => <InlineSingle action={action} />)
4655
.add('Inline Multiple Selection', () => <InlineMultiple action={action} />)
4756
.add('Base Custom Menu Item', () => <BaseCustomMenuItem action={action} />)
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/* eslint-disable no-console, react/prop-types */
2+
import React from 'react';
3+
import Combobox from '~/components/combobox';
4+
import comboboxFilterAndLimit from '~/components/combobox/filter';
5+
import Icon from '~/components/icon';
6+
import IconSettings from '~/components/icon-settings';
7+
import Tooltip from '../../tooltip';
8+
9+
const accounts = [
10+
{
11+
id: '1',
12+
label: 'Acme',
13+
subTitle: 'Account • San Francisco',
14+
type: 'account',
15+
},
16+
{
17+
id: '2',
18+
label: 'Salesforce.com, Inc.',
19+
subTitle: 'Account • San Francisco',
20+
type: 'account',
21+
},
22+
{
23+
id: '3',
24+
label: "Paddy's Pub",
25+
subTitle: 'Account • Boston, MA',
26+
type: 'account',
27+
},
28+
{
29+
id: '4',
30+
label: 'Tyrell Corp',
31+
subTitle: 'Account • San Francisco, CA',
32+
type: 'account',
33+
},
34+
{
35+
id: '5',
36+
label: 'Paper St. Soap Company',
37+
subTitle: 'Account • Beloit, WI',
38+
type: 'account',
39+
},
40+
{
41+
id: '6',
42+
label: 'Nakatomi Investments',
43+
subTitle: 'Account • Chicago, IL',
44+
type: 'account',
45+
},
46+
{ id: '7', label: 'Acme Landscaping', subTitle: '\u00A0', type: 'account' },
47+
{
48+
id: '8',
49+
label: 'Acme Construction',
50+
subTitle: 'Account • Grand Marais, MN',
51+
type: 'account',
52+
},
53+
];
54+
55+
const accountsWithIcon = accounts.map((elem) => ({
56+
...elem,
57+
...{
58+
icon: (
59+
<Icon
60+
assistiveText={{ label: 'Account' }}
61+
category="standard"
62+
name={elem.type}
63+
/>
64+
),
65+
},
66+
}));
67+
68+
class Example extends React.Component {
69+
constructor(props) {
70+
super(props);
71+
72+
this.state = {
73+
inputValue: '',
74+
selection: [accountsWithIcon[0], accountsWithIcon[1]],
75+
};
76+
}
77+
78+
render() {
79+
return (
80+
<IconSettings iconPath="/assets/icons">
81+
<Combobox
82+
id="combobox-unique-id"
83+
disabled={this.props.disabled}
84+
events={{
85+
onChange: (event, { value }) => {
86+
if (this.props.action) {
87+
this.props.action('onChange')(event, value);
88+
} else if (console) {
89+
console.log('onChange', event, value);
90+
}
91+
this.setState({ inputValue: value });
92+
},
93+
onRequestRemoveSelectedOption: (event, data) => {
94+
this.setState({
95+
inputValue: '',
96+
selection: data.selection,
97+
});
98+
},
99+
onSubmit: (event, { value }) => {
100+
if (this.props.action) {
101+
this.props.action('onChange')(event, value);
102+
} else if (console) {
103+
console.log('onChange', event, value);
104+
}
105+
this.setState({
106+
inputValue: '',
107+
selection: [
108+
...this.state.selection,
109+
{
110+
label: value,
111+
icon: (
112+
<Icon
113+
assistiveText="Account"
114+
category="standard"
115+
name="account"
116+
/>
117+
),
118+
},
119+
],
120+
});
121+
},
122+
onSelect: (event, data) => {
123+
if (this.props.action) {
124+
this.props.action('onSelect')(
125+
event,
126+
...Object.keys(data).map((key) => data[key])
127+
);
128+
} else if (console) {
129+
console.log('onSelect', event, data);
130+
}
131+
this.setState({
132+
inputValue: '',
133+
selection: data.selection,
134+
});
135+
},
136+
}}
137+
fieldLevelHelpTooltip={
138+
<Tooltip
139+
align="top left"
140+
content="Type to search Salesforce objects..."
141+
id="field-level-help-tooltip"
142+
/>
143+
}
144+
labels={{
145+
label: 'Search',
146+
placeholder: 'Search Salesforce',
147+
}}
148+
multiple
149+
options={comboboxFilterAndLimit({
150+
inputValue: this.state.inputValue,
151+
limit: 10,
152+
options: accountsWithIcon,
153+
selection: this.state.selection,
154+
})}
155+
selection={this.state.selection}
156+
value={this.state.inputValue}
157+
/>
158+
</IconSettings>
159+
);
160+
}
161+
}
162+
163+
Example.displayName = 'ComboboxExample';
164+
export default Example; // export is replaced with `ReactDOM.render(<Example />, mountNode);` at runtime

components/combobox/combobox.jsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Menu from './private/menu';
2727
import Label from '../forms/private/label';
2828
import SelectedListBox from '../pill-container/private/selected-listbox';
2929

30+
import FieldLevelHelpTooltip from '../tooltip/private/field-level-help-tooltip';
3031
import KEYS from '../../utilities/key-code';
3132
import KeyBuffer from '../../utilities/key-buffer';
3233
import keyLetterMenuItemSelect from '../../utilities/key-letter-menu-item-select';
@@ -126,6 +127,10 @@ const propTypes = {
126127
* Message to display when the input is in an error state. When this is present, also visually highlights the component as in error. _Tested with snapshot testing._
127128
*/
128129
errorText: PropTypes.string,
130+
/**
131+
* A [Tooltip](https://react.lightningdesignsystem.com/components/tooltips/) component that is displayed next to the `labels.label`. The props from the component will be merged and override any default props.
132+
*/
133+
fieldLevelHelpTooltip: PropTypes.node,
129134
/**
130135
* By default, dialogs will flip their alignment (such as bottom to top) if they extend beyond a boundary element such as a scrolling parent or a window/viewpoint. `hasStaticAlignment` disables this behavior and allows this component to extend beyond boundary elements. _Not tested._
131136
*/
@@ -1382,7 +1387,8 @@ class Combobox extends React.Component {
13821387
props.assistiveText
13831388
);
13841389
const labels = assign({}, defaultProps.labels, this.props.labels);
1385-
1390+
const hasRenderedLabel =
1391+
labels.label || (assistiveText && assistiveText.label);
13861392
const subRenderParameters = { assistiveText, labels, props: this.props };
13871393
const multipleOrSingle = this.props.multiple ? 'multiple' : 'single';
13881394
const subRenders = {
@@ -1411,6 +1417,11 @@ class Combobox extends React.Component {
14111417
label={labels.label}
14121418
required={props.required}
14131419
/>
1420+
{this.props.fieldLevelHelpTooltip && hasRenderedLabel ? (
1421+
<FieldLevelHelpTooltip
1422+
fieldLevelHelpTooltip={this.props.fieldLevelHelpTooltip}
1423+
/>
1424+
) : null}
14141425
{variantExists
14151426
? subRenders[this.props.variant][multipleOrSingle](
14161427
subRenderParameters

components/component-docs.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3386,6 +3386,26 @@
33863386
}
33873387
],
33883388
"returns": null
3389+
},
3390+
{
3391+
"name": "renderFieldLevelHelpTooltip",
3392+
"docblock": null,
3393+
"modifiers": [],
3394+
"params": [
3395+
{
3396+
"name": "fieldLevelHelpTooltip",
3397+
"type": null
3398+
},
3399+
{
3400+
"name": "labels",
3401+
"type": null
3402+
},
3403+
{
3404+
"name": "assistiveText",
3405+
"type": null
3406+
}
3407+
],
3408+
"returns": null
33893409
}
33903410
],
33913411
"props": {
@@ -3561,6 +3581,13 @@
35613581
"required": false,
35623582
"description": "Message to display when the input is in an error state. When this is present, also visually highlights the component as in error. _Tested with snapshot testing._"
35633583
},
3584+
"fieldLevelHelpTooltip": {
3585+
"type": {
3586+
"name": "node"
3587+
},
3588+
"required": false,
3589+
"description": "A [Tooltip](https://react.lightningdesignsystem.com/components/tooltips/) component that is displayed next to the `labels.label`. The props from the component will be merged and override any default props."
3590+
},
35643591
"hasStaticAlignment": {
35653592
"type": {
35663593
"name": "bool"

components/input/__examples__/field-level-help.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ class Example extends React.Component {
1818
</h1>
1919
<Input
2020
id="field-level-help"
21-
assistiveText={{ fieldLevelHelpButton: 'Info' }}
2221
label="My Label"
2322
fieldLevelHelpTooltip={
2423
<Tooltip

components/input/check-props.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ if (process.env.NODE_ENV !== 'production') {
2121
const iconDeprecatedMessage = `Please use \`iconLeft\` and \`iconRight\` to pass in a customized <Icon> component. ${createDocUrl()}`;
2222

2323
// Deprecated and changed to another property
24+
deprecatedProperty(
25+
COMPONENT,
26+
props.assistiveText.fieldLevelHelpButton,
27+
'assistiveText.fieldLevelHelpButton',
28+
undefined,
29+
`Please pass a \`Tooltip\` component into \`fieldLevelHelpTooltip\` with \`assistiveText.triggerLearnMoreIcon\`.`
30+
);
2431
deprecatedProperty(
2532
COMPONENT,
2633
props.iconCategory,

components/input/index.jsx

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import classNames from 'classnames';
2525
import shortid from 'shortid';
2626

2727
import Button from '../button';
28-
import Tooltip from '../tooltip';
2928

3029
// ## Children
3130
import InputIcon from '../icon/input-icon';
@@ -37,6 +36,7 @@ import checkProps from './check-props';
3736

3837
import { INPUT } from '../../utilities/constants';
3938
import componentDoc from './docs.json';
39+
import FieldLevelHelpTooltip from '../tooltip/private/field-level-help-tooltip';
4040

4141
const COUNTER = 'counter';
4242
const DECREMENT = 'Decrement';
@@ -45,7 +45,6 @@ const INCREMENT = 'Increment';
4545
const defaultProps = {
4646
assistiveText: {
4747
decrement: `${DECREMENT} ${COUNTER}`,
48-
fieldLevelHelpButton: 'Help',
4948
increment: `${INCREMENT} ${COUNTER}`,
5049
},
5150
type: 'text',
@@ -104,12 +103,10 @@ class Input extends React.Component {
104103
* **Assistive text for accessibility**
105104
* * `label`: Visually hidden label but read out loud by screen readers.
106105
* * `spinner`: Text for loading spinner icon.
107-
* * `fieldLevelHelpButton`: The field level help button, by default an 'info' icon.
108106
*/
109107
assistiveText: PropTypes.shape({
110108
label: PropTypes.string,
111109
spinner: PropTypes.string,
112-
fieldLevelHelpButton: PropTypes.string,
113110
}),
114111
/**
115112
* Elements are added after the `input`.
@@ -141,7 +138,7 @@ class Input extends React.Component {
141138
*/
142139
errorText: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
143140
/**
144-
* A [Tooltip](https://react.lightningdesignsystem.com/components/tooltips/) component that is displayed next to the label. The props from the component will be merged and override any default props.
141+
* A [Tooltip](https://react.lightningdesignsystem.com/components/tooltips/) component that is displayed next to the label.
145142
*/
146143
fieldLevelHelpTooltip: PropTypes.node,
147144
/**
@@ -539,33 +536,11 @@ class Input extends React.Component {
539536
};
540537
const inputRef =
541538
this.props.variant === COUNTER ? this.setInputRef : this.props.inputRef;
542-
let fieldLevelHelpTooltip;
543539
let iconLeft = null;
544540
let iconRight = null;
545541

546-
if (
547-
(this.props.label ||
548-
(this.props.assistiveText && this.props.assistiveText.label)) &&
549-
this.props.fieldLevelHelpTooltip
550-
) {
551-
const defaultTooltipProps = {
552-
triggerClassName: 'slds-form-element__icon',
553-
triggerStyle: { position: 'static' },
554-
children: (
555-
<Button
556-
assistiveText={{ icon: assistiveText.fieldLevelHelpButton }}
557-
iconCategory="utility"
558-
iconName="info"
559-
variant="icon"
560-
/>
561-
),
562-
};
563-
const tooltipProps = {
564-
...defaultTooltipProps,
565-
...this.props.fieldLevelHelpTooltip.props,
566-
};
567-
fieldLevelHelpTooltip = <Tooltip {...tooltipProps} />;
568-
}
542+
const hasRenderedLabel =
543+
this.props.label || (assistiveText && assistiveText.label);
569544

570545
// Remove at next breaking change
571546
// this is a hack to make left the default prop unless overwritten by `iconPosition="right"`
@@ -608,13 +583,20 @@ class Input extends React.Component {
608583
)}
609584
>
610585
<Label
611-
assistiveText={this.props.assistiveText}
586+
assistiveText={assistiveText}
612587
htmlFor={this.props.isStatic ? undefined : this.getId()}
613588
label={this.props.label}
614589
required={this.props.required}
615590
variant={this.props.isStatic ? 'static' : 'base'}
616591
/>
617-
{fieldLevelHelpTooltip}
592+
{this.props.fieldLevelHelpTooltip && hasRenderedLabel ? (
593+
<FieldLevelHelpTooltip
594+
assistiveText={{
595+
triggerLearnMoreIcon: assistiveText.fieldLevelHelpButton,
596+
}}
597+
fieldLevelHelpTooltip={this.props.fieldLevelHelpTooltip}
598+
/>
599+
) : null}
618600
<InnerInput
619601
aria-activedescendant={this.props['aria-activedescendant']}
620602
aria-autocomplete={this.props['aria-autocomplete']}

0 commit comments

Comments
 (0)