From fb006a9ecfb8639970b2b1e592b917460604c532 Mon Sep 17 00:00:00 2001 From: rebecca shoptaw Date: Wed, 3 Jun 2026 14:09:03 -0400 Subject: [PATCH 1/5] Draft link-that-looks-like-button implementation --- src/elements/ia-button/ia-button-story.ts | 6 ++ src/elements/ia-button/ia-button.test.ts | 36 ++++++++++- src/elements/ia-button/ia-button.ts | 73 ++++++++++++++++------- 3 files changed, 91 insertions(+), 24 deletions(-) diff --git a/src/elements/ia-button/ia-button-story.ts b/src/elements/ia-button/ia-button-story.ts index 35da5e2..c6c39ca 100644 --- a/src/elements/ia-button/ia-button-story.ts +++ b/src/elements/ia-button/ia-button-story.ts @@ -53,6 +53,12 @@ const propInputSettings: PropInputSettings[] = [ inputType: 'radio', radioOptions: ['button', 'submit', 'reset'], }, + { + label: 'Link to attach to button', + propertyName: 'href', + defaultValue: '', + inputType: 'text', + }, ]; const styleInputSettings: StyleInputSettings[] = [ diff --git a/src/elements/ia-button/ia-button.test.ts b/src/elements/ia-button/ia-button.test.ts index 46fb5e9..6ba5d71 100644 --- a/src/elements/ia-button/ia-button.test.ts +++ b/src/elements/ia-button/ia-button.test.ts @@ -6,7 +6,7 @@ import { IAButton } from './ia-button'; import './ia-button'; describe('IA button', () => { - test('renders a basic button', async () => { + test('renders a basic button by default', async () => { const el = await fixture(html`Submit`); const button = el.shadowRoot?.querySelector('button'); @@ -14,6 +14,40 @@ describe('IA button', () => { expect(button?.disabled).to.equal(false); }); + test('renders a link instead of a button if href provided', async () => { + const el = await fixture( + html`Submit`, + ); + + const button = el.shadowRoot?.querySelector('button'); + const link = el.shadowRoot?.querySelector('a'); + expect(button).not.to.exist; + expect(link).to.exist; + expect(link?.href).to.equal('https://archive.org/foo'); + }); + + test('does not add the disabled class to the link by default', async () => { + const el = await fixture( + html`Submit`, + ); + + const link = el.shadowRoot?.querySelector('a'); + expect(link).to.exist; + expect(link?.classList.contains('disabled')).to.be.false; + }); + + test('adds the disabled class to the link if the component is disabled', async () => { + const el = await fixture( + html`Submit`, + ); + + const link = el.shadowRoot?.querySelector('a'); + expect(link).to.exist; + expect(link?.classList.contains('disabled')).to.be.true; + }); + test('displays slotted text within button', async () => { const el = await fixture( html`Submit`, diff --git a/src/elements/ia-button/ia-button.ts b/src/elements/ia-button/ia-button.ts index 80d8f67..26189f1 100644 --- a/src/elements/ia-button/ia-button.ts +++ b/src/elements/ia-button/ia-button.ts @@ -52,15 +52,32 @@ export class IAButton extends LitElement { | 'submit' | 'reset' = 'button'; + /* An optional href to use. If provided, the button will be rendered as an a element instead of a button */ + @property({ type: String }) href?: string; + render(): TemplateResult { return html` - + ${this.href + ? html` + + ${this.buttonTextTemplate} + + ` + : html` + + `} `; } @@ -71,6 +88,11 @@ export class IAButton extends LitElement { } } + /* The text to render within the button */ + private get buttonTextTemplate(): TemplateResult { + return this.loading ? this.loadingStateTemplate : html``; + } + /* Content to render while button is loading */ private get loadingStateTemplate(): TemplateResult { return html` @@ -82,6 +104,11 @@ export class IAButton extends LitElement { `; } + /* Whether the button should be disabled */ + private get isDisabled(): boolean { + return this.disabled || this.loading; + } + /** Sets up or removes button type emulation as needed */ private setButtonTypeEmulation(): void { const hiddenButton: HTMLInputElement | null = this.querySelector( @@ -233,7 +260,7 @@ export class IAButton extends LitElement { display: inline-block; /* keeps host sized to button */ } - button { + .ia-button { font-family: var(--base-font-family--); font-size: var(--font-size-standard--); height: var(--button-height--); @@ -265,7 +292,7 @@ export class IAButton extends LitElement { } button:disabled, - button.disabled { + .ia-button.disabled { cursor: not-allowed; color: var(--disabled-cta-text-color--); background-color: var(--disabled-cta-fill--); @@ -286,61 +313,61 @@ export class IAButton extends LitElement { opacity: 0.7; } - button.primary { + .ia-button.primary { color: var(--primary-cta-text-color--); background-color: var(--primary-cta-fill--); border-color: var(--primary-cta-border--); } - button.secondary { + .ia-button.secondary { color: var(--secondary-cta-text-color--); background-color: var(--secondary-cta-fill--); border-color: var(--secondary-cta-border--); } - button.danger { + .ia-button.danger { color: var(--danger-cta-text-color--); background-color: var(--danger-cta-fill--); border-color: var(--danger-cta-border--); } - button.warning { + .ia-button.warning { color: var(--warning-cta-text-color--); background-color: var(--warning-cta-fill--); border-color: var(--warning-cta-border--); } - button.transparent { + .ia-button.transparent { color: inherit; border-width: 0; background-color: transparent; border-color: transparent; } - button.custom { + .ia-button.custom { color: var(--ia-button-custom-text-color--); background-color: var(--ia-button-custom-fill--); border-color: var(--ia-button-custom-border--); } - button.custom:enabled:is(:hover, :focus, :active) { + .ia-button.custom:enabled:is(:hover, :focus, :active) { color: var(--ia-button-custom-active-text-color--); background-color: var(--ia-button-custom-active-fill--); border-color: var(--ia-button-custom-active-border--); } - :host(.fit-content) button { + :host(.fit-content) .ia-button { padding: 0; height: fit-content; } - button.link:enabled:hover, - button.danger-link:enabled:hover { + .ia-button.link:enabled:hover, + .ia-button.danger-link:enabled:hover { text-decoration: underline; } - button.link, - button.danger-link { + .ia-button.link, + .ia-button.danger-link { margin: 0; border: 0; appearance: none; @@ -350,11 +377,11 @@ export class IAButton extends LitElement { padding: 0; } - button.link { + .ia-button.link { color: var(--link-color--); } - button.danger-link { + .ia-button.danger-link { color: var(--color-danger--); } From baaef184095a3cdd8375d47ac42ab5bf96bd8157 Mon Sep 17 00:00:00 2001 From: rebecca shoptaw Date: Wed, 3 Jun 2026 14:12:18 -0400 Subject: [PATCH 2/5] Try a different approach --- src/elements/ia-button/ia-button.test.ts | 26 +----------------- src/elements/ia-button/ia-button.ts | 35 ++++++++++-------------- 2 files changed, 16 insertions(+), 45 deletions(-) diff --git a/src/elements/ia-button/ia-button.test.ts b/src/elements/ia-button/ia-button.test.ts index 6ba5d71..33eb4c6 100644 --- a/src/elements/ia-button/ia-button.test.ts +++ b/src/elements/ia-button/ia-button.test.ts @@ -14,40 +14,16 @@ describe('IA button', () => { expect(button?.disabled).to.equal(false); }); - test('renders a link instead of a button if href provided', async () => { + test('renders a link around the button if href provided', async () => { const el = await fixture( html`Submit`, ); - const button = el.shadowRoot?.querySelector('button'); const link = el.shadowRoot?.querySelector('a'); - expect(button).not.to.exist; expect(link).to.exist; expect(link?.href).to.equal('https://archive.org/foo'); }); - test('does not add the disabled class to the link by default', async () => { - const el = await fixture( - html`Submit`, - ); - - const link = el.shadowRoot?.querySelector('a'); - expect(link).to.exist; - expect(link?.classList.contains('disabled')).to.be.false; - }); - - test('adds the disabled class to the link if the component is disabled', async () => { - const el = await fixture( - html`Submit`, - ); - - const link = el.shadowRoot?.querySelector('a'); - expect(link).to.exist; - expect(link?.classList.contains('disabled')).to.be.true; - }); - test('displays slotted text within button', async () => { const el = await fixture( html`Submit`, diff --git a/src/elements/ia-button/ia-button.ts b/src/elements/ia-button/ia-button.ts index 26189f1..4cb3f12 100644 --- a/src/elements/ia-button/ia-button.ts +++ b/src/elements/ia-button/ia-button.ts @@ -58,26 +58,8 @@ export class IAButton extends LitElement { render(): TemplateResult { return html` ${this.href - ? html` - - ${this.buttonTextTemplate} - - ` - : html` - - `} + ? html`${this.buttonTemplate}` + : this.buttonTemplate} `; } @@ -88,6 +70,19 @@ export class IAButton extends LitElement { } } + /* The native button to render */ + private get buttonTemplate(): TemplateResult { + return html` + + `; + } + /* The text to render within the button */ private get buttonTextTemplate(): TemplateResult { return this.loading ? this.loadingStateTemplate : html``; From 5ba7a17482dad36dea989858ac107007451bdc12 Mon Sep 17 00:00:00 2001 From: rebecca shoptaw Date: Wed, 3 Jun 2026 14:15:52 -0400 Subject: [PATCH 3/5] Tidy up new approach --- src/elements/ia-button/ia-button.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/elements/ia-button/ia-button.ts b/src/elements/ia-button/ia-button.ts index 4cb3f12..695e1da 100644 --- a/src/elements/ia-button/ia-button.ts +++ b/src/elements/ia-button/ia-button.ts @@ -76,7 +76,7 @@ export class IAButton extends LitElement { @@ -99,11 +99,6 @@ export class IAButton extends LitElement { `; } - /* Whether the button should be disabled */ - private get isDisabled(): boolean { - return this.disabled || this.loading; - } - /** Sets up or removes button type emulation as needed */ private setButtonTypeEmulation(): void { const hiddenButton: HTMLInputElement | null = this.querySelector( @@ -286,7 +281,7 @@ export class IAButton extends LitElement { -o-user-select: none; } - button:disabled, + .ia-button:disabled, .ia-button.disabled { cursor: not-allowed; color: var(--disabled-cta-text-color--); @@ -295,16 +290,16 @@ export class IAButton extends LitElement { opacity: 0.5; } - button:enabled:hover { + .ia-button:enabled:hover { opacity: 0.9; } - button:focus-visible { + .ia-button:focus-visible { opacity: 0.8; outline-style: double; } - button:active { + .ia-button:active { opacity: 0.7; } From c99a4daf4c6abc79b0bce4eef36d1db0b5e67e80 Mon Sep 17 00:00:00 2001 From: rebecca shoptaw Date: Wed, 3 Jun 2026 14:20:17 -0400 Subject: [PATCH 4/5] Switch back from .ia-button for new implementation --- src/elements/ia-button/ia-button.ts | 48 ++++++++++++++++------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/elements/ia-button/ia-button.ts b/src/elements/ia-button/ia-button.ts index 695e1da..b78d332 100644 --- a/src/elements/ia-button/ia-button.ts +++ b/src/elements/ia-button/ia-button.ts @@ -52,7 +52,7 @@ export class IAButton extends LitElement { | 'submit' | 'reset' = 'button'; - /* An optional href to use. If provided, the button will be rendered as an a element instead of a button */ + /* An optional href to wrap around the button */ @property({ type: String }) href?: string; render(): TemplateResult { @@ -75,7 +75,7 @@ export class IAButton extends LitElement { return html`