Updating header with new Crayons buttons (#15653)
* init * whoops * components update * components update * classes * variables * focus * test * spec * test * adjust colors and variables * buttons colors * Update app/javascript/crayons/CTAs/CTA.jsx Co-authored-by: Suzanne Aitchison <suzanne@forem.com> * proptype * remove duplicated tests now in Cypress, enhance Cypress nav menu tests * premove unneeded viewport in test * remove focus check for now * classnames * Update app/javascript/crayons/CTAs/__stories__/CTAs.mdx Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Update app/javascript/crayons/CTAs/__stories__/CTAs.mdx Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * better contrast * classname * radius-full Co-authored-by: Suzanne Aitchison <suzanne@forem.com> Co-authored-by: Nick Taylor <nick@iamdeveloper.com>
This commit is contained in:
parent
ac5934e63f
commit
11cabac518
25 changed files with 333 additions and 481 deletions
|
|
@ -1,26 +0,0 @@
|
|||
function initializeSpecialNavigationFunctionality() {
|
||||
var connectLink = document.getElementById('connect-link');
|
||||
var notificationsLink = document.getElementById('notifications-link');
|
||||
var moderationLink = document.getElementById('moderation-link');
|
||||
|
||||
if (connectLink) {
|
||||
if (document.getElementById('notifications-container')) {
|
||||
notificationsLink.blur();
|
||||
notificationsLink.classList.add('crayons-header__link--current');
|
||||
} else {
|
||||
notificationsLink.classList.remove('crayons-header__link--current');
|
||||
}
|
||||
if (document.getElementById('chat')) {
|
||||
connectLink.blur();
|
||||
connectLink.classList.add('crayons-header__link--current');
|
||||
} else {
|
||||
connectLink.classList.remove('crayons-header__link--current');
|
||||
}
|
||||
if (document.getElementById('moderation-page')) {
|
||||
moderationLink.blur();
|
||||
moderationLink.classList.add('crayons-header__link--current');
|
||||
} else {
|
||||
moderationLink.classList.remove('crayons-header__link--current');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -427,7 +427,6 @@ label[class*='crayons-btn']:focus-within // label for input[type="file"] made to
|
|||
font: inherit;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-props);
|
||||
border: none;
|
||||
overflow-wrap: normal;
|
||||
outline: 0;
|
||||
|
|
@ -451,6 +450,7 @@ label[class*='crayons-btn']:focus-within // label for input[type="file"] made to
|
|||
color: var(--color-hover);
|
||||
z-index: var(--z-elevate);
|
||||
box-shadow: var(--focus-ring);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.js-focus-visible &.focus-visible:focus {
|
||||
|
|
@ -458,6 +458,14 @@ label[class*='crayons-btn']:focus-within // label for input[type="file"] made to
|
|||
color: var(--color-hover);
|
||||
z-index: var(--z-elevate);
|
||||
box-shadow: var(--focus-ring);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&[aria-expanded='true'],
|
||||
&[aria-pressed='true'] {
|
||||
--bg: var(--btn-current-bg);
|
||||
--color: var(--btn-current-color);
|
||||
font-weight: var(--fw-bold);
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
|
|
|
|||
|
|
@ -4,37 +4,79 @@
|
|||
// .c-cta //////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
.c-link {
|
||||
&.c-cta {
|
||||
--border-width: 1px;
|
||||
padding: calc(var(--su-2) - var(--border-width))
|
||||
calc(var(--su-4) - var(--border-width));
|
||||
border: var(--border-width) solid var(--border);
|
||||
text-decoration: none;
|
||||
background-color: var(--bg);
|
||||
color: var(--color);
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
.c-cta {
|
||||
--border-width: 1px;
|
||||
padding: calc(var(--su-2) - var(--border-width))
|
||||
calc(var(--su-4) - var(--border-width));
|
||||
border: var(--border-width) solid;
|
||||
border-color: var(--border);
|
||||
text-decoration: none;
|
||||
border-radius: var(--radius);
|
||||
font: inherit;
|
||||
background-color: var(--bg);
|
||||
color: var(--color);
|
||||
outline: 0;
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
overflow-wrap: normal;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-hover);
|
||||
border-color: var(--border-hover);
|
||||
color: var(--color-hover);
|
||||
z-index: var(--z-elevate);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.c-cta {
|
||||
--bg: transparent;
|
||||
--bg-hover: var(--base-20);
|
||||
--color: var(--base-80);
|
||||
--color-hover: var(--base-100);
|
||||
--border: var(--base-60);
|
||||
--border-hover: var(--base-80);
|
||||
// Both focus declarations below are the same but we unfortunately can't
|
||||
// combine them because Safari doesn't recognize it properly.
|
||||
// • First declaration is for all browsers that support :focus-visible
|
||||
// which is basically almost all of them
|
||||
// • Second declaration is for browsers that do not support :focus-visible
|
||||
// which basically is only Safari right now.
|
||||
&:focus-visible {
|
||||
background-color: var(--bg-hover);
|
||||
border-color: var(--border-hover);
|
||||
color: var(--color-hover);
|
||||
z-index: var(--z-elevate);
|
||||
box-shadow: var(--focus-ring);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.c-cta--branded {
|
||||
--bg: transparent;
|
||||
--bg-hover: var(--accent-brand-a10);
|
||||
--color: var(--accent-brand);
|
||||
--color-hover: var(--accent-brand-darker);
|
||||
--border: var(--accent-brand);
|
||||
--border-hover: var(--accent-brand-darker);
|
||||
.js-focus-visible &.focus-visible:focus {
|
||||
background-color: var(--bg-hover);
|
||||
border-color: var(--border-hover);
|
||||
color: var(--color-hover);
|
||||
z-index: var(--z-elevate);
|
||||
box-shadow: var(--focus-ring);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.c-cta {
|
||||
--bg: var(--cta-bg);
|
||||
--bg-hover: var(--cta-bg-hover);
|
||||
--color: var(--cta-color);
|
||||
--color-hover: var(--cta-color-hover);
|
||||
--border: var(--cta-border);
|
||||
--border-hover: var(--cta-border-hover);
|
||||
|
||||
&--branded {
|
||||
--bg: var(--cta-branded-bg);
|
||||
--bg-hover: var(--cta-branded-bg-hover);
|
||||
--color: var(--cta-branded-color);
|
||||
--color-hover: var(--cta-branded-color-hover);
|
||||
--border: var(--cta-branded-border);
|
||||
--border-hover: var(--cta-branded-border-hover);
|
||||
|
||||
font-weight: var(--fw-medium);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.c-cta--icon-left {
|
||||
.c-cta__icon {
|
||||
margin-right: var(--su-2);
|
||||
margin-left: calc(var(--su-1) * -1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
&-focus {
|
||||
background-color: var(--form-bg-focus);
|
||||
border-color: var(--form-border-focus);
|
||||
box-shadow: 0 0 0 1px var(--form-border-focus);
|
||||
border-color: var(--focus);
|
||||
box-shadow: 0 0 0 1px var(--focus);
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
@import './skip-link';
|
||||
|
||||
.crayons-header {
|
||||
--indicator-outline: var(--header-bg);
|
||||
|
||||
height: var(--header-height);
|
||||
background: var(--header-bg);
|
||||
box-shadow: 0 1px 1px var(--header-shadow);
|
||||
|
|
@ -30,7 +28,7 @@
|
|||
position: absolute;
|
||||
top: calc(var(--su-1) * -1);
|
||||
right: 0;
|
||||
box-shadow: 0 0 0 2px var(--indicator-outline);
|
||||
box-shadow: 0 0 0 2px var(--header-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,64 +43,18 @@
|
|||
margin: 0 var(--su-4);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.crayons-btn:focus {
|
||||
outline: revert;
|
||||
}
|
||||
}
|
||||
|
||||
.crayons-header__link {
|
||||
margin: 0 var(--su-1);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
--indicator-outline: var(--header-bg-hover);
|
||||
|
||||
color: var(--header-icons-color-hover);
|
||||
background: var(--header-bg-hover);
|
||||
}
|
||||
|
||||
&--current {
|
||||
--indicator-outline: var(--header-bg-current);
|
||||
|
||||
color: var(--header-icons-color-hover);
|
||||
background: var(--header-bg-current) !important;
|
||||
|
||||
.crayons-indicator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown under profile picture / hamburger icon
|
||||
.crayons-header__menu {
|
||||
--trigger-color: var(--header-icons-color);
|
||||
--trigger-border-color: transparent;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@media (min-width: $breakpoint-s) {
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
&__trigger {
|
||||
all: unset;
|
||||
padding: calc(var(--su-1) / 2);
|
||||
margin: 0 var(--su-2);
|
||||
border-radius: 50%;
|
||||
color: var(--trigger-color);
|
||||
cursor: pointer;
|
||||
// hold space for interaction style
|
||||
border: 2px solid var(--trigger-border-color);
|
||||
}
|
||||
|
||||
&__dropdown {
|
||||
transform-style: preserve-3d;
|
||||
top: var(--header-height);
|
||||
transform-origin: top right;
|
||||
transition: all var(--transition-props);
|
||||
transition-delay: var(--dropdown-delay, 0.25s);
|
||||
transform: var(
|
||||
--dropdown-transform,
|
||||
translateY(calc(1 * var(--su-1))) rotateX(-10deg)
|
||||
|
|
@ -111,20 +63,11 @@
|
|||
visibility: var(--dropdown-visibility, hidden);
|
||||
}
|
||||
|
||||
&.showing,
|
||||
&.desktop:hover {
|
||||
--trigger-border-color: var(--header-bg-hover);
|
||||
--trigger-color: var(--header-icons-color-hover);
|
||||
&.showing {
|
||||
--dropdown-transform: translateY(0) rotateX(0);
|
||||
--dropdown-delay: 0;
|
||||
--dropdown-opacity: 1;
|
||||
--dropdown-visibility: visible;
|
||||
}
|
||||
|
||||
.focus-visible:focus {
|
||||
--trigger-border-color: var(--header-button-focus-color);
|
||||
--trigger-color: var(--header-button-focus-color);
|
||||
}
|
||||
}
|
||||
|
||||
.pwa-nav-buttons {
|
||||
|
|
@ -147,52 +90,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.authentication-header {
|
||||
&__container {
|
||||
padding: var(--su-6);
|
||||
}
|
||||
|
||||
&__image-container {
|
||||
width: var(--su-8);
|
||||
height: var(--su-8);
|
||||
margin-bottom: var(--su-2);
|
||||
}
|
||||
|
||||
&__image {
|
||||
border-radius: var(--radius);
|
||||
transform: rotate(-10deg);
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--card-color);
|
||||
font-size: var(--fs-2xl);
|
||||
line-height: var(--lh-tight);
|
||||
|
||||
@media (min-width: $breakpoint-s) {
|
||||
font-size: var(--fs-3xl);
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: var(--card-color-secondary);
|
||||
margin-bottom: var(--su-4);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: grid;
|
||||
grid-gap: var(--su-1);
|
||||
|
||||
@media (min-width: $breakpoint-s) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: max-content;
|
||||
justify-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-progress {
|
||||
--duration: 3600ms;
|
||||
--delay: 250ms;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
border-radius: var(--radius);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-props);
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
color: var(--color);
|
||||
|
|
@ -31,7 +30,7 @@
|
|||
z-index: var(--z-elevate);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
// Both focus declarations below are the same but we unfortunately can't
|
||||
// combine them because Safari doesn't recognize it properly.
|
||||
// • First declaration is for all browsers that support :focus-visible
|
||||
|
|
@ -61,20 +60,26 @@
|
|||
background-color: var(--bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-current='page'] {
|
||||
--bg: var(--link-current-bg);
|
||||
--color: var(--link-current-color);
|
||||
font-weight: var(--fw-bold);
|
||||
}
|
||||
}
|
||||
|
||||
.c-link {
|
||||
--bg: transparent;
|
||||
--bg-hover: var(--base-20);
|
||||
--color: var(--base-80);
|
||||
--color-hover: var(--accent-brand-darker);
|
||||
--bg: var(--link-bg);
|
||||
--bg-hover: var(--link-bg-hover);
|
||||
--color: var(--link-color);
|
||||
--color-hover: var(--link-branded-color-hover);
|
||||
}
|
||||
|
||||
.c-link--branded {
|
||||
--bg: transparent;
|
||||
--bg-hover: var(--accent-brand-a10);
|
||||
--color: var(--accent-brand);
|
||||
--color-hover: var(--accent-brand-darker);
|
||||
--bg: var(--link-branded-bg);
|
||||
--bg-hover: var(--link-branded-bg-hover);
|
||||
--color: var(--link-branded-color);
|
||||
--color-hover: var(--link-branded-color-hover);
|
||||
}
|
||||
|
||||
.c-link--icon-alone {
|
||||
|
|
|
|||
|
|
@ -116,9 +116,6 @@
|
|||
--footer-link-color-hover: var(--base-100);
|
||||
|
||||
// Links
|
||||
--link-color: var(--base-90);
|
||||
--link-color-hover: var(--accent-brand-darker);
|
||||
--link-bg-hover: var(--base-a5);
|
||||
--link-bg-hover-alt: var(--base-inverted);
|
||||
--link-brand-color: var(--accent-brand);
|
||||
--link-color-current: var(--base-100);
|
||||
|
|
@ -253,9 +250,12 @@
|
|||
|
||||
// Buttons: Default
|
||||
--btn-bg: transparent;
|
||||
--btn-bg-hover: var(--base-20);
|
||||
--btn-bg-hover: var(--accent-brand-a10);
|
||||
--btn-color: var(--base-80);
|
||||
--btn-color-hover: var(--base-100);
|
||||
--btn-color-hover: var(--accent-brand-darker);
|
||||
|
||||
--btn-current-bg: var(--base-20);
|
||||
--btn-current-color: var(--base-100);
|
||||
|
||||
// Buttons: Default Destructive
|
||||
--btn-destructive-bg: transparent;
|
||||
|
|
@ -274,4 +274,49 @@
|
|||
--btn-primary-destructive-bg-hover: var(--accent-danger-darker);
|
||||
--btn-primary-destructive-color: var(--base-inverted);
|
||||
--btn-primary-destructive-color-hover: var(--base-inverted);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Links ///////////////////////////////////////
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Links: Default
|
||||
--link-bg: transparent;
|
||||
--link-bg-hover: var(--accent-brand-a10);
|
||||
--link-color: var(--base-80);
|
||||
--link-color-hover: var(--accent-brand-darker);
|
||||
|
||||
--link-current-bg: var(--base-20);
|
||||
--link-current-color: var(--base-100);
|
||||
|
||||
// Links: Branded
|
||||
--link-branded-bg: transparent;
|
||||
--link-branded-bg-hover: var(--accent-brand-a10);
|
||||
--link-branded-color: var(--accent-brand);
|
||||
--link-branded-color-hover: var(--accent-brand-darker);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// CTAs ////////////////////////////////////////
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// CTAs: Default
|
||||
--cta-bg: transparent;
|
||||
--cta-bg-hover: var(--accent-brand-a10);
|
||||
--cta-color: var(--base-80);
|
||||
--cta-color-hover: var(--accent-brand-darker);
|
||||
--cta-border: var(--base-60);
|
||||
--cta-border-hover: var(--accent-brand-darker);
|
||||
|
||||
// CTAs: Branded
|
||||
--cta-branded-bg: transparent;
|
||||
--cta-branded-bg-hover: var(--accent-brand);
|
||||
--cta-branded-color: var(--accent-brand);
|
||||
--cta-branded-color-hover: var(--base-inverted);
|
||||
--cta-branded-border: var(--accent-brand);
|
||||
--cta-branded-border-hover: var(--accent-brand-darker);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Utilities ///////////////////////////////////
|
||||
////////////////////////////////////////////////
|
||||
|
||||
--focus: var(--accent-brand);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,6 +184,13 @@
|
|||
(),
|
||||
false
|
||||
),
|
||||
(
|
||||
'inset',
|
||||
('top', 'left', 'right', 'bottom'),
|
||||
('px': 1px, 'unset': unset, '0': 0),
|
||||
(),
|
||||
false
|
||||
),
|
||||
(
|
||||
'top',
|
||||
'top',
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@
|
|||
// Focus ring //////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
--focus-ring: 0 0 0 2px var(--base-inverted),
|
||||
0 0 0 4px var(--button-primary-bg-hover); // TODO: replace with appropriate variable from _colors.scss
|
||||
--focus-ring: 0 0 0 2px var(--base-inverted), 0 0 0 4px var(--focus);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Typography. /////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -109,9 +109,6 @@
|
|||
--footer-link-color-hover: var(--base-100);
|
||||
|
||||
// Links
|
||||
--link-color: var(--base-90);
|
||||
--link-color-hover: var(--accent-brand-lighter);
|
||||
--link-bg-hover: var(--base-a5);
|
||||
--link-bg-hover-alt: var(--base-inverted);
|
||||
--link-brand-color: #17a1f6;
|
||||
--link-color-current: var(--base-100);
|
||||
|
|
@ -240,9 +237,40 @@
|
|||
// Buttons /////////////////////////////////////
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Buttons: Default
|
||||
--btn-color-hover: var(--accent-brand-lighter);
|
||||
|
||||
// Buttons: Primary
|
||||
--btn-primary-bg-hover: var(--accent-brand-lighter);
|
||||
|
||||
// Buttons: Primary Destructive
|
||||
--btn-primary-destructive-bg-hover: var(--accent-danger-lighter);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Links ///////////////////////////////////////
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Links: Default
|
||||
--link-color-hover: var(--accent-brand-lighter);
|
||||
|
||||
// Links: Branded
|
||||
--link-branded-color-hover: var(--accent-brand-lighter);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// CTAs ////////////////////////////////////////
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// CTAs: Default
|
||||
--cta-color-hover: var(--accent-brand-lighter);
|
||||
--cta-border-hover: var(--accent-brand-lighter);
|
||||
|
||||
// CTAs: Branded
|
||||
--cta-branded-bg-hover: var(--accent-brand);
|
||||
--cta-branded-border: var(--accent-brand);
|
||||
--cta-branded-border-hover: var(--accent-brand-lighter);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Utilities ///////////////////////////////////
|
||||
////////////////////////////////////////////////
|
||||
--focus: var(--accent-brand-lighter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,8 @@ import PropTypes from 'prop-types';
|
|||
import { h } from 'preact';
|
||||
import { forwardRef } from 'preact/compat';
|
||||
import { locale } from '../utilities/locale';
|
||||
import { Button } from '@crayons';
|
||||
|
||||
|
||||
const SearchIcon = () => (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
className="crayons-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0111 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 01-1.969 5.617zm-2.006-.742A6.977 6.977 0 0018 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 004.875-1.975l.15-.15z" />
|
||||
</svg>
|
||||
);
|
||||
import { ButtonNew as Button } from '@crayons';
|
||||
import SearchIcon from '@images/search.svg';
|
||||
|
||||
export const SearchForm = forwardRef(({ searchTerm, onSubmitSearch }, ref) => (
|
||||
<form
|
||||
|
|
@ -40,11 +28,8 @@ export const SearchForm = forwardRef(({ searchTerm, onSubmitSearch }, ref) => (
|
|||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="ghost"
|
||||
contentType="icon-rounded"
|
||||
icon={SearchIcon}
|
||||
size="s"
|
||||
className="absolute right-2 bottom-0 top-0 m-1"
|
||||
className="absolute inset-px left-auto mt-0 py-0"
|
||||
aria-label="Search"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export const ButtonNew = (props) => {
|
|||
aria-hidden="true"
|
||||
focusable="false"
|
||||
src={icon}
|
||||
className={classNames('c-btn__icon')}
|
||||
className="c-btn__icon"
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
|
|
@ -80,5 +80,5 @@ ButtonNew.propTypes = {
|
|||
className: PropTypes.string,
|
||||
tooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
onKeyUp: PropTypes.func,
|
||||
icon: PropTypes.ReactNode,
|
||||
icon: PropTypes.elementType,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,22 +1,47 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames/bind';
|
||||
import { Link } from '@crayons';
|
||||
import { defaultChildrenPropTypes } from '../../common-prop-types/default-children-prop-types';
|
||||
import { Icon } from '@crayons';
|
||||
|
||||
export const CTA = (props) => {
|
||||
const { variant = 'default', className, ...otherProps } = props;
|
||||
const {
|
||||
children,
|
||||
href = '#',
|
||||
variant,
|
||||
icon,
|
||||
className,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const classes = classNames('c-cta', {
|
||||
[`c-cta--${variant}`]: variant,
|
||||
'c-cta--icon-left': icon && children,
|
||||
[className]: className,
|
||||
});
|
||||
|
||||
return <Link block className={classes} {...otherProps} />;
|
||||
return (
|
||||
<a href={href} className={classes} {...otherProps}>
|
||||
{icon && (
|
||||
<Icon
|
||||
src={icon}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className="c-cta__icon"
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
CTA.displayName = 'CTA';
|
||||
|
||||
CTA.propTypes = {
|
||||
variant: PropTypes.oneOf(['default', 'branded']),
|
||||
variant: PropTypes.oneOf([undefined, 'branded']),
|
||||
rounded: PropTypes.bool,
|
||||
href: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
children: defaultChildrenPropTypes.isRequired,
|
||||
icon: PropTypes.elementType,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# CTAs (Call To Actions)
|
||||
|
||||
Call To Action is an extension for `<Link block />` component therefore it inherits Link's props and configuration.
|
||||
CTA is meant to be more prominent and visually heavy so use these cautiously.
|
||||
CTA is meant to be more prominent and visually heavy than regular links and should deliver clear... well... *call to action* :).
|
||||
|
||||
## CTAs vs Links vs Buttons
|
||||
|
||||
|
|
@ -14,10 +13,9 @@ Do not use `<Button />s` as navigational elements. Instead, use Links (or CTAs)
|
|||
|
||||
## Usage
|
||||
|
||||
Under the hood, CTA outputs `<a>` element.
|
||||
A CTA renders an anchor element, therefore standard link markup must be used,
|
||||
including a `href` attribute, otherwise, it is not focusable by the keyboard.
|
||||
|
||||
Call To Actions has slightly different UI than regular `<Link block />` as our intention here was to make these more prominent.
|
||||
`<CTA />` is basically a `<Link />` therefore standard link markup should be used, including an `href` (links omitting `href` attributes are not focusable by keyboard).
|
||||
|
||||
### Icons
|
||||
|
||||
|
|
@ -27,13 +25,8 @@ By default, the icon itself won't be customizable. Icons within CTAs are hidden
|
|||
|
||||
#### Icon-only
|
||||
|
||||
There are certain instances where an icon will suffice in place of a text label, but use these cautiously.
|
||||
Icon-only CTAs should always use clear and obvious icon, ideally accompanied by a title. Icon-only CTAs will require an `aria-label` to ensure they are understood by users of assistive technologies.
|
||||
|
||||
##### Fully rounded
|
||||
|
||||
In most cases, whenever we display icon-only CTAs we tend to make them fully rounded - use `rounded` prop for that.
|
||||
Other than that please avoid customizing border-radius on CTAs.
|
||||
A call to action requires a label, therefore it is forbidden to create a CTA with an icon only.
|
||||
For such a scenario, use a `<Link />` or `<Button />` component (depending on the use case).
|
||||
|
||||
## Design
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ export default {
|
|||
variant: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: ['default', 'branded'],
|
||||
options: {
|
||||
default: undefined,
|
||||
branded: 'branded',
|
||||
},
|
||||
},
|
||||
description:
|
||||
'There are two available variants (styles) to pick from: _default_ and _branded_. The primary difference is color: _default_ uses grey color and _branded_ uses accent color.',
|
||||
|
|
@ -23,21 +26,12 @@ export default {
|
|||
defaultValue: { summary: 'default' },
|
||||
},
|
||||
},
|
||||
rounded: {
|
||||
description:
|
||||
'By enabling this setting you can turn all corners of the CTA to be fully rounded. We usually use it **only** for CTAs containing **icon without label**.',
|
||||
table: {
|
||||
defaultValue: { summary: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = (args) => <CTA {...args} />;
|
||||
Default.args = {
|
||||
children: 'Call to action',
|
||||
variant: 'default',
|
||||
rounded: false,
|
||||
};
|
||||
|
||||
export const Branded = (args) => <CTA {...args} />;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<CTA /> renders branded CTA 1`] = `"<a href=\\"#\\" class=\\"c-link c-link--block c-cta c-cta--branded\\">Hello world!</a>"`;
|
||||
exports[`<CTA /> renders branded CTA 1`] = `"<a href=\\"#\\" class=\\"c-cta c-cta--branded\\">Hello world!</a>"`;
|
||||
|
||||
exports[`<CTA /> renders default CTA 1`] = `"<a href=\\"#\\" class=\\"c-link c-link--block c-cta c-cta--default\\">Hello world!</a>"`;
|
||||
exports[`<CTA /> renders default CTA 1`] = `"<a href=\\"#\\" class=\\"c-cta\\">Hello world!</a>"`;
|
||||
|
|
|
|||
|
|
@ -24,5 +24,5 @@ Icon.displayName = 'Icon';
|
|||
Icon.propTypes = {
|
||||
native: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
src: PropTypes.node.isRequired,
|
||||
src: PropTypes.elementType.isRequired,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export const Link = (props) => {
|
|||
src={icon}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className={classNames('c-link__icon')}
|
||||
className="c-link__icon"
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
|
|
@ -43,11 +43,11 @@ export const Link = (props) => {
|
|||
Link.displayName = 'Link';
|
||||
|
||||
Link.propTypes = {
|
||||
variant: PropTypes.oneOf(['default', 'branded']),
|
||||
variant: PropTypes.oneOf([undefined, 'branded']),
|
||||
block: PropTypes.bool,
|
||||
rounded: PropTypes.bool,
|
||||
href: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
children: defaultChildrenPropTypes,
|
||||
icon: PropTypes.ReactNode,
|
||||
icon: PropTypes.elementType,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ export default {
|
|||
variant: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: ['default', 'branded'],
|
||||
options: {
|
||||
default: undefined,
|
||||
branded: 'branded',
|
||||
},
|
||||
},
|
||||
description:
|
||||
'There are two available variants (styles) to pick from: _default_ and _branded_. The primary difference is color: _default_ uses grey color and _branded_ uses accent color.',
|
||||
|
|
@ -60,7 +63,7 @@ export const Block = (args) => <Link {...args} />;
|
|||
Block.args = {
|
||||
...Inline.args,
|
||||
block: true,
|
||||
variant: 'default',
|
||||
variant: undefined,
|
||||
children: 'Block link',
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ function getPageEntries() {
|
|||
return Object.entries({
|
||||
'notifications-index': document.getElementById('notifications-link'),
|
||||
'moderations-index': document.getElementById('moderation-link'),
|
||||
'stories-search': document.getElementById('search-link'),
|
||||
'articles_search-index': document.getElementById('search-link'),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,10 @@
|
|||
// TODO: Once this is merged, PR up the removal of this and in other tests and move it to testSetup.js
|
||||
import '@testing-library/jest-dom';
|
||||
import {
|
||||
getByTestId,
|
||||
getByLabelText,
|
||||
getByText,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from '@testing-library/dom';
|
||||
import { getByLabelText } from '@testing-library/dom';
|
||||
import {
|
||||
getInstantClick,
|
||||
initializeMobileMenu,
|
||||
setCurrentPageIconLink,
|
||||
initializeMemberMenu,
|
||||
} from '../utilities';
|
||||
|
||||
// TODO: ★★★ These tests should be promoted to E2E tests once we have that in place. ★★★
|
||||
|
|
@ -41,7 +34,7 @@ describe('top navigation utilities', () => {
|
|||
describe('initializeMobileMenu', () => {
|
||||
it('should open the hamburger menu', () => {
|
||||
document.body.innerHTML = `
|
||||
<button aria-label="nav-button-left" class="crayons-btn crayons-btn--ghost crayons-btn--icon-rounded js-hamburger-trigger inline-block m:hidden mx-2">
|
||||
<button aria-label="nav-button-left" class="c-btn c-btn--icon-alone radius-full js-hamburger-trigger mx-2">
|
||||
<svg><title>Navigation menu</title></svg>
|
||||
</button>
|
||||
`;
|
||||
|
|
@ -57,7 +50,7 @@ describe('top navigation utilities', () => {
|
|||
|
||||
it('should close the hamburger menu', () => {
|
||||
document.body.innerHTML = `
|
||||
<button aria-label="nav-button-left" class="crayons-btn crayons-btn--ghost crayons-btn--icon-rounded js-hamburger-trigger inline-block m:hidden mx-2">
|
||||
<button aria-label="nav-button-left" class="c-btn c-btn--icon-alone radius-full js-hamburger-trigger mx-2">
|
||||
<svg><title>Navigation menu</title></svg>
|
||||
</button>
|
||||
`;
|
||||
|
|
@ -72,176 +65,23 @@ describe('top navigation utilities', () => {
|
|||
navButton.click();
|
||||
expect(document.body.dataset.leftNavState).toEqual('closed');
|
||||
});
|
||||
|
||||
it('should open the more menu', () => {
|
||||
document.body.innerHTML = `
|
||||
<a href="javascript:void(0)" class="crayons-link crayons-link--secondary crayons-link--block crayons-link--block--indented fs-s js-nav-more-trigger">More...</a>
|
||||
<div class="hidden js-nav-more spec-nav-more">
|
||||
<div class="flex justify-around p-4 mt-4 border-solid border-0 border-t-1 border-base-10">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const navMoreLink = getByText(document.body, 'More...');
|
||||
|
||||
initializeMobileMenu([], [navMoreLink]);
|
||||
|
||||
navMoreLink.click();
|
||||
|
||||
expect(navMoreLink).toHaveClass('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCurrentPageIconLink', () => {
|
||||
it('should set the current page icon', () => {
|
||||
document.body.innerHTML = `
|
||||
<a href="/connect" id="connect-link" class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="Connect"></a>
|
||||
<a href="/notifications" id="notifications-link" class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="Notifications"></a>
|
||||
<a href="/notifications" id="notifications-link" class="c-link c-link--icon-alone c-link--block radius-full mx-1" aria-current="page" aria-label="Notifications"></a>
|
||||
`;
|
||||
|
||||
const connectLink = getByLabelText(document.body, 'Connect');
|
||||
const notificationsLink = getByLabelText(document.body, 'Notifications');
|
||||
|
||||
const pageEntries = Object.entries({
|
||||
'page-1': connectLink,
|
||||
'page-2': notificationsLink,
|
||||
'page-1': notificationsLink,
|
||||
});
|
||||
|
||||
const page = 'page-1';
|
||||
setCurrentPageIconLink('page-1', pageEntries);
|
||||
|
||||
setCurrentPageIconLink(page, pageEntries);
|
||||
|
||||
expect(connectLink).toHaveClass('crayons-header__link--current');
|
||||
expect(notificationsLink).not.toHaveClass(
|
||||
'crayons-header__link--current',
|
||||
);
|
||||
});
|
||||
|
||||
it('should set the current page icon and remove the previous current page icon styling', () => {
|
||||
document.body.innerHTML = `
|
||||
<a href="/connect" id="connect-link" class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="Connect"></a>
|
||||
<a href="/notifications" id="notifications-link" class="crayons-header__link crayons-header__link--current crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="Notifications"></a>
|
||||
`;
|
||||
|
||||
const connectLink = getByLabelText(document.body, 'Connect');
|
||||
const notificationsLink = getByLabelText(document.body, 'Notifications');
|
||||
|
||||
const pageEntries = Object.entries({
|
||||
'page-1': connectLink,
|
||||
'page-2': notificationsLink,
|
||||
});
|
||||
|
||||
const page = 'page-1';
|
||||
|
||||
setCurrentPageIconLink(page, pageEntries);
|
||||
|
||||
expect(connectLink).toHaveClass('crayons-header__link--current');
|
||||
expect(notificationsLink).not.toHaveClass(
|
||||
'crayons-header__link--current',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeMemberMenu', () => {
|
||||
const initialMenuHTML = `
|
||||
<div class="crayons-header__menu" id="crayons-header__menu" data-testid="menu-dropdown">
|
||||
<button type="button" class="crayons-header__menu__trigger" id="member-menu-button" aria-expanded="false" aria-label="Navigation menu">
|
||||
<span class="crayons-avatar crayons-avatar--l"><img class="crayons-avatar__image" alt="" id="nav-profile-image" src=""></span>
|
||||
</button>
|
||||
<div class="crayons-dropdown left-2 right-2 s:right-4 s:left-auto p-0 crayons-header__menu__dropdown inline-block">
|
||||
<ul class="p-0" id="crayons-header__menu__dropdown__list">
|
||||
<li id="user-profile-link-placeholder" class="border-0 border-b-1 border-solid border-base-20 p-1 mb-1">
|
||||
<a id="first-nav-link" class="crayons-link crayons-link--block" href="">
|
||||
</a>
|
||||
</li>
|
||||
<li class="px-1 py-1">
|
||||
<a href="/signout_confirm" class="crayons-link crayons-link--block" id="last-nav-link">Sign Out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
it('toggles dropdown menu expanded state', async () => {
|
||||
document.body.innerHTML = initialMenuHTML;
|
||||
|
||||
const menuNavButton = getByLabelText(document.body, 'Navigation menu');
|
||||
const memberMenu = getByTestId(document.body, 'menu-dropdown');
|
||||
|
||||
initializeMemberMenu(memberMenu, menuNavButton);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
||||
);
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false');
|
||||
expect(memberMenu).not.toHaveClass('showing');
|
||||
|
||||
fireEvent.click(menuNavButton);
|
||||
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('true');
|
||||
expect(memberMenu).toHaveClass('showing');
|
||||
|
||||
fireEvent.click(menuNavButton);
|
||||
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false');
|
||||
expect(memberMenu).not.toHaveClass('showing');
|
||||
});
|
||||
|
||||
it('closes menu on Escape key', async () => {
|
||||
document.body.innerHTML = initialMenuHTML;
|
||||
|
||||
const menuNavButton = getByLabelText(document.body, 'Navigation menu');
|
||||
const memberMenu = getByTestId(document.body, 'menu-dropdown');
|
||||
|
||||
initializeMemberMenu(memberMenu, menuNavButton);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
||||
);
|
||||
|
||||
fireEvent.click(menuNavButton);
|
||||
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('true');
|
||||
expect(memberMenu).toHaveClass('showing');
|
||||
|
||||
fireEvent.keyUp(memberMenu, { key: 'Escape' });
|
||||
|
||||
await waitFor(() =>
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
||||
);
|
||||
|
||||
expect(memberMenu).not.toHaveClass('showing');
|
||||
});
|
||||
|
||||
it('closes the menu on click outside', async () => {
|
||||
document.body.innerHTML = `
|
||||
<div data-testid="container">
|
||||
${initialMenuHTML}
|
||||
</div>
|
||||
`;
|
||||
|
||||
const menuNavButton = getByLabelText(document.body, 'Navigation menu');
|
||||
const memberMenu = getByTestId(document.body, 'menu-dropdown');
|
||||
|
||||
initializeMemberMenu(memberMenu, menuNavButton);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
||||
);
|
||||
|
||||
fireEvent.click(menuNavButton);
|
||||
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('true');
|
||||
expect(memberMenu).toHaveClass('showing');
|
||||
|
||||
// Click on a link in the menu to close load a page and close the menu.
|
||||
memberMenu.querySelector('a').click();
|
||||
|
||||
await waitFor(() =>
|
||||
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
||||
);
|
||||
|
||||
expect(memberMenu).not.toHaveClass('showing');
|
||||
expect(notificationsLink).toHaveAttribute('aria-current', 'page');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -69,16 +69,6 @@ export function initializeMemberMenu(memberTopMenu, menuNavButton) {
|
|||
menuNavButton.setAttribute('aria-expanded', 'true');
|
||||
});
|
||||
} else {
|
||||
memberTopMenu.addEventListener('mouseover', (_event) => {
|
||||
classList.add('desktop');
|
||||
openHeaderMenu(memberTopMenu, menuNavButton);
|
||||
});
|
||||
memberTopMenu.addEventListener('mouseout', (_event) => {
|
||||
if (!memberTopMenu.dataset.clicked) {
|
||||
closeHeaderMenu(memberTopMenu, menuNavButton);
|
||||
}
|
||||
});
|
||||
|
||||
memberTopMenu.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Escape' && classList.contains('showing')) {
|
||||
closeHeaderMenu(memberTopMenu, menuNavButton);
|
||||
|
|
@ -192,9 +182,9 @@ export function setCurrentPageIconLink(currentPage, pageEntries) {
|
|||
.forEach(([page, iconLink]) => {
|
||||
if (currentPage === page) {
|
||||
iconLink.blur();
|
||||
iconLink.classList.add('crayons-header__link--current');
|
||||
iconLink.setAttribute('aria-current', 'page');
|
||||
} else {
|
||||
iconLink.classList.remove('crayons-header__link--current');
|
||||
iconLink.removeAttribute('aria-current');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,35 @@
|
|||
<div class="crayons-dropdown left-2 right-2 s:right-4 s:left-auto crayons-header__menu__dropdown inline-block m:-mr-2 top-100 -mt-1">
|
||||
<div class="crayons-dropdown left-2 right-2 s:left-auto crayons-header__menu__dropdown inline-block m:-mr-2 top-100">
|
||||
<ul class="m-0" id="crayons-header__menu__dropdown__list">
|
||||
<li id="user-profile-link-placeholder" class="border-0 border-b-1 border-solid border-base-20 pb-2 mb-2">
|
||||
<a id="first-nav-link" class="crayons-link crayons-link--block">
|
||||
<a href="#" id="first-nav-link" class="c-link c-link--default c-link--block lh-tight">
|
||||
<div>
|
||||
<span class="fw-medium block"><%= t("views.main.nav.placeholder") %></span>
|
||||
<small class="fs-s color-base-50"></small>
|
||||
<small class="fs-s opacity-75"></small>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="js-header-menu-admin-link hidden">
|
||||
<a href="<%= admin_path %>" class="crayons-link crayons-link--block" data-no-instant><%= t("views.main.nav.admin") %></a>
|
||||
<a href="<%= admin_path %>" class="c-link c-link--block" data-no-instant><%= t("views.main.nav.admin") %></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<%= dashboard_path %>" class="c-link c-link--block"><%= t("views.main.nav.dashboard") %></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<%= mod_path %>" class="c-link c-link--block trusted-visible-block"><%= t("views.main.nav.moderator_center") %></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<%= new_path %>" class="c-link c-link--block"><%= t("views.main.create_post") %></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<%= readinglist_path %>" class="c-link c-link--block"><%= t("views.main.nav.list") %></a>
|
||||
</li>
|
||||
<li><a href="<%= dashboard_path %>" class="crayons-link crayons-link--block"><%= t("views.main.nav.dashboard") %></a></li>
|
||||
<li><a href="<%= mod_path %>" class="crayons-link crayons-link--block trusted-visible-block"><%= t("views.main.nav.moderator_center") %></a></li>
|
||||
<li><a href="<%= new_path %>" class="crayons-link crayons-link--block"><%= t("views.main.create_post") %></a></li>
|
||||
<li><a href="<%= readinglist_path %>" class="crayons-link crayons-link--block"><%= t("views.main.nav.list") %></a></li>
|
||||
<li class="border-0 border-b-1 border-solid border-base-20 pb-2">
|
||||
<a href="<%= user_settings_path %>" class="crayons-link crayons-link--block" id="second-last-nav-link"><%= t("views.main.nav.settings") %></a>
|
||||
<a href="<%= user_settings_path %>" class="c-link c-link--block" id="second-last-nav-link"><%= t("views.main.nav.settings") %></a>
|
||||
</li>
|
||||
|
||||
<li class="pt-2">
|
||||
<a href="<%= signout_confirm_path %>" class="crayons-link crayons-link--block" id="last-nav-link"><%= t("views.main.signout") %></a>
|
||||
<a href="<%= signout_confirm_path %>" class="c-link c-link--block" id="last-nav-link"><%= t("views.main.signout") %></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
<span id="route-change-target" tabindex="-1"></span>
|
||||
<a href="#main-content" class="skip-content-link"><%= t("views.main.header.skip") %></a>
|
||||
<div class="crayons-header__container">
|
||||
<button class="crayons-btn crayons-btn--ghost crayons-btn--icon-rounded js-hamburger-trigger inline-block m:hidden mx-2">
|
||||
<%= inline_svg_tag("hamburger.svg", aria: true, class: "crayons-icon", title: t("views.hamburger.icon")) %>
|
||||
</button>
|
||||
<span class="inline-block m:hidden ">
|
||||
<button class="c-btn c-btn--icon-alone js-hamburger-trigger mx-2">
|
||||
<%= inline_svg_tag("hamburger.svg", aria: true, class: "crayons-icon", title: t("views.hamburger.icon")) %>
|
||||
</button>
|
||||
</span>
|
||||
<%= render "layouts/logo" %>
|
||||
|
||||
<div id="pwa-nav-buttons" class="pwa-nav-buttons">
|
||||
|
|
@ -25,7 +27,7 @@
|
|||
<div class="crayons-fields crayons-fields--horizontal">
|
||||
<div class="crayons-field flex-1 relative">
|
||||
<input class="crayons-header--search-input crayons-textfield js-search-input" type="text" id="nav-search" name="q" placeholder="<%= t("views.search.placeholder") %>" autocomplete="off" />
|
||||
<button type="submit" aria-label="<%= t("views.search.icon.aria_label") %>" class="crayons-btn crayons-btn--ghost crayons-btn--s crayons-btn--icon-rounded absolute right-2 bottom-0 top-0 m-1">
|
||||
<button type="submit" aria-label="<%= t("views.search.icon.aria_label") %>" class="c-btn c-btn--icon-alone absolute inset-px left-auto mt-0 py-0">
|
||||
<%= inline_svg_tag("search.svg", aria_hidden: true, width: 24, height: 24, class: "crayons-icon", title: t("views.search.icon.title")) %>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -34,35 +36,39 @@
|
|||
</div>
|
||||
|
||||
<div class="flex items-center h-100 ml-auto">
|
||||
<a class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded m:hidden <% unless user_signed_in? %>mx-2<% end %>" id="search-link" aria-label="<%= t("views.search.icon.aria_label") %>" href="<%= search_path %>">
|
||||
<a class="c-link c-link--icon-alone c-link--block m:hidden <% unless user_signed_in? %>mx-1<% end %>" id="search-link" aria-label="<%= t("views.search.icon.aria_label") %>" href="<%= search_path %>">
|
||||
<%= inline_svg_tag("search.svg", aria: true, class: "crayons-icon", title: t("views.search.icon.title")) %>
|
||||
</a>
|
||||
<% if user_signed_in? %>
|
||||
<a href="<%= new_path %>" class="crayons-btn hidden mr-2 whitespace-nowrap m:block ml-auto"><%= t("views.main.create_post") %></a>
|
||||
<span class="hidden m:flex ml-auto">
|
||||
<a href="<%= new_path %>" class="c-cta c-cta--branded mr-2 whitespace-nowrap"><%= t("views.main.create_post") %></a>
|
||||
|
||||
<span class="trusted-visible-block">
|
||||
<a id="moderation-link" class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded m:flex hidden" aria-label="<%= t("views.main.header.moderation.aria_label") %>" href="<%= mod_path %>">
|
||||
<%= inline_svg_tag("mod.svg", aria: true, class: "crayons-icon", title: t("views.main.header.moderation.icon")) %>
|
||||
</a>
|
||||
<span class="trusted-visible-block">
|
||||
<a id="moderation-link" class="c-link c-link--icon-alone c-link--block mx-1" aria-label="<%= t("views.main.header.moderation.aria_label") %>" href="<%= mod_path %>">
|
||||
<%= inline_svg_tag("mod.svg", aria: true, class: "crayons-icon", title: t("views.main.header.moderation.icon")) %>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<a href="<%= notifications_path %>" id="notifications-link" class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="<%= t("views.main.header.notifications.aria_label") %>">
|
||||
<a href="<%= notifications_path %>" id="notifications-link" class="c-link c-link--icon-alone c-link--block mx-1" aria-label="<%= t("views.main.header.notifications.aria_label") %>">
|
||||
<%= inline_svg_tag("bell.svg", aria: true, class: "crayons-icon", title: t("views.main.header.notifications.icon")) %>
|
||||
<span class="crayons-indicator crayons-indicator--critical hidden" id="notifications-number"></span>
|
||||
</a>
|
||||
|
||||
<div class="crayons-header__menu" id="crayons-header__menu">
|
||||
<button type="button" class="crayons-header__menu__trigger" id="member-menu-button" aria-label="<%= t("views.main.header.nav.aria_label") %>" aria-expanded="false">
|
||||
<div class="crayons-header__menu mx-1" id="crayons-header__menu">
|
||||
<button type="button" class="c-btn c-btn--icon-alone radius-full p-1" id="member-menu-button" aria-label="<%= t("views.main.header.nav.aria_label") %>" aria-expanded="false">
|
||||
<span class="crayons-avatar crayons-avatar--l"><img class="crayons-avatar__image" alt="" id="nav-profile-image" /></span>
|
||||
</button>
|
||||
<%= render "layouts/nav_menu" %>
|
||||
</div>
|
||||
<% else %>
|
||||
<a href="<%= sign_up_path %>" class="crayons-btn crayons-btn--ghost-brand mr-2 hidden m:block whitespace-nowrap ml-auto" data-no-instant>
|
||||
<%= t("views.main.header.login") %>
|
||||
</a>
|
||||
<span class="hidden m:block">
|
||||
<a href="<%= sign_up_path %>" class="c-link c-link--block mr-2 whitespace-nowrap ml-auto" data-no-instant>
|
||||
<%= t("views.main.header.login") %>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<a href="<%= sign_up_path(state: "new-user") %>" class="crayons-btn mr-2 whitespace-nowrap fs-s s:fs-base" data-no-instant>
|
||||
<a href="<%= sign_up_path(state: "new-user") %>" class="c-cta c-cta--branded whitespace-nowrap mr-2" data-no-instant>
|
||||
<%= t("views.main.header.create_account") %>
|
||||
</a>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -3,69 +3,72 @@ describe('Member Menu Dropdown', () => {
|
|||
cy.testSetup();
|
||||
cy.fixture('users/articleEditorV1User.json').as('user');
|
||||
|
||||
// Explicitly set the viewport to make sure we're in the full desktop view for these tests
|
||||
cy.viewport('macbook-15');
|
||||
|
||||
cy.get('@user').then((user) => {
|
||||
cy.loginAndVisit(user, '/');
|
||||
});
|
||||
});
|
||||
|
||||
it('hovering on User Avatar should show User Dropdown menu', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).trigger('mouseover');
|
||||
it('clicking on User Avatar should open and close User Dropdown menu', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).as('menuButton');
|
||||
cy.get('@menuButton')
|
||||
.should('have.attr', 'aria-expanded', 'false')
|
||||
.click()
|
||||
.should('have.attr', 'aria-expanded', 'true');
|
||||
|
||||
cy.get('#crayons-header__menu__dropdown__list').within(() => {
|
||||
cy.findByRole('link', {
|
||||
name: 'Article Editor v1 User @article_editor_v1_user',
|
||||
}).should('have.focus');
|
||||
|
||||
cy.findByRole('link', { name: 'Dashboard' });
|
||||
cy.findByRole('link', { name: 'Create Post' });
|
||||
cy.findByRole('link', { name: 'Reading list' });
|
||||
cy.findByRole('link', { name: 'Settings' });
|
||||
cy.findByRole('link', { name: 'Sign Out' });
|
||||
});
|
||||
|
||||
cy.get('@menuButton')
|
||||
.click()
|
||||
.should('have.attr', 'aria-expanded', 'false')
|
||||
.should('have.focus');
|
||||
});
|
||||
|
||||
it('pressing escape should close the User Dropdown menu', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).trigger('mouseover');
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).as('menuButton');
|
||||
cy.get('@menuButton')
|
||||
.should('have.attr', 'aria-expanded', 'false')
|
||||
.click()
|
||||
.should('have.attr', 'aria-expanded', 'true');
|
||||
|
||||
cy.findByRole('link', { name: 'Dashboard' })
|
||||
.as('dashboard')
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('body').type('{esc}');
|
||||
cy.get('@dashboard').should('not.be.visible');
|
||||
cy.get('@menuButton').should('have.attr', 'aria-expanded', 'false');
|
||||
});
|
||||
|
||||
it('close menu on clicking', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).trigger('mouseover');
|
||||
it('closes menu on click outside', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).as('menuButton');
|
||||
cy.get('@menuButton')
|
||||
.should('have.attr', 'aria-expanded', 'false')
|
||||
.click()
|
||||
.should('have.attr', 'aria-expanded', 'true');
|
||||
|
||||
cy.findByRole('link', { name: 'Dashboard' })
|
||||
.as('dashboard')
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('body').click('topLeft');
|
||||
|
||||
cy.get('@dashboard').should('not.be.visible');
|
||||
cy.get('@menuButton').should('have.attr', 'aria-expanded', 'false');
|
||||
});
|
||||
|
||||
it('closes menu on selecting a link from the menu', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).click();
|
||||
cy.findByRole('link', { name: 'Dashboard' }).as('dashboard').click();
|
||||
cy.get('@dashboard').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('if User profile is already hovered, clicking on it again should not close it', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).as(
|
||||
'memberDropDownButton',
|
||||
);
|
||||
cy.get('@memberDropDownButton').trigger('mouseover');
|
||||
cy.findByRole('link', { name: 'Dashboard' }).as('dashboard');
|
||||
cy.get('@memberDropDownButton').click();
|
||||
cy.get('@dashboard').should('be.visible');
|
||||
});
|
||||
|
||||
it('hovering out should close menu', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).as(
|
||||
'memberDropDownButton',
|
||||
);
|
||||
cy.get('@memberDropDownButton').trigger('mouseover');
|
||||
cy.findByRole('link', { name: 'Dashboard' }).as('dashboard');
|
||||
cy.get('@memberDropDownButton').trigger('mouseout');
|
||||
cy.get('@dashboard').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('Dropdown menu opened by click will not close on hovering out', () => {
|
||||
cy.findByRole('button', { name: 'Navigation menu' }).as(
|
||||
'memberDropDownButton',
|
||||
);
|
||||
cy.get('@memberDropDownButton').trigger('mouseover').click();
|
||||
cy.findByRole('link', { name: 'Dashboard' })
|
||||
.as('dashboard')
|
||||
.should('be.visible');
|
||||
cy.get('@memberDropDownButton').trigger('mouseout');
|
||||
cy.get('@dashboard').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue