Skip to content
This repository has been archived by the owner on May 24, 2024. It is now read-only.

[terra-application-navigation]Add action button to user config #306

Merged
merged 1 commit into from
Aug 29, 2022
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
3 changes: 3 additions & 0 deletions packages/terra-application/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Added
* Added user action utility button.

## 1.53.1 - (June 22, 2022)

* Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import uuidv4 from 'uuid/v4';
import TerraApplicationNavigation from './private/ApplicationNavigation';
import {
titleConfigPropType, navigationItemsPropType, extensionItemsPropType, utilityItemsPropType, userConfigPropType,
titleConfigPropType, navigationItemsPropType, extensionItemsPropType, utilityItemsPropType, userConfigPropType, userActionConfigPropType,
} from './private/utils/propTypes';

import ApplicationErrorBoundary from '../application-error-boundary';
Expand Down Expand Up @@ -113,6 +113,10 @@ const propTypes = {
* A configuration object that defines the strings rendered within the ApplicationNavigation header.
*/
titleConfig: titleConfigPropType,
/**
* A configuration object to render an action button for user Config.
*/
userActionConfig: userActionConfigPropType,
/**
* A configuration object with information pertaining to the application's user.
*/
Expand Down Expand Up @@ -150,6 +154,7 @@ const ApplicationNavigation = ({
userConfig,
utilityItems,
workspace,
userActionConfig,
}) => {
const applicationIntl = React.useContext(ApplicationIntlContext);
const navigationPromptCheckpointRef = useRef();
Expand Down Expand Up @@ -187,6 +192,7 @@ const ApplicationNavigation = ({
onSelectNavigationItem={propOnSelectNavigationItem && onSelectNavigationItem}
activeNavigationItemKey={activeNavigationItemKey}
userConfig={userConfig}
userActionConfig={userActionConfig}
extensionItems={extensionItems}
onSelectExtensionItem={onSelectExtensionItem}
utilityItems={utilityItems}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import DrawerMenu from './drawer-menu/_DrawerMenu';
import UtilityMenu from './utility-menu/_UtilityMenu';
import { shouldRenderCompactNavigation } from './utils/helpers';
import {
titleConfigPropType, userConfigPropType, navigationItemsPropType, extensionItemsPropType, utilityItemsPropType,
titleConfigPropType, userConfigPropType, navigationItemsPropType, extensionItemsPropType, utilityItemsPropType, userActionConfigPropType,
} from './utils/propTypes';
import WorkspaceLayout from './workspace-layout/WorkspaceLayout';

Expand Down Expand Up @@ -95,6 +95,10 @@ const propTypes = {
* Ex: `onSelectLogout()`
*/
onSelectLogout: PropTypes.func,
/**
* A configuration object to render an action button for user Config.
*/
userActionConfig: userActionConfigPropType,
/**
* An array of configuration objects with information specifying the creation of additional utility menu items.
* These items are rendered within the popup utility menu at larger breakpoints and within the drawer menu at smaller breakpoints.
Expand Down Expand Up @@ -140,6 +144,7 @@ const ApplicationNavigation = ({
notifications,
children,
workspace,
userActionConfig,
}) => {
const drawerMenuRef = useRef();
const contentLayoutRef = useRef();
Expand Down Expand Up @@ -235,6 +240,7 @@ const ApplicationNavigation = ({
<DrawerMenu
titleConfig={titleConfig}
userConfig={userConfig}
userActionConfig={userActionConfig}
hero={hero}
navigationItems={navigationItems}
activeNavigationItemKey={activeNavigationItemKey}
Expand Down Expand Up @@ -270,6 +276,7 @@ const ApplicationNavigation = ({
<UtilityMenu
hero={hero}
userConfig={userConfig}
userActionConfig={userActionConfig}
onSelectSettings={onSelectSettings ? generateMenuClosingCallback(onSelectSettings) : undefined}
onSelectHelp={onSelectHelp ? generateMenuClosingCallback(onSelectHelp) : undefined}
onSelectLogout={onSelectLogout ? generateMenuClosingCallback(onSelectLogout) : undefined}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
--terra-application-navigation-drawer-count-background-color: #ffda6c;
--terra-application-navigation-drawer-count-border: 1px solid #ffb20b;
--terra-application-navigation-drawer-count-color: #000;

--terra-application-navigation-drawer-menu-action-button-background-color: #222a2e;
--terra-application-navigation-drawer-menu-action-button-border: 1px solid #868a8c;
--terra-application-navigation-drawer-menu-action-button-border-radius: 3px;
--terra-application-navigation-drawer-menu-action-button-color: #b2b5b6;
--terra-application-navigation-drawer-menu-action-button-active-background-color: #000;
--terra-application-navigation-drawer-menu-action-button-active-color: #b2b5b6;
--terra-application-navigation-drawer-menu-action-button-focus-outline: 2px dashed #dae2e7;
--terra-application-navigation-drawer-menu-action-button-focus-outline-offset: 1px;
--terra-application-navigation-drawer-menu-action-button-hover-background-color: #181b1d;
--terra-application-navigation-drawer-menu-action-button-hover-color: #b2b5b6;

--terra-application-navigation-drawer-menu-user-large-avatar-inner-font-size: 1.6521rem;
--terra-application-navigation-drawer-menu-avatar-outline-border: 0.1429rem solid #181b1d;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
}

.info-container {
align-items: flex-start;
display: flex;
flex: 1 1 auto;
flex-direction: column;
Expand All @@ -41,6 +42,10 @@
white-space: normal;
word-break: break-word;
word-wrap: break-word;
}
}

.action-button {
margin-top: 0.2142857143rem;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {
useRef,
useEffect,
} from 'react';
import PropTypes from 'prop-types';
import classNamesBind from 'classnames/bind';
Expand All @@ -16,8 +17,8 @@ import {
} from 'keycode-js';

import PopupMenuListItem from './_PopupMenuListItem';
import { userConfigPropType } from '../utils/propTypes';
import { logoutUtilityItemId } from '../utils/helpers';
import { userConfigPropType, userActionConfigPropType } from '../utils/propTypes';
import { logoutUtilityItemId, userActionItemId } from '../utils/helpers';
import PopupMenuUser from './_PopupMenuUser';

import styles from './PopupMenu.module.scss';
Expand Down Expand Up @@ -102,6 +103,10 @@ const propTypes = {
* Role of the parent ul that child items should match.
*/
role: PropTypes.oneOf(['list', 'menu', 'listbox']),
/**
* A configuration object to render an action button for user Config.
*/
userActionConfig: userActionConfigPropType,
};

const defaultProps = {
Expand All @@ -112,14 +117,17 @@ const defaultProps = {
};

const PopupMenu = ({
title, footerText, id, onSelectFooterItem, onSelectMenuItem, customContent, userConfig, menuItems, isHeightBounded, showSelections, role,
title, footerText, id, onSelectFooterItem, onSelectMenuItem, customContent, userConfig, menuItems, isHeightBounded, showSelections, role, userActionConfig,
}) => {
const listRef = useRef();
const buttonRef = useRef();

function setButtonRef(node) {
buttonRef.current = node;
}
useEffect(() => {
listRef.current.focus();
}, []);

function handleArrowDown(event) {
if (listRef.current.hasChildNodes()) {
Expand Down Expand Up @@ -201,7 +209,7 @@ const PopupMenu = ({
{customContent}
</div>
) : undefined}
{userConfig ? <PopupMenuUser userConfig={userConfig} /> : null}
{userConfig ? <PopupMenuUser id={id && userActionItemId(id)} userActionConfig={userActionConfig} userConfig={userConfig} /> : null}
<ul className={cx('utility-list')} aria-label={title} ref={listRef} role={role} tabIndex="0" onKeyDown={handleKeyDown}>
{menuItems.map(item => (
<PopupMenuListItem
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames/bind';
import Avatar, { Generic } from 'terra-avatar';
import ThemeContext from 'terra-theme-context';
import Button from 'terra-button';

import { userConfigPropType } from '../utils/propTypes';
import { userConfigPropType, userActionConfigPropType } from '../utils/propTypes';

import styles from './PopupMenuUser.module.scss';

const cx = classNames.bind(styles);

const propTypes = {
/**
* An id for the user action button
*/
id: PropTypes.string,
/**
* A configuration object to render an action button for user Config.
*/
userActionConfig: userActionConfigPropType,
/**
* A configuration object with information pertaining to the application's user.
*/
userConfig: userConfigPropType.isRequired,
};

const PopupMenuUser = ({ userConfig }) => {
const PopupMenuUser = ({ userConfig, userActionConfig, id }) => {
const theme = React.useContext(ThemeContext);

return (
Expand All @@ -32,6 +42,17 @@ const PopupMenuUser = ({ userConfig }) => {
<div className={cx('info-container')}>
<div aria-hidden className={cx('name')}>{userConfig.name}</div>
{userConfig.detail ? <div className={cx('detail')}>{userConfig.detail}</div> : null}
{ userActionConfig && (
<Button
id={id || undefined}
text={userActionConfig.text}
onClick={userActionConfig.userActionCallback}
data-navigation-utility-item-logout
className={cx('action-button')}
variant="ghost"
isCompact
/>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,40 @@
.avatar-inner {
z-index: 0;
}

.drawer-menu-action-button {
background-color: var(--terra-application-navigation-drawer-menu-action-button-background-color, #004c76);
border: var(--terra-application-navigation-drawer-menu-action-button-border, 1px solid #a7aaab);
border-radius: var(--terra-application-navigation-drawer-menu-action-button-border-radius, 3px);
color: var(--terra-application-navigation-drawer-menu-action-button-color, #dedfe0);
font-size: 1rem;
outline: none;
overflow-wrap: break-word; /* Modern browsers */
padding: 3px 0.7142857143rem;
text-align: center;
text-decoration: none;
text-transform: none;
touch-action: manipulation; // Enable fast tap interaction in webkit browsers; see https://webkit.org/blog/5610/more-responsive-tapping-on-ios/
user-select: none; // Prevent text selection on buttons on mobile devices
white-space: normal;
width: 100%;
word-wrap: break-word; /* For IE 10 and IE 11 */

&[data-focus-styles-enabled='true']:focus {
outline: var(--terra-application-navigation-drawer-menu-action-button-focus-outline, 2px dashed #fff);
outline-offset: var(--terra-application-navigation-drawer-menu-action-button-focus-outline-offset, 1px);
}

&:hover {
background-color: var(--terra-application-navigation-drawer-menu-action-button-hover-background-color, #005685);
color: var(--terra-application-navigation-drawer-menu-action-button-hover-color, #dedfe0);
}

&:active {
background-color: var(--terra-application-navigation-drawer-menu-action-button-active-background-color, #0065a3);
color: var(--terra-application-navigation-drawer-menu-action-button-active-color, #dedfe0);
}
}
}

.large-user-layout {
Expand Down Expand Up @@ -68,7 +102,11 @@
white-space: normal;
word-break: break-word;
word-wrap: break-word;
}
}

.drawer-menu-action-button {
margin-top: 12px;
}
}

.small-user-layout {
Expand Down Expand Up @@ -121,7 +159,11 @@
white-space: normal;
word-break: break-word;
word-wrap: break-word;
}
}

.drawer-menu-action-button {
margin-top: 6px;
}
}
}
/* stylelint-enable selector-max-compound-selectors */
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import DrawerMenuListItem from './_DrawerMenuListItem';
import DrawerMenuUser from './_DrawerMenuUser';
import DrawerMenuFooterButton from './_DrawerMenuFooterButton';
import {
titleConfigPropType, userConfigPropType, navigationItemsPropType, utilityItemsPropType,
titleConfigPropType, userConfigPropType, navigationItemsPropType, utilityItemsPropType, userActionConfigPropType,
} from '../utils/propTypes';
import {
navigationItemId, utilityItemId, settingsUtilityItemId, helpUtilityItemId, logoutUtilityItemId,
navigationItemId, utilityItemId, settingsUtilityItemId, helpUtilityItemId, logoutUtilityItemId, userActionItemId,
} from '../utils/helpers';

import styles from './DrawerMenu.module.scss';
Expand Down Expand Up @@ -89,6 +89,10 @@ const propTypes = {
* Object containing intl APIs
*/
intl: PropTypes.shape({ formatMessage: PropTypes.func }),
/**
* A configuration object to render an action button for user Config.
*/
userActionConfig: userActionConfigPropType,
};

const defaultProps = {
Expand All @@ -111,9 +115,10 @@ const DrawerMenu = ({
onSelectUtilityItem,
notifications,
intl,
userActionConfig,
}) => {
const titleComponent = titleConfig && !(titleConfig.element || titleConfig.hideTitleWithinDrawerMenu) ? <DrawerMenuTitle titleConfig={titleConfig} /> : undefined;
const userComponent = userConfig ? <DrawerMenuUser userConfig={userConfig} variant={hero ? 'small' : 'large'} /> : undefined;
const userComponent = userConfig ? <DrawerMenuUser id={id && userActionItemId(id)} userActionConfig={userActionConfig} userConfig={userConfig} variant={hero ? 'small' : 'large'} /> : undefined;
const logoutButton = onSelectLogout ? (
<div className={cx('footer')}>
<DrawerMenuFooterButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@ import PropTypes from 'prop-types';
import classNames from 'classnames/bind';
import Avatar, { Generic } from 'terra-avatar';
import ThemeContext from 'terra-theme-context';
import { userConfigPropType } from '../utils/propTypes';
import { userConfigPropType, userActionConfigPropType } from '../utils/propTypes';
import { enableFocusStyles, disableFocusStyles } from '../utils/helpers';

import styles from './DrawerMenuUser.module.scss';

const cx = classNames.bind(styles);

const propTypes = {
/**
* An id for the user action button
*/
id: PropTypes.string,
/**
* A configuration object to render an action button for user Config.
*/
userActionConfig: userActionConfigPropType,
/**
* A configuration object with information pertaining to the application's user.
*/
Expand All @@ -24,7 +33,9 @@ const defaultProps = {
variant: 'small',
};

const DrawerMenuUser = ({ userConfig, variant }) => {
const DrawerMenuUser = ({
userConfig, variant, userActionConfig, id,
}) => {
const theme = React.useContext(ThemeContext);

return (
Expand All @@ -42,6 +53,19 @@ const DrawerMenuUser = ({ userConfig, variant }) => {
<div className={cx('info-container')}>
<div aria-hidden className={cx('name')}>{userConfig.name}</div>
{userConfig.detail ? <div className={cx('detail')}>{userConfig.detail}</div> : null}
{ userActionConfig && (
<button
id={id || undefined}
className={cx('drawer-menu-action-button', theme.className)}
type="button"
onClick={userActionConfig.userActionCallback}
onBlur={enableFocusStyles}
onMouseDown={disableFocusStyles}
data-focus-styles-enabled
>
{userActionConfig.text}
</button>
)}
</div>
</div>
);
Expand Down
Loading