From 2ffde213ff09de4f032a6ca3d2291a17a61c409f Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Thu, 15 Aug 2024 17:04:55 +0100 Subject: [PATCH 01/11] feat(pie-button): DSW-2305 add tag prop which lets the button render as an anchor --- .changeset/fifty-panthers-bake.md | 6 + .changeset/little-ligers-sniff.md | 7 + .../stories/pie-button.stories.ts | 130 ++++++++++++--- .../components/pie-button/src/button.scss | 1 + packages/components/pie-button/src/defs.ts | 33 +++- packages/components/pie-button/src/index.ts | 155 ++++++++++++------ 6 files changed, 253 insertions(+), 79 deletions(-) create mode 100644 .changeset/fifty-panthers-bake.md create mode 100644 .changeset/little-ligers-sniff.md diff --git a/.changeset/fifty-panthers-bake.md b/.changeset/fifty-panthers-bake.md new file mode 100644 index 0000000000..3cd2b6f0ff --- /dev/null +++ b/.changeset/fifty-panthers-bake.md @@ -0,0 +1,6 @@ +--- +"@justeattakeaway/pie-button": minor +--- + +[Added] - tag prop which lets the button render as an anchor tag +[Added] - `href`, `rel` and `target` props which can be passed to the anchor element diff --git a/.changeset/little-ligers-sniff.md b/.changeset/little-ligers-sniff.md new file mode 100644 index 0000000000..c16b5f186c --- /dev/null +++ b/.changeset/little-ligers-sniff.md @@ -0,0 +1,7 @@ +--- +"pie-storybook": patch +--- + +[Added] - anchor story for pie-button +[Added] - new props to button stories +[Changed] - exclude some button props from stories where they aren't relevant diff --git a/apps/pie-storybook/stories/pie-button.stories.ts b/apps/pie-storybook/stories/pie-button.stories.ts index ab4144c653..9dfe45f154 100644 --- a/apps/pie-storybook/stories/pie-button.stories.ts +++ b/apps/pie-storybook/stories/pie-button.stories.ts @@ -1,19 +1,21 @@ import { html, nothing } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; +import { type Meta } from '@storybook/web-components'; /* eslint-disable import/no-duplicates */ import '@justeattakeaway/pie-button'; import { - ButtonProps as ButtonPropsBase, iconPlacements, sizes, types, variants, responsiveSizes, defaultProps, + type ButtonProps as ButtonPropsBase, + defaultProps, iconPlacements, responsiveSizes, sizes, types, variants, } from '@justeattakeaway/pie-button'; /* eslint-enable import/no-duplicates */ import '@justeattakeaway/pie-icons-webc/dist/IconPlusCircle.js'; import { createStory, type TemplateFunction, sanitizeAndRenderHTML } from '../utilities'; -import { StoryMeta, SlottedComponentProps } from '../types'; +import { type SlottedComponentProps } from '../types'; type ButtonProps = SlottedComponentProps; -type ButtonStoryMeta = StoryMeta; +type ButtonStoryMeta = Meta; const defaultArgs: ButtonProps = { ...defaultProps, @@ -25,6 +27,15 @@ const buttonStoryMeta: ButtonStoryMeta = { title: 'Button', component: 'pie-button', argTypes: { + tag: { + description: 'Choose the HTML element that will be used to render the button.
For this story, the prop has the value of `button`. See the Anchor story to interact with the component when this prop has a value of `a`.', + control: { + disable: true, + }, + defaultValue: { + summary: 'button', + }, + }, size: { description: 'Set the size of the button.', control: 'select', @@ -34,7 +45,7 @@ const buttonStoryMeta: ButtonStoryMeta = { }, }, type: { - description: 'Set the type of the button.', + description: 'Set the type of the button.

Set this to `submit` to reveal more controls relating to form submission.', control: 'select', options: types, defaultValue: { @@ -76,7 +87,7 @@ const buttonStoryMeta: ButtonStoryMeta = { }, }, isResponsive: { - description: 'If `true`, uses the next larger size on wide viewports', + description: 'If `true`, uses the next larger size on wide viewports.

Set this to `true` to show the `responsiveSize` control.', control: 'boolean', defaultValue: { summary: defaultProps.isResponsive, @@ -154,6 +165,18 @@ const buttonStoryMeta: ButtonStoryMeta = { }, if: { arg: 'isResponsive', eq: true }, }, + href: { + description: 'Set the href attribute for the underlying anchor tag.', + control: 'text', + }, + target: { + description: 'Set the target attribute for the underlying anchor tag.', + control: 'text', + }, + rel: { + description: 'Set the rel attribute for the underlying anchor tag', + control: 'text', + }, }, args: defaultArgs, parameters: { @@ -186,26 +209,43 @@ const Template: TemplateFunction = ({ responsiveSize, }) => html` ${iconPlacement ? html`` : nothing} ${sanitizeAndRenderHTML(slot)} `; +const AnchorTemplate: TemplateFunction = (props: ButtonProps) => html` + + ${props.iconPlacement ? html`` : nothing} + ${sanitizeAndRenderHTML(props.slot)} + `; + const FormTemplate: TemplateFunction = (props: ButtonProps) => html`

Fake form

@@ -302,18 +342,60 @@ const createButtonStory = createStory(Template, defaultArgs); const createButtonStoryWithForm = createStory(FormTemplate, defaultArgs); -export const Primary = createButtonStory(); -export const Secondary = createButtonStory({ variant: 'secondary' }); -export const Outline = createButtonStory({ variant: 'outline' }, { bgColor: 'background-subtle' }); -export const Ghost = createButtonStory({ variant: 'ghost' }, { bgColor: 'background-subtle' }); -export const Destructive = createButtonStory({ variant: 'destructive' }); -export const DestructiveGhost = createButtonStory({ variant: 'destructive-ghost' }, { bgColor: 'background-subtle' }); -export const Inverse = createButtonStory({ variant: 'inverse' }, { bgColor: 'dark (container-dark)' }); -export const GhostInverse = createButtonStory({ variant: 'ghost-inverse' }, { bgColor: 'dark (container-dark)' }); -export const OutlineInverse = createButtonStory({ variant: 'outline-inverse' }, { bgColor: 'dark (container-dark)' }); -// For this story we simply want to test form integration with a reset and submit button. Therefore we are restricting what controls are shown. +export const Primary = createButtonStory({}, { + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const Secondary = createButtonStory({ variant: 'secondary' }, { + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const Outline = createButtonStory({ variant: 'outline' }, { + bgColor: 'background-subtle', + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const Ghost = createButtonStory({ variant: 'ghost' }, { + bgColor: 'background-subtle', + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const Destructive = createButtonStory({ variant: 'destructive' }, { + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const DestructiveGhost = createButtonStory({ variant: 'destructive-ghost' }, { + bgColor: 'background-subtle', + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const Inverse = createButtonStory({ variant: 'inverse' }, { + bgColor: 'dark (container-dark)', + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const GhostInverse = createButtonStory({ variant: 'ghost-inverse' }, { + bgColor: 'dark (container-dark)', + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const OutlineInverse = createButtonStory({ variant: 'outline-inverse' }, { + bgColor: 'dark (container-dark)', + controls: { exclude: ['variant', 'href', 'rel', 'target'] }, +}); + +export const Anchor = createStory(AnchorTemplate, defaultArgs)({ + href: '/?path=/story/button--anchor', +}, { + controls: { + // Hide button-only controls + exclude: ['type', 'disabled', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'isLoading', 'name', 'value'], + }, +}); + export const FormIntegration = createButtonStoryWithForm({ type: 'submit' }, { controls: { - exclude: ['type', 'slot', 'variant', 'isFullWidth', 'iconPlacement'], + // For this story we simply want to test form integration with a reset and submit button. Therefore we are restricting what controls are shown. + exclude: ['type', 'slot', 'variant', 'isFullWidth', 'iconPlacement', 'href', 'rel', 'target'], }, }); diff --git a/packages/components/pie-button/src/button.scss b/packages/components/pie-button/src/button.scss index ed949dd725..be17c68014 100644 --- a/packages/components/pie-button/src/button.scss +++ b/packages/components/pie-button/src/button.scss @@ -89,6 +89,7 @@ line-height: var(--btn-line-height); cursor: pointer; user-select: none; + text-decoration: none; // used to specify whether the button should be full width or not inline-size: var(--btn-inline-size); diff --git a/packages/components/pie-button/src/defs.ts b/packages/components/pie-button/src/defs.ts index 8bd31ae702..1adcae9780 100644 --- a/packages/components/pie-button/src/defs.ts +++ b/packages/components/pie-button/src/defs.ts @@ -1,5 +1,6 @@ import { type ComponentDefaultProps } from '@justeattakeaway/pie-webc-core'; +export const tags = ['button', 'a'] as const; export const sizes = ['xsmall', 'small-productive', 'small-expressive', 'medium', 'large'] as const; export const responsiveSizes = ['productive', 'expressive'] as const; export const types = ['submit', 'button', 'reset'] as const; @@ -16,30 +17,41 @@ export const formMethodTypes = ['post', 'get', 'dialog'] as const; export const formTargetTypes = ['_self', '_blank', '_parent', '_top'] as const; export interface ButtonProps { + /** + * Which HTML element to use when rendering the button. + */ + tag?: typeof tags[number]; + /** * What size the button should be. */ size?: typeof sizes[number]; + /** * What type attribute should be applied to the button. For example submit, button. */ type?: typeof types[number]; + /** * What style variant the button should be such as primary, outline or ghost. */ variant?: Variant; + /** * The placement of the icon slot, if provided, such as leading or trailing */ iconPlacement?: typeof iconPlacements[number]; + /** * When true, the button element is disabled. */ disabled?: boolean; + /** * When true, the button element will occupy the full width of its container. */ isFullWidth?: boolean; + /** * When true, displays a loading indicator inside the button. */ @@ -99,11 +111,30 @@ export interface ButtonProps { * What size should be attributed to the button when isResponsive is true and the screen is wide. */ responsiveSize?: typeof responsiveSizes[number]; + + /** + * If the button is rendered as an anchor element, this attribute will be applied to the `href` attribute on the anchor. + * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href) + */ + href?: string; + + /** + * If the button is rendered as an anchor element, this attribute will be applied to the `rel` attribute on the anchor. + * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel) + */ + rel?: string; + + /** + * If the button is rendered as an anchor element, this attribute will be applied to the `target` attribute on the anchor. + * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target) + */ + target?: string; } -export type DefaultProps = ComponentDefaultProps; +export type DefaultProps = ComponentDefaultProps; export const defaultProps: DefaultProps = { + tag: 'button', size: 'medium', type: 'submit', variant: 'primary', diff --git a/packages/components/pie-button/src/index.ts b/packages/components/pie-button/src/index.ts index fa7079dbf0..9db2589e3e 100644 --- a/packages/components/pie-button/src/index.ts +++ b/packages/components/pie-button/src/index.ts @@ -1,15 +1,20 @@ import { - LitElement, html, unsafeCSS, nothing, PropertyValues, TemplateResult, + LitElement, html, unsafeCSS, nothing, type PropertyValues, type TemplateResult, } from 'lit'; -import { classMap } from 'lit/directives/class-map.js'; +import { classMap, type ClassInfo } from 'lit/directives/class-map.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; import { property } from 'lit/decorators.js'; +import 'element-internals-polyfill'; + import { validPropertyValues, defineCustomElement, FormControlMixin } from '@justeattakeaway/pie-webc-core'; + +import '@justeattakeaway/pie-spinner'; +import { type SpinnerProps } from '@justeattakeaway/pie-spinner'; + import { - ButtonProps, sizes, types, variants, iconPlacements, defaultProps, + type ButtonProps, defaultProps, iconPlacements, sizes, tags, types, variants, } from './defs'; import styles from './button.scss?inline'; -import 'element-internals-polyfill'; -import '@justeattakeaway/pie-spinner'; // Valid values available to consumers export * from './defs'; @@ -39,8 +44,6 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro } updated (changedProperties: PropertyValues): void { - super.updated(changedProperties); - if (changedProperties.has('type')) { // If the new type is "submit", add the keydown event listener if (this.type === 'submit') { @@ -51,21 +54,25 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro } } - @property() + @property({ type: String }) + @validPropertyValues(componentSelector, tags, defaultProps.tag) + public tag = defaultProps.tag; + + @property({ type: String }) @validPropertyValues(componentSelector, sizes, defaultProps.size) - public size: ButtonProps['size'] = defaultProps.size; + public size = defaultProps.size; - @property() + @property({ type: String }) @validPropertyValues(componentSelector, types, defaultProps.type) - public type: ButtonProps['type'] = defaultProps.type; + public type = defaultProps.type; - @property() + @property({ type: String }) @validPropertyValues(componentSelector, variants, defaultProps.variant) - public variant: ButtonProps['variant'] = defaultProps.variant; + public variant = defaultProps.variant; @property({ type: String }) @validPropertyValues(componentSelector, iconPlacements, defaultProps.iconPlacement) - public iconPlacement: ButtonProps['iconPlacement'] = defaultProps.iconPlacement; + public iconPlacement = defaultProps.iconPlacement; @property({ type: Boolean }) public disabled = defaultProps.disabled; @@ -80,28 +87,37 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro public isResponsive = defaultProps.isResponsive; @property({ type: String }) - public name?: string; + public name: ButtonProps['name']; @property({ type: String }) - public value?: string; + public value: ButtonProps['value']; - @property() + @property({ type: String }) public formaction: ButtonProps['formaction']; - @property() + @property({ type: String }) public formenctype: ButtonProps['formenctype']; - @property() + @property({ type: String }) public formmethod: ButtonProps['formmethod']; @property({ type: Boolean }) public formnovalidate: ButtonProps['formnovalidate']; - @property() + @property({ type: String }) public formtarget: ButtonProps['formtarget']; @property({ type: String }) - public responsiveSize?: ButtonProps['responsiveSize']; + public responsiveSize: ButtonProps['responsiveSize']; + + @property({ type: String }) + public href: ButtonProps['href']; + + @property({ type: String }) + public rel: ButtonProps['rel']; + + @property({ type: String }) + public target: ButtonProps['target']; /** * This method creates an invisible button of the same type as pie-button. It is then clicked, and immediately removed from the DOM. @@ -157,17 +173,17 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro } private _handleClick () { - if (!this.isLoading && this.form) { - if (this.type === 'submit') { - // only submit the form if either formnovalidate is set, or the form passes validation checks (triggers native form validation) - if (this.formnovalidate || this.form.reportValidity()) { - this._simulateNativeButtonClick('submit'); - } - } + if (!this.form) return; + if (this.isLoading) return; + if (this.tag !== 'button') return; - if (this.type === 'reset') { - this._simulateNativeButtonClick('reset'); + if (this.type === 'submit') { + // only submit the form if either formnovalidate is set, or the form passes validation checks (triggers native form validation) + if (this.formnovalidate || this.form.reportValidity()) { + this._simulateNativeButtonClick('submit'); } + } else if (this.type === 'reset') { + this._simulateNativeButtonClick('reset'); } } @@ -197,8 +213,9 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro */ private renderSpinner (): TemplateResult { const { size, variant, disabled } = this; - const spinnerSize = size && size.includes('small') ? 'small' : 'medium'; // includes("small") matches for any small size value and xsmall - let spinnerVariant; + + const spinnerSize: SpinnerProps['size'] = size && size.includes('small') ? 'small' : 'medium'; // includes("small") matches for any small size value and xsmall + let spinnerVariant: SpinnerProps['variant']; if (disabled) { spinnerVariant = variant === 'ghost-inverse' ? 'inverse' : 'secondary'; } else { @@ -207,23 +224,60 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro } return html` - - `; + + `; + } + + renderAnchor (classes: ClassInfo) { + const { + href, iconPlacement, rel, target, + } = this; + + return html` + + ${iconPlacement === 'leading' ? html`` : nothing} + + ${iconPlacement === 'trailing' ? html`` : nothing} + `; + } + + renderButton (classes: ClassInfo) { + const { + disabled, iconPlacement, isLoading, type, + } = this; + + const buttonClasses = { + ...classes, + 'is-loading': isLoading, + }; + + return html` + `; } render () { const { - type, - disabled, isFullWidth, - variant, - size, - isLoading, isResponsive, - iconPlacement, responsiveSize, + size, + tag, + variant, } = this; const classes = { @@ -233,20 +287,13 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro [`o-btn--${responsiveSize}`]: Boolean(isResponsive && responsiveSize), [`o-btn--${variant}`]: true, [`o-btn--${size}`]: true, - 'is-loading': isLoading, }; - return html` - `; + if (tag === 'a') { + return this.renderAnchor(classes); + } + + return this.renderButton(classes); } focus () { From 33f44528f80c727787e3ce1fabdf6b11f10b48d1 Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Fri, 16 Aug 2024 15:17:09 +0100 Subject: [PATCH 02/11] Write tag tests --- .changeset/rich-gorillas-agree.md | 5 + .../test/accessibility/pie-button.spec.ts | 2 +- .../test/component/pie-button.spec.ts | 139 +++++++++++++++--- .../test/visual/pie-button-anchor.spec.ts | 47 ++++++ .../test/visual/pie-button-size.spec.ts | 12 +- .../pie-webc-testing/src/helpers/defs.ts | 19 ++- 6 files changed, 193 insertions(+), 31 deletions(-) create mode 100644 .changeset/rich-gorillas-agree.md create mode 100644 packages/components/pie-button/test/visual/pie-button-anchor.spec.ts diff --git a/.changeset/rich-gorillas-agree.md b/.changeset/rich-gorillas-agree.md new file mode 100644 index 0000000000..a9af7403ec --- /dev/null +++ b/.changeset/rich-gorillas-agree.md @@ -0,0 +1,5 @@ +--- +"@justeattakeaway/pie-webc-testing": patch +--- + +[Changed] - update PropObject type to adhere more closely to component interface diff --git a/packages/components/pie-button/test/accessibility/pie-button.spec.ts b/packages/components/pie-button/test/accessibility/pie-button.spec.ts index 08c2387318..78f3ceb17f 100644 --- a/packages/components/pie-button/test/accessibility/pie-button.spec.ts +++ b/packages/components/pie-button/test/accessibility/pie-button.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '@justeattakeaway/pie-webc-testing/src/playwright/webc-fixtures.ts'; import { getAllPropCombinations, splitCombinationsByPropertyValue } from '@justeattakeaway/pie-webc-testing/src/helpers/get-all-prop-combos.ts'; -import { PropObject, WebComponentPropValues } from '@justeattakeaway/pie-webc-testing/src/helpers/defs.ts'; +import { type PropObject, type WebComponentPropValues } from '@justeattakeaway/pie-webc-testing/src/helpers/defs.ts'; import { PieButton } from '../../src/index.ts'; import { sizes, variants } from '../../src/defs.ts'; diff --git a/packages/components/pie-button/test/component/pie-button.spec.ts b/packages/components/pie-button/test/component/pie-button.spec.ts index 3339c39729..c845789874 100644 --- a/packages/components/pie-button/test/component/pie-button.spec.ts +++ b/packages/components/pie-button/test/component/pie-button.spec.ts @@ -1,18 +1,13 @@ import { getShadowElementStylePropValues } from '@justeattakeaway/pie-webc-testing/src/helpers/get-shadow-element-style-prop-values.ts'; import { test, expect } from '@sand4rt/experimental-ct-web'; -import { PieButton, ButtonProps } from '../../src/index.ts'; - -const props: Partial = { - size: 'large', - variant: 'primary', -}; +import { PieButton, type ButtonProps } from '../../src/index.ts'; type SizeResponsiveSize = { sizeName: ButtonProps['size']; responsiveSize: string; }; -const sizes:Array = [ +const sizes: Array = [ { sizeName: 'xsmall', responsiveSize: '--btn-height--small' }, { sizeName: 'small-expressive', responsiveSize: '--btn-height--medium' }, { sizeName: 'small-productive', responsiveSize: '--btn-height--medium' }, @@ -26,7 +21,6 @@ test('should correctly work with native click events', async ({ mount }) => { const component = await mount( PieButton, { - props, slots: { default: 'Click me!', }, @@ -552,7 +546,6 @@ test.describe('props', () => { const component = await mount( PieButton, { - props, slots: { default: 'Click me!', }, @@ -565,8 +558,7 @@ test.describe('props', () => { }); test.describe('when set to true', () => { test('the button should have the attribute', async ({ mount }) => { - const testProps:Partial = { - ...props, + const props: ButtonProps = { size: 'xsmall', isResponsive: true, }; @@ -574,7 +566,7 @@ test.describe('props', () => { const component = await mount( PieButton, { - props: testProps, + props, slots: { default: 'Click me!', }, @@ -587,14 +579,13 @@ test.describe('props', () => { sizes.forEach(({ sizeName, responsiveSize }) => { test(`a "${sizeName}" size button height should be equivalent to "${responsiveSize}"`, async ({ mount }) => { - const testProps: Partial = { - ...props, + const props: ButtonProps = { size: sizeName, isResponsive: true, }; const component = await mount(PieButton, { - props: testProps, + props, slots: { default: 'Click me!', }, @@ -614,7 +605,6 @@ test.describe('props', () => { const component = await mount( PieButton, { - props, slots: { default: 'Click me!', }, @@ -629,8 +619,7 @@ test.describe('props', () => { test.describe('when "isResponsive" is true', () => { test.describe('when "responsiveSize" is "expressive"', () => { test('the button should have the expected attribute', async ({ mount }) => { - const testProps:Partial = { - ...props, + const props: ButtonProps = { size: 'xsmall', isResponsive: true, responsiveSize: 'expressive', @@ -639,7 +628,7 @@ test.describe('props', () => { const component = await mount( PieButton, { - props: testProps, + props, slots: { default: 'Click me!', }, @@ -654,8 +643,7 @@ test.describe('props', () => { test.describe('when "responsiveSize" is "productive"', () => { test('the button should have the expected attribute', async ({ mount }) => { - const testProps:Partial = { - ...props, + const props: ButtonProps = { size: 'xsmall', isResponsive: true, responsiveSize: 'productive', @@ -664,7 +652,7 @@ test.describe('props', () => { const component = await mount( PieButton, { - props: testProps, + props, slots: { default: 'Click me!', }, @@ -676,4 +664,111 @@ test.describe('props', () => { }); }); }); + + test.describe('tag', () => { + test.describe('when set to "button"', () => { + test('should render a button element', async ({ mount }) => { + // Arrange + const props: ButtonProps = { + tag: 'button', + }; + + // Act + const component = await mount(PieButton, { + props, + slots: { + default: 'Click me!', + }, + }); + + const button = component.locator('button'); + + // Assert + expect(button).toBeVisible(); + }); + + test('should not render anchor-specific attributes', async ({ mount }) => { + // Arrange + const props: ButtonProps = { + tag: 'button', + // Anchor-specific props + href: '/test', + rel: 'noopener noreferrer', + target: '_blank', + }; + + // Act + const component = await mount(PieButton, { + props, + slots: { + default: 'Click me!', + }, + }); + + const button = component.locator('button'); + + const href = await button.getAttribute('href'); + const rel = await button.getAttribute('rel'); + const target = await button.getAttribute('target'); + + // Assert + expect.soft(rel).toBeNull(); + expect.soft(target).toBeNull(); + expect(href).toBeNull(); + }); + }); + + test.describe('when set to "a"', () => { + test('should render an anchor element', async ({ mount }) => { + // Arrange + const props: ButtonProps = { + tag: 'a', + }; + + // Act + const component = await mount(PieButton, { + props, + slots: { + default: 'Click me!', + }, + }); + + const anchor = component.locator('a'); + + // Assert + expect(anchor).toBeVisible(); + }); + + test('should not render button-specific attributes', async ({ mount }) => { + // Arrange + const props: ButtonProps = { + tag: 'a', + // Button-specific props + disabled: true, + isLoading: true, + type: 'submit', + }; + + // Act + const component = await mount(PieButton, { + props, + slots: { + default: 'Click me!', + }, + }); + + const anchor = component.locator('a'); + + // Assert + const disabled = await anchor.getAttribute('disabled'); + const type = await anchor.getAttribute('type'); + const spinner = component.locator('pie-spinner'); + + expect.soft(anchor).not.toHaveClass(/is-loading/); + expect.soft(disabled).toBeNull(); + expect.soft(type).toBeNull(); + expect(spinner).not.toBeVisible(); + }); + }); + }); }); diff --git a/packages/components/pie-button/test/visual/pie-button-anchor.spec.ts b/packages/components/pie-button/test/visual/pie-button-anchor.spec.ts new file mode 100644 index 0000000000..87c899460c --- /dev/null +++ b/packages/components/pie-button/test/visual/pie-button-anchor.spec.ts @@ -0,0 +1,47 @@ +import { test } from '@sand4rt/experimental-ct-web'; +import percySnapshot from '@percy/playwright'; + +import { type PropObject, type WebComponentPropValues } from '@justeattakeaway/pie-webc-testing/src/helpers/defs.ts'; +import { getAllPropCombinations } from '@justeattakeaway/pie-webc-testing/src/helpers/get-all-prop-combos.ts'; +import { createTestWebComponent } from '@justeattakeaway/pie-webc-testing/src/helpers/rendering.ts'; +import { WebComponentTestWrapper } from '@justeattakeaway/pie-webc-testing/src/helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts'; +import { percyWidths } from '@justeattakeaway/pie-webc-testing/src/percy/breakpoints.ts'; + +import { PieButton } from '../../src/index.ts'; +import { type ButtonProps, sizes, variants } from '../../src/defs.ts'; + +const props: PropObject = { + variant: variants, + size: sizes, + tag: 'a', +}; + +// Renders a HTML string with the given prop values +const renderTestPieButton = (propVals: WebComponentPropValues) => `Hello world`; + +const componentPropsMatrix = getAllPropCombinations(props); + +test.beforeEach(async ({ mount }, testInfo) => { + testInfo.setTimeout(testInfo.timeout + 40000); + const component = await mount(PieButton); + await component.unmount(); +}); + +test('should render all size and variant variations for anchor tag', async ({ page, mount }) => { + for (const combo of componentPropsMatrix) { + const { renderedString, propValues } = createTestWebComponent(combo, renderTestPieButton); + const propKeyValues = `tag: ${propValues.tag}, size: ${propValues.size}, variant: ${propValues.variant}`; + + await mount( + WebComponentTestWrapper, + { + props: { propKeyValues }, + slots: { + component: renderedString.trim(), + }, + }, + ); + } + + await percySnapshot(page, 'PIE Button Anchor - sizes/variants', percyWidths); +}); diff --git a/packages/components/pie-button/test/visual/pie-button-size.spec.ts b/packages/components/pie-button/test/visual/pie-button-size.spec.ts index 5e72307ba3..5f97dfb25a 100644 --- a/packages/components/pie-button/test/visual/pie-button-size.spec.ts +++ b/packages/components/pie-button/test/visual/pie-button-size.spec.ts @@ -1,7 +1,7 @@ import { test } from '@sand4rt/experimental-ct-web'; import percySnapshot from '@percy/playwright'; -import type { - PropObject, WebComponentPropValues, WebComponentTestInput, +import { + type PropObject, type WebComponentPropValues, } from '@justeattakeaway/pie-webc-testing/src/helpers/defs.ts'; import { getAllPropCombinations, @@ -14,9 +14,9 @@ import { } from '@justeattakeaway/pie-webc-testing/src/helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts'; import { percyWidths } from '@justeattakeaway/pie-webc-testing/src/percy/breakpoints.ts'; import { PieButton } from '../../src/index.ts'; -import { sizes } from '../../src/defs.ts'; +import { type ButtonProps, sizes } from '../../src/defs.ts'; -const props: PropObject = { +const props: PropObject = { variant: ['primary'], size: sizes, isResponsive: [true, false], @@ -26,7 +26,7 @@ const props: PropObject = { // Renders a HTML string with the given prop values const renderTestPieButton = (propVals: WebComponentPropValues) => `Hello world`; -const componentPropsMatrix : WebComponentPropValues[] = getAllPropCombinations(props); +const componentPropsMatrix = getAllPropCombinations(props); test.beforeEach(async ({ mount }, testInfo) => { testInfo.setTimeout(testInfo.timeout + 40000); @@ -36,7 +36,7 @@ test.beforeEach(async ({ mount }, testInfo) => { test('should render all size variations', async ({ page, mount }) => { for (const combo of componentPropsMatrix) { - const testComponent: WebComponentTestInput = createTestWebComponent(combo, renderTestPieButton); + const testComponent = createTestWebComponent(combo, renderTestPieButton); const propKeyValues = `size: ${testComponent.propValues.size}, isResponsive: ${testComponent.propValues.isResponsive}, responsiveSize: ${testComponent.propValues.responsiveSize}`; await mount( diff --git a/packages/components/pie-webc-testing/src/helpers/defs.ts b/packages/components/pie-webc-testing/src/helpers/defs.ts index 3f5b118ac6..1d619b0db5 100644 --- a/packages/components/pie-webc-testing/src/helpers/defs.ts +++ b/packages/components/pie-webc-testing/src/helpers/defs.ts @@ -1,6 +1,21 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -export type PropObject = { - [key: string]: any; + +/** + * This test helper type contains the same keys as the component prop interface. + * The values can be either a single value or an array of values. + * This is useful for testing components that have multiple props with the same type. + * @example + * interface TestComponentProps { + * foo: string; + * bar: number; + * } + * const testComponentProps: PropObject = { + * foo: 'foo', + * bar: [1, 2, 3], + * }; + */ +export type PropObject = { + [K in keyof T]: T[K] | Readonly; }; export type WebComponentPropValues = { From b7f1a550fcc83ead0e92098d9da04f8fd93d15e0 Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Fri, 16 Aug 2024 15:17:31 +0100 Subject: [PATCH 03/11] Use updated PropObject in divider visual test --- .../components/pie-divider/test/visual/pie-divider.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/pie-divider/test/visual/pie-divider.spec.ts b/packages/components/pie-divider/test/visual/pie-divider.spec.ts index 5468ef435c..7785218c69 100644 --- a/packages/components/pie-divider/test/visual/pie-divider.spec.ts +++ b/packages/components/pie-divider/test/visual/pie-divider.spec.ts @@ -14,10 +14,10 @@ import { WebComponentTestWrapper, } from '@justeattakeaway/pie-webc-testing/src/helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts'; import { percyWidths } from '@justeattakeaway/pie-webc-testing/src/percy/breakpoints.ts'; -import { variants, orientations } from '../../src/defs.ts'; +import { variants, orientations, DividerProps } from '../../src/defs.ts'; import { PieDivider } from '../../src/index.ts'; -const props: PropObject = { +const props: PropObject = { variant: variants, orientation: orientations, }; From c804b3574490c5ec7b10c980ead0313ec6cd9f51 Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Tue, 27 Aug 2024 16:47:39 +0100 Subject: [PATCH 04/11] Update docs --- .changeset/curly-shrimps-smash.md | 5 +++++ .../components/button/overview/overview.md | 18 ++++++++++++++--- .../src/components/link/overview/overview.md | 20 +++++++++++-------- 3 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 .changeset/curly-shrimps-smash.md diff --git a/.changeset/curly-shrimps-smash.md b/.changeset/curly-shrimps-smash.md new file mode 100644 index 0000000000..79d6dea189 --- /dev/null +++ b/.changeset/curly-shrimps-smash.md @@ -0,0 +1,5 @@ +--- +"pie-docs": patch +--- + +[Added] - documentation for when buttons and links act like one another diff --git a/apps/pie-docs/src/components/button/overview/overview.md b/apps/pie-docs/src/components/button/overview/overview.md index f410809771..271c8bd8f3 100644 --- a/apps/pie-docs/src/components/button/overview/overview.md +++ b/apps/pie-docs/src/components/button/overview/overview.md @@ -36,7 +36,7 @@ Buttons serve a wide range of purposes in user interfaces, such as submitting fo dont: { type: usageTypes.text, items: [ - "Do not use buttons as navigational elements. Instead, use links when the desired action is to take the user to a new page." + "Only include one primary button in a page or area of UI." ] } } %} @@ -87,7 +87,7 @@ Secondary buttons serve as supplementary options for secondary, non-essential ac ### Outline -Outline buttons are designed to provide increased emphasis compared to ghost buttons, owing to their visible stroke. They can be utilized either as standalone buttons or in combination with a primary button. +Outline buttons are designed to provide increased emphasis compared to ghost buttons, owing to their visible stroke. They can be utilised either as standalone buttons or in combination with a primary button. {% contentPageImage { src:"../../../assets/img/components/button/variation-outline.svg", @@ -257,11 +257,23 @@ Button sizes can adapt to different screen widths, like wide and narrow views, b --- +## Behaviours + +### Buttons that act as links + +This is available when a button needs to be used as a navigational element to direct users to a new page or location. + +Use these with caution - dictation software users may not be able to properly identify these actions, since they are semantically links, even though they may look like buttons. + +--- + ## Content ### Labels -Button labels should clearly indicate the action of the Button and describe what will occur once the user clicks the Button. Use active verbs, such as Add or Delete. For sets of buttons, use specific labels, such as Save or Discard, instead of using OK and Cancel. This is particularly helpful when the user is confirming an action. Use sentence-style capitalisation (only the first world in a phrase and any proper nouns capitalised). +Button labels should clearly indicate the action of the Button and describe what will occur once the user clicks the Button. Use active verbs, such as Add or Delete. For sets of buttons, use specific labels, such as Save or Discard, instead of using OK and Cancel. This is particularly helpful when the user is confirming an action. + +Use sentence-style capitalisation (only the first world in a phrase and any proper nouns capitalised). --- diff --git a/apps/pie-docs/src/components/link/overview/overview.md b/apps/pie-docs/src/components/link/overview/overview.md index 4fb658f2c9..d1d10eb4ae 100644 --- a/apps/pie-docs/src/components/link/overview/overview.md +++ b/apps/pie-docs/src/components/link/overview/overview.md @@ -33,8 +33,7 @@ Links are often used to connect various pages, sections, or external resources, dont: { type: usageTypes.text, items: [ - "Don’t use standalone links as calls to action. Use buttons instead.", - "Don’t use standalone links for actions that will change elements in a screen. Use buttons instead." + "Don't use the reversed styling when surrounded by regular text, as it will get lost." ] } } %} @@ -201,11 +200,21 @@ You can use icons to reinforce the action that will take place when the user int --- +## Behaviours + +### Links that act as buttons + +This is available when a link needs to be used as a call to action that triggers an action for the users. + +Use these with caution - dictation software users may not be able to properly identify these actions, since they are semantically buttons, even though they may look like links. + +--- + ## Content - Be mindful of which words in a paragraph you use for your links. Make sure the words you convert into links are directly related to the content that the link will lead you to. -- Use sentence-style capitalization (only the first word in a phrase and any proper nouns capitalized). +- Use sentence-style capitalisation (only the first word in a phrase and any proper nouns capitalised). --- @@ -372,11 +381,6 @@ Here are some examples of links in right-to-left context: ## Resources -{% notification { - type: "warning", - message: "We’re currently working on updating our Link documentation, please see the resources below." -} %} - {% resourceTable { componentName: 'Link' } %} From dfab789163b8d52babfac63e332c35c717932259 Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Wed, 28 Aug 2024 14:34:39 +0100 Subject: [PATCH 05/11] Update button dos and don'ts section --- .../components/button/overview/overview.md | 8 +- yarn.lock | 90 +++++++++---------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/apps/pie-docs/src/components/button/overview/overview.md b/apps/pie-docs/src/components/button/overview/overview.md index 271c8bd8f3..fcba447f01 100644 --- a/apps/pie-docs/src/components/button/overview/overview.md +++ b/apps/pie-docs/src/components/button/overview/overview.md @@ -28,15 +28,15 @@ Buttons serve a wide range of purposes in user interfaces, such as submitting fo do: { type: usageTypes.text, items: [ - "Use Buttons when you need to direct the user to an action.", - "When pairing Buttons, use the same sized Buttons together." - + "Use buttons when you need to direct the user to an action.", + "When pairing buttons, use the same sized buttons together.", + "When multiple buttons are used within the same component or page, ensure that there is a clear hierarchy of actions." ] }, dont: { type: usageTypes.text, items: [ - "Only include one primary button in a page or area of UI." + "Don't mix button sizes when buttons are used together in a pair." ] } } %} diff --git a/yarn.lock b/yarn.lock index 9539c384d0..3c850d0e66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5459,7 +5459,7 @@ __metadata: resolution: "@justeattakeaway/pie-assistive-text@workspace:packages/components/pie-assistive-text" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 "@justeattakeaway/pie-webc-core": 0.24.0 @@ -5472,7 +5472,7 @@ __metadata: resolution: "@justeattakeaway/pie-button@workspace:packages/components/pie-button" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-spinner": 0.6.7 "@justeattakeaway/pie-webc-core": 0.24.0 @@ -5487,7 +5487,7 @@ __metadata: resolution: "@justeattakeaway/pie-card@workspace:packages/components/pie-card" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-webc-core": 0.24.0 "@justeattakeaway/pie-wrapper-react": 0.14.1 @@ -5495,38 +5495,38 @@ __metadata: languageName: unknown linkType: soft -"@justeattakeaway/pie-checkbox-group@0.6.1, @justeattakeaway/pie-checkbox-group@workspace:packages/components/pie-checkbox-group": +"@justeattakeaway/pie-checkbox-group@0.6.2, @justeattakeaway/pie-checkbox-group@workspace:packages/components/pie-checkbox-group": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-checkbox-group@workspace:packages/components/pie-checkbox-group" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 "@justeattakeaway/pie-assistive-text": 0.7.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-webc-core": 0.24.0 cem-plugin-module-file-extensions: 0.0.5 languageName: unknown linkType: soft -"@justeattakeaway/pie-checkbox@0.12.1, @justeattakeaway/pie-checkbox@workspace:packages/components/pie-checkbox": +"@justeattakeaway/pie-checkbox@0.12.2, @justeattakeaway/pie-checkbox@workspace:packages/components/pie-checkbox": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-checkbox@workspace:packages/components/pie-checkbox" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 "@justeattakeaway/pie-assistive-text": 0.7.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-webc-core": 0.24.0 cem-plugin-module-file-extensions: 0.0.5 languageName: unknown linkType: soft -"@justeattakeaway/pie-chip@0.7.2, @justeattakeaway/pie-chip@workspace:packages/components/pie-chip": +"@justeattakeaway/pie-chip@0.8.0, @justeattakeaway/pie-chip@workspace:packages/components/pie-chip": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-chip@workspace:packages/components/pie-chip" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 "@justeattakeaway/pie-spinner": 0.6.7 @@ -5535,7 +5535,7 @@ __metadata: languageName: unknown linkType: soft -"@justeattakeaway/pie-components-config@0.17.0, @justeattakeaway/pie-components-config@workspace:configs/pie-components-config": +"@justeattakeaway/pie-components-config@0.18.0, @justeattakeaway/pie-components-config@workspace:configs/pie-components-config": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-components-config@workspace:configs/pie-components-config" dependencies: @@ -5554,7 +5554,7 @@ __metadata: "@custom-elements-manifest/analyzer": 0.9.0 "@justeat/pie-design-tokens": 6.3.1 "@justeattakeaway/pie-button": 0.48.1 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-divider": 0.13.9 "@justeattakeaway/pie-icon-button": 0.28.10 @@ -5584,7 +5584,7 @@ __metadata: resolution: "@justeattakeaway/pie-divider@workspace:packages/components/pie-divider" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-webc-core": 0.24.0 "@justeattakeaway/pie-wrapper-react": 0.14.1 @@ -5597,7 +5597,7 @@ __metadata: resolution: "@justeattakeaway/pie-form-label@workspace:packages/components/pie-form-label" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-switch": 0.30.0 "@justeattakeaway/pie-text-input": 0.23.4 @@ -5618,7 +5618,7 @@ __metadata: resolution: "@justeattakeaway/pie-icon-button@workspace:packages/components/pie-icon-button" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 "@justeattakeaway/pie-spinner": 0.6.7 @@ -5680,7 +5680,7 @@ __metadata: version: 0.0.0-use.local resolution: "@justeattakeaway/pie-icons-webc@workspace:packages/tools/pie-icons-webc" dependencies: - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-icons": 4.19.0 "@justeattakeaway/pie-icons-configs": 4.5.1 "@justeattakeaway/pie-webc-core": 0.24.0 @@ -5716,7 +5716,7 @@ __metadata: resolution: "@justeattakeaway/pie-link@workspace:packages/components/pie-link" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 "@justeattakeaway/pie-webc-core": 0.24.0 @@ -5732,7 +5732,7 @@ __metadata: "@custom-elements-manifest/analyzer": 0.9.0 "@justeat/pie-design-tokens": 6.3.1 "@justeattakeaway/pie-button": 0.48.1 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icon-button": 0.28.10 "@justeattakeaway/pie-icons-webc": 0.25.0 @@ -5751,7 +5751,7 @@ __metadata: resolution: "@justeattakeaway/pie-notification@workspace:packages/components/pie-notification" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icon-button": 0.28.10 "@justeattakeaway/pie-icons-webc": 0.25.0 @@ -5766,7 +5766,7 @@ __metadata: resolution: "@justeattakeaway/pie-spinner@workspace:packages/components/pie-spinner" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-webc-core": 0.24.0 "@justeattakeaway/pie-wrapper-react": 0.14.1 @@ -5779,7 +5779,7 @@ __metadata: resolution: "@justeattakeaway/pie-switch@workspace:packages/components/pie-switch" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 "@justeattakeaway/pie-webc-core": 0.24.0 @@ -5794,7 +5794,7 @@ __metadata: resolution: "@justeattakeaway/pie-tag@workspace:packages/components/pie-tag" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 "@justeattakeaway/pie-webc-core": 0.24.0 @@ -5809,7 +5809,7 @@ __metadata: dependencies: "@custom-elements-manifest/analyzer": 0.9.0 "@justeattakeaway/pie-assistive-text": 0.7.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 "@justeattakeaway/pie-webc-core": 0.24.0 @@ -5819,12 +5819,12 @@ __metadata: languageName: unknown linkType: soft -"@justeattakeaway/pie-textarea@0.7.0, @justeattakeaway/pie-textarea@workspace:packages/components/pie-textarea": +"@justeattakeaway/pie-textarea@0.8.0, @justeattakeaway/pie-textarea@workspace:packages/components/pie-textarea": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-textarea@workspace:packages/components/pie-textarea" dependencies: "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-form-label": 0.14.1 "@justeattakeaway/pie-webc-core": 0.24.0 @@ -5840,7 +5840,7 @@ __metadata: dependencies: "@custom-elements-manifest/analyzer": 0.9.0 "@justeattakeaway/pie-button": 0.48.1 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icon-button": 0.28.10 "@justeattakeaway/pie-icons-webc": 0.25.0 @@ -5853,7 +5853,7 @@ __metadata: version: 0.0.0-use.local resolution: "@justeattakeaway/pie-webc-core@workspace:packages/components/pie-webc-core" dependencies: - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-components-config": 0.18.0 lit: 3.1.3 languageName: unknown linkType: soft @@ -5869,17 +5869,17 @@ __metadata: languageName: unknown linkType: soft -"@justeattakeaway/pie-webc@0.5.24, @justeattakeaway/pie-webc@workspace:packages/components/pie-webc": +"@justeattakeaway/pie-webc@0.5.25, @justeattakeaway/pie-webc@workspace:packages/components/pie-webc": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-webc@workspace:packages/components/pie-webc" dependencies: "@justeattakeaway/pie-assistive-text": 0.7.0 "@justeattakeaway/pie-button": 0.48.1 "@justeattakeaway/pie-card": 0.19.8 - "@justeattakeaway/pie-checkbox": 0.12.1 - "@justeattakeaway/pie-checkbox-group": 0.6.1 - "@justeattakeaway/pie-chip": 0.7.2 - "@justeattakeaway/pie-components-config": 0.17.0 + "@justeattakeaway/pie-checkbox": 0.12.2 + "@justeattakeaway/pie-checkbox-group": 0.6.2 + "@justeattakeaway/pie-chip": 0.8.0 + "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-cookie-banner": 0.25.1 "@justeattakeaway/pie-divider": 0.13.9 "@justeattakeaway/pie-form-label": 0.14.1 @@ -5891,7 +5891,7 @@ __metadata: "@justeattakeaway/pie-switch": 0.30.0 "@justeattakeaway/pie-tag": 0.9.9 "@justeattakeaway/pie-text-input": 0.23.4 - "@justeattakeaway/pie-textarea": 0.7.0 + "@justeattakeaway/pie-textarea": 0.8.0 "@justeattakeaway/pie-toast": 0.3.1 chalk: 5.3.0 bin: @@ -26000,9 +26000,9 @@ __metadata: "@justeattakeaway/pie-assistive-text": 0.7.0 "@justeattakeaway/pie-button": 0.48.1 "@justeattakeaway/pie-card": 0.19.8 - "@justeattakeaway/pie-checkbox": 0.12.1 - "@justeattakeaway/pie-checkbox-group": 0.6.1 - "@justeattakeaway/pie-chip": 0.7.2 + "@justeattakeaway/pie-checkbox": 0.12.2 + "@justeattakeaway/pie-checkbox-group": 0.6.2 + "@justeattakeaway/pie-chip": 0.8.0 "@justeattakeaway/pie-cookie-banner": 0.25.1 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-divider": 0.13.9 @@ -26017,7 +26017,7 @@ __metadata: "@justeattakeaway/pie-switch": 0.30.0 "@justeattakeaway/pie-tag": 0.9.9 "@justeattakeaway/pie-text-input": 0.23.4 - "@justeattakeaway/pie-textarea": 0.7.0 + "@justeattakeaway/pie-textarea": 0.8.0 "@justeattakeaway/pie-toast": 0.3.1 "@storybook/addon-a11y": 8.2.6 "@storybook/addon-designs": 8.0.3 @@ -33939,7 +33939,7 @@ __metadata: "@angular/platform-browser-dynamic": 15.2.0 "@angular/router": 15.2.0 "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 rxjs: 7.8.0 tslib: 2.3.0 typescript: 4.9.4 @@ -33956,7 +33956,7 @@ __metadata: "@babel/preset-env": 7.24.5 "@babel/preset-react": 7.24.1 "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 "@lit/react": 1.0.2 babel-loader: 8 eslint: 8.37.0 @@ -33973,7 +33973,7 @@ __metadata: resolution: "wc-next13@workspace:apps/examples/wc-next13" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 "@lit-labs/nextjs": 0.2.0 "@lit/react": 1.0.5 "@types/react": 18.3.3 @@ -33996,7 +33996,7 @@ __metadata: dependencies: "@babel/preset-env": 7.24.5 "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 babel-loader: 8 core-js: 3.30.0 nuxt: 2.17.0 @@ -34011,7 +34011,7 @@ __metadata: resolution: "wc-nuxt3@workspace:apps/examples/wc-nuxt3" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 "@types/node": 18 nuxt: 3.4.3 nuxt-ssr-lit: 1.6.5 @@ -34023,7 +34023,7 @@ __metadata: resolution: "wc-react17@workspace:apps/examples/wc-react17" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 "@lit/react": 1.0.5 "@types/react": ^17.0.2 "@types/react-dom": ^17.0.2 @@ -34043,7 +34043,7 @@ __metadata: resolution: "wc-react18@workspace:apps/examples/wc-react18" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 "@lit/react": 1.0.5 "@types/react": 18.3.3 "@types/react-dom": 18.3.0 @@ -34065,7 +34065,7 @@ __metadata: "@justeat/pie-design-tokens": 6.3.1 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 vite: 4.5.3 languageName: unknown linkType: soft @@ -34075,7 +34075,7 @@ __metadata: resolution: "wc-vue3@workspace:apps/examples/wc-vue3" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.24 + "@justeattakeaway/pie-webc": 0.5.25 "@types/node": 18.15.11 "@vitejs/plugin-vue": 4.0.0 "@vue/tsconfig": 0.1.3 From 2ff368bf4508fc29e5cfe973108e784dbfb346a9 Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Thu, 29 Aug 2024 14:00:18 +0100 Subject: [PATCH 06/11] Changes from self-review --- .../stories/pie-button.stories.ts | 5 ++-- yarn.lock | 26 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/apps/pie-storybook/stories/pie-button.stories.ts b/apps/pie-storybook/stories/pie-button.stories.ts index 9dfe45f154..4c26176959 100644 --- a/apps/pie-storybook/stories/pie-button.stories.ts +++ b/apps/pie-storybook/stories/pie-button.stories.ts @@ -1,6 +1,5 @@ import { html, nothing } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; -import { type Meta } from '@storybook/web-components'; /* eslint-disable import/no-duplicates */ import '@justeattakeaway/pie-button'; @@ -12,10 +11,10 @@ import { import '@justeattakeaway/pie-icons-webc/dist/IconPlusCircle.js'; import { createStory, type TemplateFunction, sanitizeAndRenderHTML } from '../utilities'; -import { type SlottedComponentProps } from '../types'; +import { type StoryMeta, type SlottedComponentProps } from '../types'; type ButtonProps = SlottedComponentProps; -type ButtonStoryMeta = Meta; +type ButtonStoryMeta = StoryMeta; const defaultArgs: ButtonProps = { ...defaultProps, diff --git a/yarn.lock b/yarn.lock index 4097f11011..a69a4c26e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5547,7 +5547,7 @@ __metadata: languageName: unknown linkType: soft -"@justeattakeaway/pie-cookie-banner@0.25.1, @justeattakeaway/pie-cookie-banner@workspace:packages/components/pie-cookie-banner": +"@justeattakeaway/pie-cookie-banner@0.26.0, @justeattakeaway/pie-cookie-banner@workspace:packages/components/pie-cookie-banner": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-cookie-banner@workspace:packages/components/pie-cookie-banner" dependencies: @@ -5869,7 +5869,7 @@ __metadata: languageName: unknown linkType: soft -"@justeattakeaway/pie-webc@0.5.25, @justeattakeaway/pie-webc@workspace:packages/components/pie-webc": +"@justeattakeaway/pie-webc@0.5.26, @justeattakeaway/pie-webc@workspace:packages/components/pie-webc": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-webc@workspace:packages/components/pie-webc" dependencies: @@ -5880,7 +5880,7 @@ __metadata: "@justeattakeaway/pie-checkbox-group": 0.6.2 "@justeattakeaway/pie-chip": 0.8.0 "@justeattakeaway/pie-components-config": 0.18.0 - "@justeattakeaway/pie-cookie-banner": 0.25.1 + "@justeattakeaway/pie-cookie-banner": 0.26.0 "@justeattakeaway/pie-divider": 0.13.9 "@justeattakeaway/pie-form-label": 0.14.1 "@justeattakeaway/pie-icon-button": 0.28.10 @@ -26182,7 +26182,7 @@ __metadata: "@justeattakeaway/pie-checkbox": 0.12.2 "@justeattakeaway/pie-checkbox-group": 0.6.2 "@justeattakeaway/pie-chip": 0.8.0 - "@justeattakeaway/pie-cookie-banner": 0.25.1 + "@justeattakeaway/pie-cookie-banner": 0.26.0 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-divider": 0.13.9 "@justeattakeaway/pie-form-label": 0.14.1 @@ -34128,7 +34128,7 @@ __metadata: "@angular/platform-browser-dynamic": 15.2.0 "@angular/router": 15.2.0 "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 rxjs: 7.8.0 tslib: 2.3.0 typescript: 4.9.4 @@ -34145,7 +34145,7 @@ __metadata: "@babel/preset-env": 7.24.5 "@babel/preset-react": 7.24.1 "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 "@lit/react": 1.0.2 babel-loader: 8 eslint: 8.37.0 @@ -34162,7 +34162,7 @@ __metadata: resolution: "wc-next13@workspace:apps/examples/wc-next13" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 "@lit-labs/nextjs": 0.2.0 "@lit/react": 1.0.5 "@types/react": 18.3.3 @@ -34185,7 +34185,7 @@ __metadata: dependencies: "@babel/preset-env": 7.24.5 "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 babel-loader: 8 core-js: 3.30.0 nuxt: 2.17.0 @@ -34200,7 +34200,7 @@ __metadata: resolution: "wc-nuxt3@workspace:apps/examples/wc-nuxt3" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 "@types/node": 18 nuxt: 3.4.3 nuxt-ssr-lit: 1.6.5 @@ -34212,7 +34212,7 @@ __metadata: resolution: "wc-react17@workspace:apps/examples/wc-react17" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 "@lit/react": 1.0.5 "@types/react": ^17.0.2 "@types/react-dom": ^17.0.2 @@ -34232,7 +34232,7 @@ __metadata: resolution: "wc-react18@workspace:apps/examples/wc-react18" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 "@lit/react": 1.0.5 "@types/react": 18.3.3 "@types/react-dom": 18.3.0 @@ -34254,7 +34254,7 @@ __metadata: "@justeat/pie-design-tokens": 6.3.1 "@justeattakeaway/pie-css": 0.12.1 "@justeattakeaway/pie-icons-webc": 0.25.0 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 vite: 4.5.3 languageName: unknown linkType: soft @@ -34264,7 +34264,7 @@ __metadata: resolution: "wc-vue3@workspace:apps/examples/wc-vue3" dependencies: "@justeattakeaway/pie-css": 0.12.1 - "@justeattakeaway/pie-webc": 0.5.25 + "@justeattakeaway/pie-webc": 0.5.26 "@types/node": 18.15.11 "@vitejs/plugin-vue": 4.0.0 "@vue/tsconfig": 0.1.3 From c111de64dd1a7c36e1b92b0b08bc2a290153cd7a Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Thu, 29 Aug 2024 14:35:45 +0100 Subject: [PATCH 07/11] Update button display mode --- packages/components/pie-button/src/button.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/pie-button/src/button.scss b/packages/components/pie-button/src/button.scss index be17c68014..7acd5bf464 100644 --- a/packages/components/pie-button/src/button.scss +++ b/packages/components/pie-button/src/button.scss @@ -72,7 +72,7 @@ } position: relative; - display: flex; + display: inline-flex; gap: var(--dt-spacing-b); align-items: center; justify-content: center; From 1af45f6422764c54c56b6d91de0ebab82d1365c0 Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Thu, 29 Aug 2024 15:19:13 +0100 Subject: [PATCH 08/11] Enable dark mode for inverse buttons in anchor visual tests --- .../pie-button/test/visual/pie-button-anchor.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/pie-button/test/visual/pie-button-anchor.spec.ts b/packages/components/pie-button/test/visual/pie-button-anchor.spec.ts index 87c899460c..a18832a64a 100644 --- a/packages/components/pie-button/test/visual/pie-button-anchor.spec.ts +++ b/packages/components/pie-button/test/visual/pie-button-anchor.spec.ts @@ -31,11 +31,12 @@ test('should render all size and variant variations for anchor tag', async ({ pa for (const combo of componentPropsMatrix) { const { renderedString, propValues } = createTestWebComponent(combo, renderTestPieButton); const propKeyValues = `tag: ${propValues.tag}, size: ${propValues.size}, variant: ${propValues.variant}`; + const darkMode = ['inverse', 'ghost-inverse', 'outline-inverse'].includes(propValues.variant); await mount( WebComponentTestWrapper, { - props: { propKeyValues }, + props: { propKeyValues, darkMode }, slots: { component: renderedString.trim(), }, From 84d0be2423cd5c8652e787dcb2e11104ca048a9a Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Thu, 29 Aug 2024 16:00:00 +0100 Subject: [PATCH 09/11] Small refactor in button story --- .../stories/pie-button.stories.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/pie-storybook/stories/pie-button.stories.ts b/apps/pie-storybook/stories/pie-button.stories.ts index 4c26176959..7f05038904 100644 --- a/apps/pie-storybook/stories/pie-button.stories.ts +++ b/apps/pie-storybook/stories/pie-button.stories.ts @@ -341,46 +341,48 @@ const createButtonStory = createStory(Template, defaultArgs); const createButtonStoryWithForm = createStory(FormTemplate, defaultArgs); +const anchorOnlyProps : Array = ['href', 'target', 'rel']; + export const Primary = createButtonStory({}, { - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const Secondary = createButtonStory({ variant: 'secondary' }, { - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const Outline = createButtonStory({ variant: 'outline' }, { bgColor: 'background-subtle', - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const Ghost = createButtonStory({ variant: 'ghost' }, { bgColor: 'background-subtle', - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const Destructive = createButtonStory({ variant: 'destructive' }, { - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const DestructiveGhost = createButtonStory({ variant: 'destructive-ghost' }, { bgColor: 'background-subtle', - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const Inverse = createButtonStory({ variant: 'inverse' }, { bgColor: 'dark (container-dark)', - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const GhostInverse = createButtonStory({ variant: 'ghost-inverse' }, { bgColor: 'dark (container-dark)', - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const OutlineInverse = createButtonStory({ variant: 'outline-inverse' }, { bgColor: 'dark (container-dark)', - controls: { exclude: ['variant', 'href', 'rel', 'target'] }, + controls: { exclude: ['variant', ...anchorOnlyProps] }, }); export const Anchor = createStory(AnchorTemplate, defaultArgs)({ @@ -395,6 +397,6 @@ export const Anchor = createStory(AnchorTemplate, defaultArgs)({ export const FormIntegration = createButtonStoryWithForm({ type: 'submit' }, { controls: { // For this story we simply want to test form integration with a reset and submit button. Therefore we are restricting what controls are shown. - exclude: ['type', 'slot', 'variant', 'isFullWidth', 'iconPlacement', 'href', 'rel', 'target'], + exclude: ['type', 'slot', 'variant', 'isFullWidth', 'iconPlacement', ...anchorOnlyProps], }, }); From 0c1c50146fa4194df7d88f0768cfe22300b4cc20 Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Thu, 29 Aug 2024 16:11:48 +0100 Subject: [PATCH 10/11] Changes from self-review --- packages/components/pie-divider/test/visual/pie-divider.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/pie-divider/test/visual/pie-divider.spec.ts b/packages/components/pie-divider/test/visual/pie-divider.spec.ts index 7785218c69..3316e51a9b 100644 --- a/packages/components/pie-divider/test/visual/pie-divider.spec.ts +++ b/packages/components/pie-divider/test/visual/pie-divider.spec.ts @@ -14,7 +14,7 @@ import { WebComponentTestWrapper, } from '@justeattakeaway/pie-webc-testing/src/helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts'; import { percyWidths } from '@justeattakeaway/pie-webc-testing/src/percy/breakpoints.ts'; -import { variants, orientations, DividerProps } from '../../src/defs.ts'; +import { variants, orientations, type DividerProps } from '../../src/defs.ts'; import { PieDivider } from '../../src/index.ts'; const props: PropObject = { From fb1fb442ccca6a056ce0effc6a4dd2cd4b7f5118 Mon Sep 17 00:00:00 2001 From: Xander Marjoram Date: Fri, 30 Aug 2024 15:16:32 +0100 Subject: [PATCH 11/11] Update description for tag control on anchor story --- apps/pie-storybook/stories/pie-button.stories.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/pie-storybook/stories/pie-button.stories.ts b/apps/pie-storybook/stories/pie-button.stories.ts index 7f05038904..00556c5a54 100644 --- a/apps/pie-storybook/stories/pie-button.stories.ts +++ b/apps/pie-storybook/stories/pie-button.stories.ts @@ -388,6 +388,11 @@ export const OutlineInverse = createButtonStory({ variant: 'outline-inverse' }, export const Anchor = createStory(AnchorTemplate, defaultArgs)({ href: '/?path=/story/button--anchor', }, { + argTypes: { + tag: { + description: 'Choose the HTML element that will be used to render the button.
For this story, the prop has the value of `a`. See the other stories to interact with the component when this prop has a value of `button`.', + }, + }, controls: { // Hide button-only controls exclude: ['type', 'disabled', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'isLoading', 'name', 'value'],