diff --git a/README.md b/README.md index 11ac49c7..941c615f 100644 --- a/README.md +++ b/README.md @@ -349,6 +349,8 @@ html``; Outside lit, use the raw file at `@internetarchive/elements/icons/search.svg`. +`ia-topnav` and `ia-wayback-search-form` render the `topnav-*` icons. + - **Decorative by default.** The root `` has `aria-hidden="true"` and `focusable="false"`. If an icon is the only content of a control, label the control (`aria-label`), or wrap the icon in an element with `role="img"` and an `aria-label`. - **Color and size.** Icons are filled with `currentColor` and sized `1em` by `width`/`height` on the root, so they follow the surrounding text. Set `font-size` or override `width`/`height` in CSS to resize. - **No barrel.** There's deliberately no `icons/index`. Every icon is its own module, so a page that imports one loads one. diff --git a/icons/.multicolor b/icons/.multicolor new file mode 100644 index 00000000..8b26afb4 --- /dev/null +++ b/icons/.multicolor @@ -0,0 +1,4 @@ +# Icons that keep their own colours. The colour and id lint skips them. +topnav-ia-logo +topnav-wayback-logo +topnav-wordmark-stacked diff --git a/src/elements/ia-topnav/assets/img/icon-audio.ts b/icons/topnav-audio.svg similarity index 57% rename from src/elements/ia-topnav/assets/img/icon-audio.ts rename to icons/topnav-audio.svg index a420b69f..b9a70fe4 100644 --- a/src/elements/ia-topnav/assets/img/icon-audio.ts +++ b/icons/topnav-audio.svg @@ -1,28 +1,6 @@ -import { html } from 'lit'; - -export default html` - - Audio icon - An illustration of an audio speaker. - - - - - - - -`; + + + + + + diff --git a/icons/topnav-close.svg b/icons/topnav-close.svg new file mode 100644 index 00000000..7f99fbce --- /dev/null +++ b/icons/topnav-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-donate-unpadded.svg b/icons/topnav-donate-unpadded.svg new file mode 100644 index 00000000..0c77ec92 --- /dev/null +++ b/icons/topnav-donate-unpadded.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-donate.svg b/icons/topnav-donate.svg new file mode 100644 index 00000000..a0619c1a --- /dev/null +++ b/icons/topnav-donate.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-ellipses.svg b/icons/topnav-ellipses.svg new file mode 100644 index 00000000..4d5dee08 --- /dev/null +++ b/icons/topnav-ellipses.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-ia-logo.svg b/icons/topnav-ia-logo.svg new file mode 100644 index 00000000..8d353562 --- /dev/null +++ b/icons/topnav-ia-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/topnav-images.svg b/icons/topnav-images.svg new file mode 100644 index 00000000..c78d0a69 --- /dev/null +++ b/icons/topnav-images.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-search.svg b/icons/topnav-search.svg new file mode 100644 index 00000000..e093af58 --- /dev/null +++ b/icons/topnav-search.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-software.svg b/icons/topnav-software.svg new file mode 100644 index 00000000..357307cb --- /dev/null +++ b/icons/topnav-software.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-texts.svg b/icons/topnav-texts.svg new file mode 100644 index 00000000..b2a3c2ee --- /dev/null +++ b/icons/topnav-texts.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-upload-unpadded.svg b/icons/topnav-upload-unpadded.svg new file mode 100644 index 00000000..04c0da25 --- /dev/null +++ b/icons/topnav-upload-unpadded.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-upload.svg b/icons/topnav-upload.svg new file mode 100644 index 00000000..23658ff8 --- /dev/null +++ b/icons/topnav-upload.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-user.svg b/icons/topnav-user.svg new file mode 100644 index 00000000..db6abb32 --- /dev/null +++ b/icons/topnav-user.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-video.svg b/icons/topnav-video.svg new file mode 100644 index 00000000..542af560 --- /dev/null +++ b/icons/topnav-video.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/topnav-wayback-logo.svg b/icons/topnav-wayback-logo.svg new file mode 100644 index 00000000..b93b032e --- /dev/null +++ b/icons/topnav-wayback-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/topnav-web.svg b/icons/topnav-web.svg new file mode 100644 index 00000000..3032737e --- /dev/null +++ b/icons/topnav-web.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/elements/ia-topnav/assets/img/wordmark-stacked.ts b/icons/topnav-wordmark-stacked.svg similarity index 96% rename from src/elements/ia-topnav/assets/img/wordmark-stacked.ts rename to icons/topnav-wordmark-stacked.svg index 4276a376..a4618110 100644 --- a/src/elements/ia-topnav/assets/img/wordmark-stacked.ts +++ b/icons/topnav-wordmark-stacked.svg @@ -1,13 +1,8 @@ -import { svg } from 'lit'; - -export default svg` - - + + - - -`; + diff --git a/src/elements/ia-topnav/assets/img/icon-close.ts b/src/elements/ia-topnav/assets/img/icon-close.ts deleted file mode 100644 index 60dab359..00000000 --- a/src/elements/ia-topnav/assets/img/icon-close.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Close icon - A line drawing of an X - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-donate-unpadded.ts b/src/elements/ia-topnav/assets/img/icon-donate-unpadded.ts deleted file mode 100644 index 8e1b0429..00000000 --- a/src/elements/ia-topnav/assets/img/icon-donate-unpadded.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Donate icon - An illustration of a heart shape - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-donate.ts b/src/elements/ia-topnav/assets/img/icon-donate.ts deleted file mode 100644 index e7f2c1b0..00000000 --- a/src/elements/ia-topnav/assets/img/icon-donate.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Donate icon - An illustration of a heart shape - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-ellipses.ts b/src/elements/ia-topnav/assets/img/icon-ellipses.ts deleted file mode 100644 index 191e71c7..00000000 --- a/src/elements/ia-topnav/assets/img/icon-ellipses.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Ellipses icon - An illustration of text ellipses. - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-ia-logo.ts b/src/elements/ia-topnav/assets/img/icon-ia-logo.ts deleted file mode 100644 index ccacaced..00000000 --- a/src/elements/ia-topnav/assets/img/icon-ia-logo.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { html } from 'lit'; - -export default html` - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-images.ts b/src/elements/ia-topnav/assets/img/icon-images.ts deleted file mode 100644 index 64e86d23..00000000 --- a/src/elements/ia-topnav/assets/img/icon-images.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Images icon - An illustration of two photographs. - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-search.ts b/src/elements/ia-topnav/assets/img/icon-search.ts deleted file mode 100644 index 18eb1fc8..00000000 --- a/src/elements/ia-topnav/assets/img/icon-search.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Search icon - An illustration of a magnifying glass. - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-software.ts b/src/elements/ia-topnav/assets/img/icon-software.ts deleted file mode 100644 index 6f9eed38..00000000 --- a/src/elements/ia-topnav/assets/img/icon-software.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Software icon - An illustration of a 3.5" floppy disk. - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-texts.ts b/src/elements/ia-topnav/assets/img/icon-texts.ts deleted file mode 100644 index e1796245..00000000 --- a/src/elements/ia-topnav/assets/img/icon-texts.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Texts icon - An illustration of an open book. - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-upload-unpadded.ts b/src/elements/ia-topnav/assets/img/icon-upload-unpadded.ts deleted file mode 100644 index 5b80855a..00000000 --- a/src/elements/ia-topnav/assets/img/icon-upload-unpadded.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Upload files - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-upload.ts b/src/elements/ia-topnav/assets/img/icon-upload.ts deleted file mode 100644 index bdc6bfd6..00000000 --- a/src/elements/ia-topnav/assets/img/icon-upload.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Upload icon - - An illustration of a horizontal line over an up pointing arrow. - - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-user.ts b/src/elements/ia-topnav/assets/img/icon-user.ts deleted file mode 100644 index 2d4a16b5..00000000 --- a/src/elements/ia-topnav/assets/img/icon-user.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { html } from 'lit'; - -export default html` - - User icon - An illustration of a person's head and chest. - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-video.ts b/src/elements/ia-topnav/assets/img/icon-video.ts deleted file mode 100644 index 27385fa2..00000000 --- a/src/elements/ia-topnav/assets/img/icon-video.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Video icon - An illustration of two cells of a film strip. - - -`; diff --git a/src/elements/ia-topnav/assets/img/icon-web.ts b/src/elements/ia-topnav/assets/img/icon-web.ts deleted file mode 100644 index a81002a9..00000000 --- a/src/elements/ia-topnav/assets/img/icon-web.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { html } from 'lit'; - -export default html` - - Web icon - An illustration of a computer application window - - -`; diff --git a/src/elements/ia-topnav/assets/img/icons.ts b/src/elements/ia-topnav/assets/img/icons.ts deleted file mode 100644 index 04a5f202..00000000 --- a/src/elements/ia-topnav/assets/img/icons.ts +++ /dev/null @@ -1,33 +0,0 @@ -import audio from './icon-audio'; -import close from './icon-close'; -import donate from './icon-donate'; -import donateUnpadded from './icon-donate-unpadded'; -import ellipses from './icon-ellipses'; -import iaLogo from './icon-ia-logo'; -import images from './icon-images'; -import search from './icon-search'; -import software from './icon-software'; -import texts from './icon-texts'; -import upload from './icon-upload'; -import uploadUnpadded from './icon-upload-unpadded'; -import user from './icon-user'; -import video from './icon-video'; -import web from './icon-web'; - -export default { - audio, - close, - donate, - donateUnpadded, - ellipses, - iaLogo, - images, - search, - software, - texts, - upload, - uploadUnpadded, - user, - video, - web, -}; diff --git a/src/elements/ia-topnav/assets/img/wayback-logo.ts b/src/elements/ia-topnav/assets/img/wayback-logo.ts deleted file mode 100644 index b7cb923d..00000000 --- a/src/elements/ia-topnav/assets/img/wayback-logo.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { html } from 'lit'; - -export default html` - - - - - - -`; diff --git a/src/elements/ia-topnav/dropdown-menu.ts b/src/elements/ia-topnav/dropdown-menu.ts index 76e3997c..64c2f81e 100644 --- a/src/elements/ia-topnav/dropdown-menu.ts +++ b/src/elements/ia-topnav/dropdown-menu.ts @@ -8,7 +8,7 @@ import { } from 'lit'; import { property } from 'lit/decorators.js'; -import icons from './assets/img/icons'; +import uploadUnpaddedIcon from '@src/icons/topnav-upload-unpadded'; import { defaultTopNavConfig } from './data/menus'; import { formatUrl, makeBooleanString } from './lib/helpers'; import { @@ -115,7 +115,7 @@ export default class DropdownMenu extends TrackedElement { ?.eventCategory}|Nav${link.analyticsEvent}" aria-label=${calloutText ? msg(str`New feature: ${link.title}`) : nothing} > - ${isMobileUpload ? icons.uploadUnpadded : nothing} ${link.title} + ${isMobileUpload ? uploadUnpaddedIcon : nothing} ${link.title} ${calloutText ? html`` : nothing} @@ -232,7 +232,7 @@ export default class DropdownMenu extends TrackedElement { align-items: center; } a.mobile-upload svg { - fill: var(--white); + color: var(--white); margin-right: 1rem; height: 1.4rem; width: 1.4rem; diff --git a/src/elements/ia-topnav/ia-topnav-desktop-subnav.ts b/src/elements/ia-topnav/ia-topnav-desktop-subnav.ts index ccf38ab9..1c78ece8 100644 --- a/src/elements/ia-topnav/ia-topnav-desktop-subnav.ts +++ b/src/elements/ia-topnav/ia-topnav-desktop-subnav.ts @@ -3,7 +3,7 @@ import { property } from 'lit/decorators.js'; import { customElement } from '@src/util/custom-element'; import TrackedElement from './tracked-element'; -import icons from './assets/img/icons'; +import donateIcon from '@src/icons/topnav-donate'; import { formatUrl } from './lib/helpers'; import { IATopNavLink } from './models'; import themeStyles from '@src/themes/theme-styles'; @@ -33,9 +33,10 @@ export class DesktopSubnav extends TrackedElement { static iconFor(title: string): TemplateResult { const subnavIcons: Record = { - Donate: icons.donate, + Donate: donateIcon, }; - return subnavIcons[title] ? subnavIcons[title] : html``; + // The leading space separates the heart from the link text. + return subnavIcons[title] ? html` ${subnavIcons[title]}` : html``; } static get styles(): CSSResultGroup { @@ -74,7 +75,7 @@ export class DesktopSubnav extends TrackedElement { width: 1.6rem; height: 1.6rem; vertical-align: top; - fill: #f00; + color: #f00; } `, ]; diff --git a/src/elements/ia-topnav/ia-topnav-icon-hamburger.test.ts b/src/elements/ia-topnav/ia-topnav-icon-hamburger.test.ts index c5b394ae..640f05b4 100644 --- a/src/elements/ia-topnav/ia-topnav-icon-hamburger.test.ts +++ b/src/elements/ia-topnav/ia-topnav-icon-hamburger.test.ts @@ -17,7 +17,11 @@ describe('', () => { icon.active = true; await icon.updateComplete; - expect(titleId()).to.match(/close/); + // The close icon is decorative, so it carries no title. + const close = icon.shadowRoot?.querySelector('svg'); + expect(titleId()).to.equal(undefined); + expect(close?.getAttribute('aria-hidden')).to.equal('true'); + expect(close?.getAttribute('fill')).to.equal('currentColor'); icon.active = false; await icon.updateComplete; diff --git a/src/elements/ia-topnav/ia-topnav-icon-hamburger.ts b/src/elements/ia-topnav/ia-topnav-icon-hamburger.ts index dab95c2a..71f6eb85 100644 --- a/src/elements/ia-topnav/ia-topnav-icon-hamburger.ts +++ b/src/elements/ia-topnav/ia-topnav-icon-hamburger.ts @@ -1,6 +1,6 @@ import { html, css } from 'lit'; import Icon from './assets/img/icon'; -import icons from './assets/img/icons'; +import closeIcon from '@src/icons/topnav-close'; import { customElement } from '@src/util/custom-element'; import { localized, msg } from '@lit/localize'; @@ -8,7 +8,7 @@ import { localized, msg } from '@lit/localize'; @localized() export class HamBurger extends Icon { render() { - if (this.active) return icons.close; + if (this.active) return closeIcon; return html` + parseFloat(getComputedStyle(document.documentElement).fontSize); + +/** Computed lengths are laid out in 1/64 px steps, so compare within a pixel. */ +function expectPx(actual: string, expected: number, message?: string) { + expect(parseFloat(actual), message).to.be.closeTo(expected, 0.5); +} + +function svgIn(root: ParentNode | null | undefined, selector = 'svg') { + const svg = root?.querySelector(selector); + expect(svg, `${selector} renders an svg`).to.exist; + return svg as SVGSVGElement; +} + +/** Icons are decorative: hidden from assistive tech and out of the tab order. */ +function expectDecorative(svg: SVGSVGElement) { + expect(svg.getAttribute('aria-hidden')).to.equal('true'); + expect(svg.getAttribute('focusable')).to.equal('false'); + expect(svg.querySelector('title, desc')).to.equal(null); + expect(svg.hasAttribute('aria-labelledby')).to.equal(false); +} + +describe('ia-topnav icons', () => { + describe('media buttons', () => { + const menus = [ + 'web', + 'texts', + 'video', + 'audio', + 'software', + 'images', + 'donate', + 'more', + ]; + + test('render one 4rem svg each, beside a label that is never display:none', async () => { + const menu = await fixture( + html``, + ); + const buttons = Array.from( + menu.shadowRoot?.querySelectorAll( + 'ia-topnav-media-button', + ) ?? [], + ); + expect(buttons.map((button) => button.mediatype)).to.deep.equal(menus); + + for (const button of buttons) { + const menuName = button.mediatype; + const svg = svgIn(button.shadowRoot, '.icon svg'); + expect(svg.getAttribute('viewBox'), menuName).to.equal('0 0 40 40'); + expectDecorative(svg); + expectPx(getComputedStyle(svg).width, 4 * rem(), menuName); + expectPx(getComputedStyle(svg).height, 4 * rem(), menuName); + + // The link is named by its own content, so the name contains the + // label even where the label is only visually hidden. + const link = button.shadowRoot?.querySelector('a'); + const label = button.shadowRoot?.querySelector('.label'); + expect(link?.hasAttribute('aria-label'), menuName).to.equal(false); + expect(label?.textContent, menuName).to.equal(button.label); + expect(getComputedStyle(label!).display, menuName).to.not.equal('none'); + } + }); + + test('are grey, red for donate, and white when selected', async () => { + const menu = await fixture( + html``, + ); + const colorOf = (name: string) => { + const button = menu.shadowRoot?.querySelector( + `[data-mediatype=${name}]`, + ); + const path = svgIn(button?.shadowRoot, '.icon svg path'); + return getComputedStyle(path).fill; + }; + + expect(colorOf('video')).to.equal('rgb(153, 153, 153)'); + expect(colorOf('donate')).to.equal('rgb(255, 0, 0)'); + expect(colorOf('texts')).to.not.equal('rgb(153, 153, 153)'); + }); + }); + + describe('primary nav', () => { + const nav = () => + fixture( + html``, + ); + + test('the home link keeps a name and both logos keep their colours', async () => { + const el = await nav(); + const home = el.shadowRoot?.querySelector('a.link-home'); + const [logo, wordmark] = Array.from(home?.querySelectorAll('svg') ?? []); + + expect(home?.getAttribute('aria-label')).to.equal('Internet Archive'); + expect(logo.getAttribute('viewBox')).to.equal('0 0 27 30'); + expect(wordmark.getAttribute('viewBox')).to.equal('0 0 95 30'); + expectDecorative(logo); + expectDecorative(wordmark); + expectPx(getComputedStyle(logo).height, 3 * rem()); + expectPx(getComputedStyle(logo).width, 2.7 * rem()); + expectPx(getComputedStyle(wordmark).height, 3 * rem()); + expectPx(getComputedStyle(wordmark).width, 9.5 * rem()); + expect(getComputedStyle(logo.querySelector('path')!).fill).to.equal( + 'rgb(255, 255, 255)', + ); + expect(getComputedStyle(wordmark.querySelector('path')!).fill).to.equal( + 'rgb(255, 255, 255)', + ); + }); + + test('the search trigger is labelled and its icon is 4rem', async () => { + const el = await nav(); + const trigger = el.shadowRoot?.querySelector('button.search-trigger'); + const svg = svgIn(trigger); + + expect(trigger?.getAttribute('aria-label')).to.equal('Search'); + expectDecorative(svg); + expectPx(getComputedStyle(svg).width, 4 * rem()); + }); + + test('the donate heart is red, 4rem and named by its hidden text', async () => { + const el = await nav(); + const link = el.shadowRoot?.querySelector('a.mobile-donate-link'); + const svg = svgIn(link); + + expectDecorative(svg); + expect(svg.getAttribute('viewBox')).to.equal('0 0 40 40'); + expectPx(getComputedStyle(svg).width, 4 * rem()); + expect(getComputedStyle(svg.querySelector('path')!).fill).to.equal( + 'rgb(255, 0, 0)', + ); + expect(link?.querySelector('ia-sr-only-text')?.textContent).to.contain( + 'Donate to the archive', + ); + }); + + test('the upload link is named even when its text is hidden', async () => { + const el = await nav(); + const link = el.shadowRoot?.querySelector('a.upload'); + const svg = svgIn(link); + + expect(link?.getAttribute('aria-label')).to.equal('Upload'); + expectDecorative(svg); + expectPx(getComputedStyle(svg).width, 3 * rem()); + }); + }); + + test('the login button icon is decorative and the button is labelled', async () => { + const el = await fixture( + html``, + ); + const button = el.shadowRoot?.querySelector('button.logged-out-menu'); + + expect(button?.getAttribute('aria-label')).to.equal('Toggle login menu'); + expectDecorative(svgIn(button)); + }); + + test('the mobile upload link in a dropdown keeps its text and a 1.4rem icon', async () => { + const el = await fixture( + html``, + ); + el.menuItems = buildTopNavMenus('brewster_userid').user; + await el.updateComplete; + + const link = el.shadowRoot?.querySelector('a.mobile-upload'); + const svg = svgIn(link); + + expect(svg.getAttribute('viewBox')).to.equal('8 8 24 24'); + expectDecorative(svg); + expect(link?.textContent?.trim()).to.not.equal(''); + expectPx(getComputedStyle(svg).width, 1.4 * rem()); + }); + + test('the desktop subnav donate link shows a red 1.6rem heart beside its text', async () => { + const el = await fixture( + html``, + ); + const link = el.shadowRoot?.querySelector('a.donate'); + const svg = svgIn(link); + + expectDecorative(svg); + // A space sits between the text and the heart. + expect(link?.textContent).to.equal('Donate '); + expectPx(getComputedStyle(svg).width, 1.6 * rem()); + expect(getComputedStyle(svg.querySelector('path')!).fill).to.equal( + 'rgb(255, 0, 0)', + ); + }); + + describe('wayback search form', () => { + test('keeps its two-colour logo at 205 x 55 and a search glyph', async () => { + const el = await fixture( + html``, + ); + const link = el.shadowRoot?.querySelector('fieldset a'); + const logo = svgIn(link); + const fills = Array.from(logo.querySelectorAll('path')).map( + (path) => getComputedStyle(path).fill, + ); + + expect(link?.getAttribute('aria-label')).to.be.a('string'); + expect(logo.getAttribute('viewBox')).to.equal('0 0 205 55'); + expect(getComputedStyle(logo).width).to.equal('205px'); + expect(getComputedStyle(logo).height).to.equal('55px'); + expect(fills).to.deep.equal(['rgb(171, 46, 51)', 'rgb(33, 30, 30)']); + + const search = svgIn(el.shadowRoot?.querySelector('.search-field')); + expectDecorative(search); + expectPx(getComputedStyle(search).width, 2.4 * rem()); + }); + }); +}); diff --git a/src/elements/ia-topnav/ia-topnav-login-button.ts b/src/elements/ia-topnav/ia-topnav-login-button.ts index 7236be03..32674fc8 100644 --- a/src/elements/ia-topnav/ia-topnav-login-button.ts +++ b/src/elements/ia-topnav/ia-topnav-login-button.ts @@ -1,6 +1,6 @@ import { CSSResultGroup, css, html } from 'lit'; import TrackedElement from './tracked-element'; -import icons from './assets/img/icons'; +import userIcon from '@src/icons/topnav-user'; import { formatUrl, makeBooleanString } from './lib/helpers'; import { property, query } from 'lit/decorators.js'; import { customElement } from '@src/util/custom-element'; @@ -28,7 +28,7 @@ export class LoginButton extends TrackedElement { aria-label=${msg('Toggle login menu')} aria-expanded="${makeBooleanString(this.menuOpened)}" > - ${icons.user} + ${userIcon} ${msg('Sign up')} @@ -104,16 +104,13 @@ export class LoginButton extends TrackedElement { .dropdown-toggle svg { height: 100%; width: 4rem; + color: var(--iconFill); } - .dropdown-toggle .fill-color { - fill: var(--iconFill); - } - - .dropdown-toggle:active .fill-color, - .dropdown-toggle:focus .fill-color, - .dropdown-toggle:hover .fill-color { - fill: var(--linkHoverColor); + .dropdown-toggle:active svg, + .dropdown-toggle:focus svg, + .dropdown-toggle:hover svg { + color: var(--linkHoverColor); } .active { @@ -121,8 +118,8 @@ export class LoginButton extends TrackedElement { background: var(--activeButtonBg); } - .active .fill-color { - fill: var(--activeColor); + .active svg { + color: var(--activeColor); } span { diff --git a/src/elements/ia-topnav/ia-topnav-media-button.ts b/src/elements/ia-topnav/ia-topnav-media-button.ts index ba1b2477..23bf5a54 100644 --- a/src/elements/ia-topnav/ia-topnav-media-button.ts +++ b/src/elements/ia-topnav/ia-topnav-media-button.ts @@ -1,6 +1,13 @@ import { CSSResultGroup, TemplateResult, css, html } from 'lit'; import TrackedElement from './tracked-element'; -import icons from './assets/img/icons'; +import audio from '@src/icons/topnav-audio'; +import donate from '@src/icons/topnav-donate'; +import ellipses from '@src/icons/topnav-ellipses'; +import images from '@src/icons/topnav-images'; +import software from '@src/icons/topnav-software'; +import texts from '@src/icons/topnav-texts'; +import video from '@src/icons/topnav-video'; +import web from '@src/icons/topnav-web'; import { toSentenceCase } from './lib/helpers'; import { property } from 'lit/decorators.js'; import { customElement } from '@src/util/custom-element'; @@ -9,6 +16,17 @@ import { defaultTopNavConfig } from './data/menus'; import themeStyles from '@src/themes/theme-styles'; import { localized, msg } from '@lit/localize'; +const icons: Record = { + audio, + donate, + ellipses, + images, + software, + texts, + video, + web, +}; + @customElement('ia-topnav-media-button') @localized() export class MediaButton extends TrackedElement { @@ -185,16 +203,16 @@ export class MediaButton extends TrackedElement { border-radius: 1rem 0 0 1rem; } - .icon .fill-color { - fill: #999; + .icon svg { + color: #999; } - .icon.active .fill-color { - fill: #fff; + .icon.active svg { + color: #fff; } - .donate .fill-color { - fill: #f00; + .donate svg { + color: #f00; } @media (min-width: ${TOPNAV_MOBILE_BREAKPOINT}px) { @@ -210,22 +228,21 @@ export class MediaButton extends TrackedElement { color: var(--linkHoverColor); } - .menu-item:hover .fill-color, - .menu-item:active .fill-color, - .menu-item:focus .fill-color { - fill: var(--linkHoverColor); + .menu-item:hover svg, + .menu-item:active svg, + .menu-item:focus svg { + color: var(--linkHoverColor); } + /* Visually hidden, so the link's name still contains the label. */ .label { - display: none; - } - - .label, - .web:after { - padding-right: 1rem; - font-size: 1.3rem; - text-transform: uppercase; - color: inherit; + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + overflow: hidden; + white-space: nowrap; + clip-path: inset(50%); } .web:after { @@ -251,35 +268,49 @@ export class MediaButton extends TrackedElement { } /* selected state icon colors */ - .web.selected .fill-color { - fill: #ffcd27; + .web.selected svg { + color: #ffcd27; } - .texts.selected .fill-color { - fill: #faab3c; + .texts.selected svg { + color: #faab3c; } - .video.selected .fill-color { - fill: #f1644b; + .video.selected svg { + color: #f1644b; } - .audio.selected .fill-color { - fill: #00adef; + .audio.selected svg { + color: #00adef; } - .software.selected .fill-color { - fill: #9ecc4f; + .software.selected svg { + color: #9ecc4f; } - .images.selected .fill-color { - fill: #aa99c9; + .images.selected svg { + color: #aa99c9; } } @media (min-width: 1200px) { + .label { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + white-space: normal; + clip-path: none; + } + .label, .web:after { display: inline; + padding-right: 1rem; + font-size: 1.3rem; + text-transform: uppercase; + color: inherit; } .web .label { diff --git a/src/elements/ia-topnav/ia-topnav-primary-nav.test.ts b/src/elements/ia-topnav/ia-topnav-primary-nav.test.ts index 87e758e3..850a2dcf 100644 --- a/src/elements/ia-topnav/ia-topnav-primary-nav.test.ts +++ b/src/elements/ia-topnav/ia-topnav-primary-nav.test.ts @@ -51,6 +51,20 @@ describe('', () => { expect(label?.textContent).to.contain('Donate to the archive'); }); + test('names the hamburger button for the state it will change to', async () => { + const el = await fixture(component({})); + const button = () => el.shadowRoot?.querySelector('button.hamburger'); + + expect(button()?.getAttribute('aria-label')).to.equal('Open main menu'); + expect(button()?.getAttribute('title')).to.equal('Open main menu'); + + el.openMenu = 'media'; + await elementUpdated(el); + + expect(button()?.getAttribute('aria-label')).to.equal('Close main menu'); + expect(button()?.getAttribute('title')).to.equal('Close main menu'); + }); + test('renders the login link when no username present', async () => { const el = await fixture( component({ diff --git a/src/elements/ia-topnav/ia-topnav-primary-nav.ts b/src/elements/ia-topnav/ia-topnav-primary-nav.ts index 6d99ed31..84d6397a 100644 --- a/src/elements/ia-topnav/ia-topnav-primary-nav.ts +++ b/src/elements/ia-topnav/ia-topnav-primary-nav.ts @@ -1,12 +1,15 @@ import { CSSResultGroup, PropertyValues, css, html, nothing } from 'lit'; import TrackedElement from './tracked-element'; -import icons from './assets/img/icons'; +import donateUnpaddedIcon from '@src/icons/topnav-donate-unpadded'; +import iaLogoIcon from '@src/icons/topnav-ia-logo'; +import searchIcon from '@src/icons/topnav-search'; +import uploadIcon from '@src/icons/topnav-upload'; +import wordmarkStackedIcon from '@src/icons/topnav-wordmark-stacked'; import './ia-topnav-icon-hamburger'; import './ia-topnav-login-button'; import type { LoginButton } from './ia-topnav-login-button'; import './ia-topnav-media-menu'; import '@src/elements/ia-sr-only-text/ia-sr-only-text'; -import logoWordmarkStacked from './assets/img/wordmark-stacked'; import { formatUrl } from './lib/helpers'; import { property, query } from 'lit/decorators.js'; import { customElement } from '@src/util/custom-element'; @@ -98,13 +101,18 @@ export class PrimaryNav extends TrackedElement { render() { // const mediaMenuTabIndex = this.openMenu === 'media' ? '' : '-1'; + const hamburgerLabel = + this.openMenu === 'media' + ? msg('Close main menu') + : msg('Open main menu'); return html`