Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion components/component-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -9883,7 +9883,7 @@
"name": "array"
},
"required": false,
"description": "An array of menu item objects. `className` and `id` object keys are applied to the `li` DOM node. `divider` key can have a value of `top` or `bottom`. `rightIcon` and `leftIcon` are not actually `Icon` components, but prop objects that get passed to an `Icon` component. The `href` key will be added to the `a` and its default click event will be prevented. Here is a sample:\n```\n[{\n className: 'custom-li-class',\n divider: 'bottom',\n label: 'A Header',\n type: 'header'\n }, {\n href: 'http://sfdc.co/',\n id: 'custom-li-id',\n label: 'Has a value',\n leftIcon: {\n name: 'settings',\n category: 'utility'\n },\n rightIcon: {\n name: 'settings',\n category: 'utility'\n },\n type: 'item',\n value: 'B0'\n }, {\n type: 'divider'\n}]\n```"
"description": "An array of menu item objects. `className` and `id` object keys are applied to the `li` DOM node. `divider` key can have a value of `top` or `bottom`. `rightIcon` and `leftIcon` are not actually `Icon` components, but prop objects that get passed to an `Icon` component. The `href` key will be added to the `a` and its default click event will be prevented. Here is a sample:\n```\n[{\n className: 'custom-li-class',\n divider: 'bottom',\n label: 'A Header',\n type: 'header'\n }, {\n href: 'http://sfdc.co/',\n id: 'custom-li-id',\n label: 'Has a value',\n leftIcon: {\n name: 'settings',\n category: 'utility'\n },\n rightIcon: {\n name: 'settings',\n category: 'utility'\n },\n type: 'item',\n value: 'B0'\n }, {\n tooltipContent: 'Displays a tooltip when hovered over with this content. The `tooltipMenuItem` prop must be set for this to work.'\n type: 'divider'\n}]\n```"
},
"style": {
"type": {
Expand Down Expand Up @@ -9939,6 +9939,13 @@
"required": false,
"description": "This prop is passed onto the triggering `Button`. It creates a tooltip with the content of the `node` provided."
},
"tooltipMenuItem": {
"type": {
"name": "node"
},
"required": false,
"description": "Accepts a `Tooltip` component to be used as the template for menu item tooltips that appear via the `tooltipContent` options object attribute. Must be present for `tooltipContent` to work"
},
"triggerClassName": {
"type": {
"name": "union",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4688,3 +4688,43 @@ exports[`DOM snapshots SLDSMenuDropdown Two Hovers 1`] = `
</div>
</div>
`;

exports[`DOM snapshots SLDSMenuDropdown With tooltips 1`] = `
<div
className="slds-p-around_medium slds-text-align_center"
>
<div
className="slds-dropdown-trigger slds-dropdown-trigger_click"
id="dropdown-with-tooltips"
onClick={[Function]}
onFocus={null}
onKeyDown={[Function]}
onMouseEnter={null}
onMouseLeave={null}
>
<button
aria-expanded={false}
aria-haspopup={true}
className="slds-button slds-button_icon-border-filled ignore-click-dropdown-with-tooltips"
disabled={false}
onClick={[Function]}
tabIndex="0"
type="button"
>
<svg
aria-hidden="true"
className="slds-button__icon"
>
<use
xlinkHref="/assets/icons/utility-sprite/svg/symbols.svg#down"
/>
</svg>
<span
className="slds-assistive-text"
>
More Options
</span>
</button>
</div>
</div>
`;
4 changes: 3 additions & 1 deletion components/menu-dropdown/__docs__/storybook-stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import IconSettings from '../../icon-settings';
import { MENU_DROPDOWN } from '../../../utilities/constants';
import Dropdown from '../../menu-dropdown';
import { DropdownNubbinPositions } from '../../menu-dropdown/menu-dropdown';
import DropdownWithTooltips from '../__examples__/with-tooltips';
import List from '../../utilities/menu-list';
import Button from '../../button';
import Trigger from '../../menu-dropdown/button-trigger';
Expand Down Expand Up @@ -472,4 +473,5 @@ storiesOf(MENU_DROPDOWN, module)
label="Dropdown Click"
options={options}
/>
));
))
.add('With tooltips', () => <DropdownWithTooltips />);
47 changes: 47 additions & 0 deletions components/menu-dropdown/__examples__/with-tooltips.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';

import IconSettings from '~/components/icon-settings';
import Dropdown from '~/components/menu-dropdown'; // `~` is replaced with design-system-react at runtime
import Tooltip from '~/components/tooltip';

class Example extends React.Component {
static displayName = 'DropdownWithTooltipsExample';

render() {
return (
<IconSettings iconPath="/assets/icons">
<Dropdown
assistiveText={{ icon: 'More Options' }}
iconCategory="utility"
iconName="down"
iconVariant="border-filled"
id="dropdown-with-tooltips"
options={[
{ label: 'Header', type: 'header' },
{ label: 'Menu Item One', value: 'A0' },
{
label: 'Menu Item Two',
value: 'B0',
tooltipContent: 'Just a friendly tooltip',
},
{ label: 'Menu Item Three', value: 'C0' },
{ type: 'divider' },
{ label: 'Menu Item Four', value: 'D0' },
{ label: 'Menu Item Five', value: 'E0' },
{
label: 'Menu Item Six',
value: 'F0',
tooltipContent: 'Another friendly tooltip',
},
{ type: 'divider' },
{ label: 'Menu Item Seven', value: 'G0' },
]}
tooltipMenuItem={<Tooltip />}
{...this.props}
/>
</IconSettings>
);
}
}

export default Example; // export is replaced with `ReactDOM.render(<Example />, mountNode);` at runtime
34 changes: 34 additions & 0 deletions components/menu-dropdown/__tests__/dropdown.browser-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
// Import your internal dependencies (for example):
import Dropdown from '../../menu-dropdown';
import IconSettings from '../../icon-settings';
import Tooltip from '../../tooltip';
import List from '../../utilities/menu-list';
import { keyObjects } from '../../../utilities/key-code';

Expand Down Expand Up @@ -548,4 +549,37 @@ describe('SLDSMenuDropdown', function() {
}, 2);
});
});

describe('Tooltips function as expected', () => {
beforeEach(
mountComponent(
<DemoComponent
options={[
{ label: 'Test item A', value: 'A0' },
{
label: 'Test item B',
value: 'B0',
tooltipContent: 'Testing tooltip content',
},
{ label: 'Test item C', value: 'C0' },
]}
tooltipMenuItem={<Tooltip />}
/>
)
);

afterEach(unmountComponent);

it('Tooltip component shows when focused on menu item.', function() {
const nodes = getNodes({ wrapper: this.wrapper });
nodes.trigger.simulate('focus');
nodes.trigger.simulate('keyDown', keyObjects.ENTER);
nodes.trigger.simulate('keyDown', keyObjects.DOWN);

const tooltip = this.wrapper
.find('#sample-dropdown-item-1-tooltip')
.hostNodes();
expect(tooltip.length).to.equal(1);
});
});
});
3 changes: 2 additions & 1 deletion components/menu-dropdown/component.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"/__examples__/default-right-to-left.jsx",
"/__examples__/sub-heading.jsx",
"/__examples__/custom-trigger.jsx",
"/__examples__/checkmark.jsx"
"/__examples__/checkmark.jsx",
"/__examples__/with-tooltips.jsx"
],
"url-slug": "menu-dropdowns"
}
6 changes: 6 additions & 0 deletions components/menu-dropdown/menu-dropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ const propTypes = {
* type: 'item',
* value: 'B0'
* }, {
* tooltipContent: 'Displays a tooltip when hovered over with this content. The `tooltipMenuItem` prop must be set for this to work.'
* type: 'divider'
* }]
* ```
Expand Down Expand Up @@ -374,6 +375,10 @@ const propTypes = {
* This prop is passed onto the triggering `Button`. It creates a tooltip with the content of the `node` provided.
*/
tooltip: PropTypes.node,
/**
* Accepts a `Tooltip` component to be used as the template for menu item tooltips that appear via the `tooltipContent` options object attribute. Must be present for `tooltipContent` to work
*/
tooltipMenuItem: PropTypes.node,
/**
* CSS classes to be added to wrapping trigger `div` around the button.
*/
Expand Down Expand Up @@ -848,6 +853,7 @@ class MenuDropdown extends React.Component {
selectedIndices={
this.props.multiple ? this.state.selectedIndices : undefined
}
tooltipMenuItem={this.props.tooltipMenuItem}
triggerId={this.getId()}
length={this.props.length}
{...customListProps}
Expand Down
1 change: 1 addition & 0 deletions components/site-stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ const documentationSiteLiveExamples = {
require('raw-loader!@salesforce/design-system-react/components/menu-dropdown/__examples__/sub-heading.jsx'),
require('raw-loader!@salesforce/design-system-react/components/menu-dropdown/__examples__/custom-trigger.jsx'),
require('raw-loader!@salesforce/design-system-react/components/menu-dropdown/__examples__/checkmark.jsx'),
require('raw-loader!@salesforce/design-system-react/components/menu-dropdown/__examples__/with-tooltips.jsx'),
],
modal: [
require('raw-loader!@salesforce/design-system-react/components/modal/__examples__/menu-contents.jsx'),
Expand Down
52 changes: 51 additions & 1 deletion components/utilities/menu-list/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class List extends React.Component {
* The index of the currently selected item in the list.
*/
selectedIndex: PropTypes.number,
/**
* Accepts a `Tooltip` component to be used as the template for menu item tooltips that appear via the `tooltipContent` options object attribute
*/
tooltipMenuItem: PropTypes.node,
/**
* The id of the element which triggered this list (in a menu context).
*/
Expand All @@ -76,10 +80,13 @@ class List extends React.Component {

render() {
let lengthClassName;
let list;

if (this.props.length) {
lengthClassName = `slds-dropdown_length-${this.props.length}`;
}
return (

list = (
<ul
aria-labelledby={this.props.triggerId}
className={classNames(
Expand Down Expand Up @@ -110,11 +117,54 @@ class List extends React.Component {
labelRenderer={this.props.itemRenderer}
onSelect={this.props.onSelect}
ref={(listItem) => this.props.itemRefs(listItem, index)}
tooltipTemplate={this.props.tooltipMenuItem}
/>
);
})}
</ul>
);

if (this.props.tooltipMenuItem) {
/* eslint-disable react/no-danger */
list = (
<React.Fragment>
<style
dangerouslySetInnerHTML={{
__html: `.slds-dropdown__item > .slds-tooltip-trigger > a {
position: relative;
display: -ms-flexbox;
display: flex;
-ms-flex-pack: justify;
justify-content: space-between;
-ms-flex-align: center;
align-items: center;
padding: 0.5rem 0.75rem;
color: #080707;
white-space: nowrap;
cursor: pointer;
}

.slds-dropdown__item > .slds-tooltip-trigger > a:active {
text-decoration: none;
background-color: #ecebea;
}

.slds-dropdown__item > .slds-tooltip-trigger > a:hover,
.slds-dropdown__item > .slds-tooltip-trigger > a:focus {
outline: 0;
text-decoration: none;
background-color: #f3f2f2;
}
`,
}}
/>
{list}
</React.Fragment>
);
/* eslint-enable react/no-danger */
}

return list;
}
}

Expand Down
51 changes: 38 additions & 13 deletions components/utilities/menu-list/item.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class ListItem extends React.Component {
category: PropTypes.string,
name: PropTypes.string,
}),
tooltipContent: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
tooltipTemplate: PropTypes.node,
type: PropTypes.string,
value: PropTypes.any,
};
Expand Down Expand Up @@ -172,6 +174,41 @@ class ListItem extends React.Component {
case 'link':
case 'item':
default: {
/* eslint-disable jsx-a11y/role-supports-aria-props */
let itemContents = (
<a
aria-checked={this.props.checkmark && this.props.isSelected}
aria-disabled={this.props['aria-disabled']}
href={this.props.href}
data-index={this.props.index}
onClick={this.handleClick}
role={this.props.checkmark ? 'menuitemcheckbox' : 'menuitem'}
tabIndex="-1"
>
{this.getLabel()}
{this.getIcon('right')}
</a>
);

if (this.props.tooltipContent && this.props.tooltipTemplate) {
const {
...userDefinedTooltipProps
} = this.props.tooltipTemplate.props;
const tooltipProps = {
align: 'top',
content: this.props.tooltipContent, // either use specific content defined on option or content defined on tooltip component.
id: `${this.props.id}-tooltip`,
position: 'absolute',
triggerStyle: { width: '100%' },
...userDefinedTooltipProps, // we want to allow user defined tooltip pros to overwrite default props, if need be.
};
itemContents = React.cloneElement(
this.props.tooltipTemplate,
tooltipProps,
itemContents
);
}

return (
/* eslint-disable jsx-a11y/role-supports-aria-props */
// disabled eslint, but using aria-selected on presentation role seems suspicious...
Expand All @@ -190,19 +227,7 @@ class ListItem extends React.Component {
onMouseDown={this.handleMouseDown}
role="presentation"
>
{/* eslint-disable jsx-a11y/role-supports-aria-props */}
<a
aria-checked={this.props.checkmark && this.props.isSelected}
aria-disabled={this.props['aria-disabled']}
href={this.props.href}
data-index={this.props.index}
onClick={this.handleClick}
role={this.props.checkmark ? 'menuitemcheckbox' : 'menuitem'}
tabIndex="-1"
>
{this.getLabel()}
{this.getIcon('right')}
</a>
{itemContents}
</li>
);
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,8 @@
"/__examples__/default-right-to-left.jsx",
"/__examples__/sub-heading.jsx",
"/__examples__/custom-trigger.jsx",
"/__examples__/checkmark.jsx"
"/__examples__/checkmark.jsx",
"/__examples__/with-tooltips.jsx"
],
"url-slug": "menu-dropdowns"
},
Expand Down