Skip to content

Commit

Permalink
[MWPW-128550] Profile block fixes (adobecom#581)
Browse files Browse the repository at this point in the history
* [MWPW-128550] Profile block fixes

* [MWPW-128550] Profile block fixes PR feedback implementation

---------

Co-authored-by: Rares Munteanu <ramuntea@adobe.com>
  • Loading branch information
overmyheadandbody and Rares Munteanu committed Mar 28, 2023
1 parent 2d811ff commit 4b6a7a5
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ function decorateAppsMenu(profileEl, appsDom, toggle) {
}

async function appLauncher(profileEl, appLauncherBlock, toggle) {
const gnav = profileEl.closest('nav.gnav');
gnav.classList.add('has-apps');

const appsLink = appLauncherBlock.querySelector('a');
appsLink.href = localizeLink(appsLink.href);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
left: 0;
padding: 0;
z-index: 1;
box-shadow: 0 3px 3px 0 rgb(0 0 0 / 20%);
}

[dir = "rtl"] .feds-popup {
Expand Down
26 changes: 26 additions & 0 deletions libs/blocks/global-navigation/blocks/profile/button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { toFragment, getFedsPlaceholderConfig } from '../../utilities/utilities.js';
import { replaceKey } from '../../../../features/placeholders.js';

const decorateButton = async ({ avatar }) => {
const label = await replaceKey(
'profile-button',
getFedsPlaceholderConfig(),
);

const buttonElem = toFragment`
<button
class="feds-profile-button"
aria-expanded="false"
aria-controls="feds-profile-menu"
aria-label="${label}"
daa-ll="Account"
aria-haspopup="true"
>
<img class="feds-profile-img" src="${avatar}"></img>
</button>
`;

return buttonElem;
};

export default decorateButton;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
:root {
--feds-color-profile-heading: #707070;
--feds-color-profile: #4b4b4b;
--feds-color-profile-emphasis: #2c2c2c;
--feds-color-profile--emphasis: #2c2c2c;
--feds-border-profile: 1px solid #e1e1e1;
}

Expand All @@ -18,32 +18,18 @@
display: none;
line-height: 1;
white-space: nowrap;
z-index: 1;
}

[dir='rtl'] .feds-profile-menu {
[dir = "rtl"] .feds-profile-menu {
right: initial;
left: 0;
}

.feds-profile.is-open .feds-profile-menu {
.feds-profile-button[aria-expanded = "true"] + .feds-profile-menu {
display: block;
}

.feds-profile li a {
color: inherit;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}

/* TODO be removable with refactoring the menu CSS */
.feds-profile.gnav-navitem.has-menu.is-open .gnav-navitem-menu {
position: absolute;
top: 100%;
left: auto;
margin-top: 0;
}

.feds-profile-header {
padding: 20px;
display: flex;
Expand All @@ -61,22 +47,23 @@
overflow: hidden;
}

.feds-profile-name,
.feds-profile-email {
text-overflow: ellipsis;
overflow: hidden;
}

.feds-profile-name {
margin: 0;
padding: 0 0 4px;
margin: 0 0 4px;
font-size: 18px;
font-weight: 700;
text-overflow: ellipsis;
overflow: hidden;
color: var(--feds-color-profile-emphasis);
color: var(--feds-color-profile--emphasis);
}

.feds-profile-email {
margin: 0 0 12px;
font-size: 14px;
color: var(--feds-color-profile-heading);
text-overflow: ellipsis;
overflow: hidden;
}

.feds-profile-account {
Expand All @@ -91,24 +78,35 @@
padding: 6px 0;
}

.feds-local-menu ul {
margin: 0;
padding: 0;
}

.feds-local-menu h5 {
margin: 0;
padding: 5px 20px;
padding: 8px 20px;
color: var(--feds-color-profile-heading);
font-size: 11px;
font-weight: 600;
line-height: 1.5;
text-transform: uppercase;
}

.feds-local-menu p {
margin: 0;
}

.feds-local-menu a,
.feds-profile-actions a {
display: block;
color: var(--feds-color-link--light);
}

.feds-local-menu a:hover,
.feds-profile-actions a:hover {
color: var(--feds-color-link--hover--light);
background-color: var(--feds-background-link--hover--light);
}

.feds-local-menu a {
font-size: 14px;
padding: 9px 18px;
color: var(--feds-color-profile);
padding: 6px 20px;
line-height: 1.4;
outline-offset: -1px;
}

Expand All @@ -121,8 +119,3 @@
padding: 14px 20px;
border-top: var(--feds-border-profile);
}

.feds-profile-menu li:hover {
color: var(--feds-color-profile-emphasis);
background-color: #f5f5f5;
}
188 changes: 188 additions & 0 deletions libs/blocks/global-navigation/blocks/profile/dropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { getConfig } from '../../../../utils/utils.js';
import { toFragment, getFedsPlaceholderConfig } from '../../utilities/utilities.js';
import { replaceKeyArray } from '../../../../features/placeholders.js';

const getLanguage = (ietfLocale) => {
if (!ietfLocale.length) return 'en';

const nonStandardLocaleMap = { 'no-NO': 'nb' };

if (nonStandardLocaleMap[ietfLocale]) {
return nonStandardLocaleMap[ietfLocale];
}

return ietfLocale.split('-')[0];
};

const decorateProfileLink = (service, path = '') => {
const defaultServiceUrls = {
adminconsole: 'https://adminconsole.adobe.com',
account: 'https://account.adobe.com',
};

if (!service.length || !defaultServiceUrls[service]) return '';

let serviceUrl;
const { env } = getConfig();

if (!env?.[service]) {
serviceUrl = defaultServiceUrls[service];
} else {
serviceUrl = new URL(defaultServiceUrls[service]);
serviceUrl.hostname = env[service];
}

return `${serviceUrl}${path}`;
};

const decorateAction = (label, path) => toFragment`<li><a class="feds-profile-action" href="${decorateProfileLink('adminconsole', path)}">${label}</a></li>`;

class ProfileDropdown {
constructor({
rawElem,
decoratedElem,
avatar,
sections,
buttonElem,
openOnInit,
} = {}) {
this.placeholders = {};
this.profileData = {};
this.avatar = avatar;
this.buttonElem = buttonElem;
this.decoratedElem = decoratedElem;
this.sections = sections;
this.openOnInit = openOnInit;
this.localMenu = rawElem.querySelector('h5')?.parentElement;
this.init();
}

async init() {
await this.getData();
this.setButtonLabel();
this.dropdown = this.decorateDropdown();
this.addEventListeners();

if (this.openOnInit) this.toggleDropdown();

this.decoratedElem.append(this.dropdown);
}

async getData() {
[
[
this.placeholders.profileButton,
this.placeholders.signOut,
this.placeholders.viewAccount,
this.placeholders.manageTeams,
this.placeholders.manageEnterprise,
],
// TODO: sanity checks if the user is logged in and mandatory properties are set.
// If not, add logs providing guidance for developers
{ displayName: this.profileData.displayName, email: this.profileData.email },
] = await Promise.all([
replaceKeyArray(
['profile-button', 'sign-out', 'view-account', 'manage-teams', 'manage-enterprise'],
getFedsPlaceholderConfig(),
),
window.adobeIMS.getProfile(),
]);
}

setButtonLabel() {
if (this.buttonElem) this.buttonElem.setAttribute('aria-label', this.profileData.displayName);
}

decorateDropdown() {
const { locale } = getConfig();
const lang = getLanguage(locale.ietf);

// TODO: the account name and email might need a bit of adaptive behavior;
// historically we shrunk the font size and displayed the account name on two lines;
// the email had some special logic as well;
// for MVP, we took a simpler approach ("Some very long name, very l...")
// TODO: historically, clicking the avatar lead to '/profile',
// but clicking the 'View account link' let to the account page;
// we need to check whether this is still needed
return toFragment`
<div id="feds-profile-menu" class="feds-profile-menu">
<a
href="${decorateProfileLink('account', `?lang=${lang}`)}"
class="feds-profile-header"
daa-ll="${this.placeholders.viewAccount}"
aria-label="${this.placeholders.viewAccount}"
>
<img class="feds-profile-img" src="${this.avatar}"></img>
<div class="feds-profile-details">
<p class="feds-profile-name">${this.profileData.displayName}</p>
<p class="feds-profile-email">${this.decorateEmail(this.profileData.email)}</p>
<p class="feds-profile-account">${this.placeholders.viewAccount}</p>
</div>
</a>
${this.localMenu ? this.decorateLocalMenu() : ''}
<ul class="feds-profile-actions">
${this.sections?.manage?.items?.team?.id ? decorateAction(this.placeholders.manageTeams, '/team') : ''}
${this.sections?.manage?.items?.enterprise?.id ? decorateAction(this.placeholders.manageEnterprise) : ''}
${this.decorateSignOut()}
</ul>
</div>
`;
}

decorateEmail() {
const maxCharacters = 12;
const emailParts = this.profileData.email.split('@');
const username = emailParts[0].length <= maxCharacters
? emailParts[0]
: `${emailParts[0].slice(0, maxCharacters)}…`;
const domainArr = emailParts[1].split('.');
const tld = domainArr.pop();
let domain = domainArr.join('.');
domain = domain.length <= maxCharacters
? domain
: `${domain.slice(0, maxCharacters)}…`;

return `${username}@${domain}.${tld}`;
}

decorateLocalMenu() {
if (this.localMenu) this.localMenu.classList.add('feds-local-menu');

return this.localMenu;
}

decorateSignOut() {
const signOutLink = toFragment`
<li>
<a href="#" class="feds-profile-action" daa-ll="${this.placeholders.signOut}">${this.placeholders.signOut}</a>
</li>
`;

// TODO consumers might want to execute their own logic before a sign out
// we might want to provide them a way to do so here
signOutLink.addEventListener('click', (e) => {
e.preventDefault();
window.adobeIMS.signOut();
});

return signOutLink;
}

addEventListeners() {
this.buttonElem.addEventListener('click', () => {
this.toggleDropdown();
});
}

toggleDropdown() {
const isOpen = this.buttonElem.getAttribute('aria-expanded') === 'true';

if (isOpen) {
this.buttonElem.setAttribute('aria-expanded', 'false');
} else {
this.buttonElem.setAttribute('aria-expanded', 'true');
}
}
}

export default ProfileDropdown;
Loading

0 comments on commit 4b6a7a5

Please sign in to comment.