diff --git a/README.md b/README.md
index 11ac49c7..941c615f 100644
--- a/README.md
+++ b/README.md
@@ -349,6 +349,8 @@ html`${search} Search `;
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`
-
- Internet Archive logo
-
- A line drawing of the Internet Archive headquarters building façade.
-
-
-
-
-
-
-
-
-
-
-
-`;
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`${calloutText} `
: 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`
${icons.iaLogo}${logoWordmarkStacked}${iaLogoIcon}${wordmarkStackedIcon}
${this.secondLogoSlot}
@@ -255,8 +264,9 @@ export class PrimaryNav extends TrackedElement {
class="search-trigger"
@click="${this.toggleSearchMenu}"
data-event-click-tracking="${this.config?.eventCategory}|NavSearchOpen"
+ aria-label=${msg('Search')}
>
- ${icons.search}
+ ${searchIcon}
`;
}
@@ -270,7 +280,7 @@ export class PrimaryNav extends TrackedElement {
this.baseHost,
)}
>
- ${icons.donateUnpadded}
+ ${donateUnpaddedIcon}
${msg('Donate to the archive')}
`;
@@ -280,9 +290,10 @@ export class PrimaryNav extends TrackedElement {
return html`
- ${icons.upload}
+ ${uploadIcon}
${msg('Upload')}
`;
}
@@ -369,19 +380,17 @@ export class PrimaryNav extends TrackedElement {
justify-self: stretch;
}
- .ia-logo {
+ .link-home svg {
height: 3rem;
+ margin-right: 5px;
+ }
+ .link-home svg:first-child {
width: 2.7rem;
display: inline-block;
}
- .ia-wordmark {
- height: 3rem;
+ .link-home svg:last-child {
width: 9.5rem;
}
- .ia-logo,
- .ia-wordmark {
- margin-right: 5px;
- }
.hamburger {
-ms-grid-row: 1;
@@ -401,9 +410,7 @@ export class PrimaryNav extends TrackedElement {
.mobile-donate-link svg {
height: 4rem;
width: 4rem;
- }
- .mobile-donate-link .fill-color {
- fill: rgb(255, 0, 0);
+ color: rgb(255, 0, 0);
}
.search-trigger {
@@ -412,9 +419,7 @@ export class PrimaryNav extends TrackedElement {
.search-trigger svg {
height: 4rem;
width: 4rem;
- }
- .search-trigger .fill-color {
- fill: var(--iconFill);
+ color: var(--iconFill);
}
.search-container {
@@ -547,8 +552,7 @@ export class PrimaryNav extends TrackedElement {
display: none;
}
- .ia-logo,
- .ia-wordmark {
+ .link-home svg {
margin-right: 10px;
}
@@ -604,13 +608,13 @@ export class PrimaryNav extends TrackedElement {
.upload svg {
vertical-align: middle;
- fill: var(--iconFill);
+ color: var(--iconFill);
}
.upload:hover svg,
.upload:focus svg,
.upload:active svg {
- fill: var(--linkHoverColor);
+ color: var(--linkHoverColor);
}
.search-container,
diff --git a/src/elements/ia-wayback-search-form/ia-wayback-search-form.ts b/src/elements/ia-wayback-search-form/ia-wayback-search-form.ts
index 993e7a1d..f4caab34 100644
--- a/src/elements/ia-wayback-search-form/ia-wayback-search-form.ts
+++ b/src/elements/ia-wayback-search-form/ia-wayback-search-form.ts
@@ -4,8 +4,8 @@ import { customElement } from '@src/util/custom-element';
import { localized, msg } from '@lit/localize';
-import searchIcon from '@src/elements/ia-topnav/assets/img/icon-search';
-import logo from '@src/elements/ia-topnav/assets/img/wayback-logo';
+import searchIcon from '@src/icons/topnav-search';
+import logo from '@src/icons/topnav-wayback-logo';
import themeStyles from '@src/themes/theme-styles';
/** Desktop styles apply at `min-width: WAYBACK_SEARCH_DESKTOP_BREAKPOINT px`. */
@@ -156,6 +156,11 @@ export class IAWaybackSearchForm extends LitElement {
font-size: 0;
}
+ fieldset a svg {
+ width: 205px;
+ height: 55px;
+ }
+
img {
width: 100%;
max-width: 215px;
@@ -193,10 +198,7 @@ export class IAWaybackSearchForm extends LitElement {
left: 3px;
width: 2.4rem;
height: 2.4rem;
- }
-
- .search-field .fill-color {
- fill: var(--iconFill);
+ color: var(--iconFill, #000);
}
@media (min-width: ${WAYBACK_SEARCH_DESKTOP_BREAKPOINT}px) {
@@ -233,8 +235,8 @@ export class IAWaybackSearchForm extends LitElement {
top: 2px;
}
- .search-field .fill-color {
- fill: var(--wayback-search-desktop-icon-fill--);
+ .search-field svg {
+ color: var(--wayback-search-desktop-icon-fill--);
}
}
`,