diff --git a/frontend/src/components/VButton.vue b/frontend/src/components/VButton.vue index 77fce9a17be..656821d4224 100644 --- a/frontend/src/components/VButton.vue +++ b/frontend/src/components/VButton.vue @@ -10,8 +10,17 @@ { [$style[`${variant}-pressed`]]: isActive, [$style[`connection-${connections}`]]: isConnected, - 'border border-tx ring-offset-1 focus-visible:outline-none focus-visible:ring focus-visible:ring-pink': - !isPlainDangerous, + [$style[`icon-start-${size}`]]: hasIconStart, + [$style[`icon-end-${size}`]]: hasIconEnd, + 'gap-x-2': + (hasIconEnd || hasIconStart) && (size == 'medium' || size == 'large'), + 'gap-x-1': (hasIconEnd || hasIconStart) && size == 'small', + // Custom tailwind classes don't work with CSS modules in Vue so they are written here explicitly instead of accessed off of `$style`. + 'focus-slim-filled': isFilled, + 'focus-slim-tx': isBordered || isTransparent, + 'description-bold': isNewVariant, + 'border border-tx ring-offset-1 focus:outline-none focus-visible:ring focus-visible:ring-pink': + !isPlainDangerous && !isNewVariant, }, ]" :aria-pressed="pressed" @@ -158,6 +167,24 @@ const VButton = defineComponent({ type: String as PropType, default: "none", }, + /** + * Whether the button has an icon at the inline start of the button. + * + * @default false + */ + hasIconStart: { + type: Boolean, + default: false, + }, + /** + * Whether the button has an icon at the inline end of the button. + * + * @default false + */ + hasIconEnd: { + type: Boolean, + default: false, + }, }, setup(props, { attrs }) { const propsRef = toRefs(props) @@ -182,6 +209,15 @@ const VButton = defineComponent({ const isPlainDangerous = computed(() => { return propsRef.variant.value === "plain--avoid" }) + const isFilled = computed(() => { + return props.variant.startsWith("filled-") + }) + const isBordered = computed(() => { + return props.variant.startsWith("bordered-") + }) + const isTransparent = computed(() => { + return props.variant.startsWith("transparent-") + }) watch( [propsRef.disabled, propsRef.focusableWhenDisabled], @@ -223,6 +259,15 @@ const VButton = defineComponent({ { immediate: true } ) + // TODO: remove after the Core UI improvements are done + const isNewVariant = computed(() => { + return ( + props.variant.startsWith("filled-") || + props.variant.startsWith("bordered-") || + props.variant.startsWith("transparent-") + ) + }) + return { disabledAttributeRef, ariaDisabledRef, @@ -230,6 +275,10 @@ const VButton = defineComponent({ isActive, isConnected, isPlainDangerous, + isNewVariant, + isFilled, + isBordered, + isTransparent, } }, }) @@ -253,10 +302,70 @@ export default VButton @apply py-6 px-8; } +.size-small { + @apply h-8 py-0 px-2; +} +.icon-start-small { + @apply ps-1; +} +.icon-end-small { + @apply pe-1; +} + +.size-medium { + @apply h-10 py-0 px-3; +} +.icon-start-medium { + @apply ps-2; +} +.icon-end-medium { + @apply pe-2; +} + +.size-large { + @apply h-12 py-0 px-5; +} +.icon-start-large { + @apply ps-4; +} +.icon-end-large { + @apply pe-4; +} + a.button { @apply no-underline hover:no-underline; } +.filled-pink { + @apply bg-pink text-white hover:bg-dark-pink hover:text-white; +} + +.filled-dark { + @apply bg-dark-charcoal text-white hover:bg-dark-charcoal-80 hover:text-white; +} + +.filled-gray { + @apply bg-dark-charcoal-10 text-dark-charcoal hover:bg-dark-charcoal hover:text-white; +} + +.filled-white { + @apply bg-white text-dark-charcoal hover:bg-dark-charcoal hover:text-white; +} + +.bordered-white { + @apply border border-white bg-white text-dark-charcoal hover:border-dark-charcoal-20; +} + +.bordered-gray { + @apply border border-dark-charcoal-20 bg-white text-dark-charcoal hover:border-dark-charcoal; +} +.transparent-gray { + @apply bg-tx text-dark-charcoal hover:bg-dark-charcoal-10; +} +.transparent-dark { + @apply bg-tx text-dark-charcoal hover:bg-dark-charcoal hover:text-white; +} + .primary { @apply border-tx bg-pink text-white hover:border-tx hover:bg-dark-pink hover:text-white; } diff --git a/frontend/src/components/meta/VButton.stories.mdx b/frontend/src/components/meta/VButton.stories.mdx index 2bb4f1a5e00..5ecfca8e3f4 100644 --- a/frontend/src/components/meta/VButton.stories.mdx +++ b/frontend/src/components/meta/VButton.stories.mdx @@ -5,17 +5,39 @@ import { Meta, Story, } from "@storybook/addon-docs" -import { buttonForms, buttonSizes, buttonVariants } from "~/types/button" +import { + buttonForms, + buttonSizes as allButtonSizes, + buttonVariants as allButtonVariants, +} from "~/types/button" import VButton from "~/components/VButton.vue" +import VIcon from "~/components/VIcon/VIcon.vue" import { capital } from "case" +import replayIcon from "~/assets/icons/replay.svg" +import externalLinkIcon from "~/assets/icons/external-link.svg" + +export const buttonVariants = allButtonVariants.filter( + (variant) => + variant.startsWith("filled-") || + variant.startsWith("bordered-") || + variant.startsWith("transparent-") +) +export const buttonSizes = allButtonSizes.filter( + (size) => !size.endsWith("-old") +) + export const Template = (args) => ({ template: ` +
Code is Poetry +
`, components: { VButton }, methods: { @@ -28,6 +50,25 @@ export const Template = (args) => ({ }, }) +export const TemplateWithIcons = (args) => ({ + template: ` +
+ + Button + + + Button + + + Button + +
`, + components: { VButton, VIcon }, + setup() { + return { args, replayIcon, externalLinkIcon } + }, +}) + # VButton @@ -48,11 +89,13 @@ export const Template = (args) => ({ variant: { options: buttonVariants, control: { type: "select" }, + defaultValue: "filled-pink", }, pressed: { control: "boolean" }, size: { options: buttonSizes, control: { type: "radio" }, + defaultValue: "medium", }, disabled: { control: "boolean" }, focusableWhenDisabled: { control: "boolean" }, @@ -69,7 +112,7 @@ export const VariantsTemplate = (args) => ({ {{ capitalize(variant) }} @@ -87,20 +130,24 @@ export const VariantsTemplate = (args) => ({ ## Button variants -### Primary +### Filled -The style used for Call-to-action buttons, such as the 'Search' button or 'Get -this media item' buttons. It is a pink button. +These buttons have a solid background color and no border. + variant.startsWith("filled-") + ), + }} argTypes={{ pressed: { control: "boolean" }, size: { options: buttonSizes, control: { type: "radio" }, + defaultValue: "medium", }, disabled: { control: "boolean" }, }} @@ -109,22 +156,24 @@ this media item' buttons. It is a pink button. -### Secondary - -The styles used for other buttons. +### Bordered -There are three variants of secondary buttons: filled, bordered and text -(without border). +These buttons have a white background and a border. + variant.startsWith("bordered-") + ), + }} argTypes={{ pressed: { control: "boolean" }, size: { options: buttonSizes, control: { type: "radio" }, + defaultValue: "medium", }, disabled: { control: "boolean" }, }} @@ -133,31 +182,24 @@ There are three variants of secondary buttons: filled, bordered and text -### Action-menu - -The styles used for header 'action-menu' buttons. +### Transparent -'action-menu' also has no border and no background. On hover, there is a light -border. It is used in the desktop header buttons and for the content type -switcher inside the searchbar. - -'action-menu-bordered' has a border but no background. It is used in the desktop -header buttons when the (old) header is scrolled. - -'action-menu-muted' has a light charcoal background. It is used for filters when -some filters are applied. +These buttons are transparent and don't have a border in resting state. + variant.startsWith("transparent-") + ), }} argTypes={{ pressed: { control: "boolean" }, size: { options: buttonSizes, control: { type: "radio" }, + defaultValue: "medium", }, disabled: { control: "boolean" }, }} @@ -165,3 +207,27 @@ some filters are applied. {VariantsTemplate.bind({})} + +### Buttons with Icons + + + + {TemplateWithIcons.bind({})} + + diff --git a/frontend/src/types/button.ts b/frontend/src/types/button.ts index 1a88d05f4fd..36abaa92f58 100644 --- a/frontend/src/types/button.ts +++ b/frontend/src/types/button.ts @@ -19,6 +19,14 @@ export const buttonVariants = [ "full", "dropdown-label", "dropdown-label-pressed", + "filled-pink", + "filled-dark", + "filled-gray", + "filled-white", + "bordered-white", + "bordered-gray", + "transparent-gray", + "transparent-dark", ] as const export type ButtonVariant = typeof buttonVariants[number] diff --git a/frontend/test/storybook/playwright.config.ts b/frontend/test/storybook/playwright.config.ts index b2d856ce6a8..fb1d4bef893 100644 --- a/frontend/test/storybook/playwright.config.ts +++ b/frontend/test/storybook/playwright.config.ts @@ -20,7 +20,7 @@ const config: PlaywrightTestConfig = { baseURL: "http://localhost:54000", trace: "retain-on-failure", }, - timeout: 2 * 60 * 1e3, + timeout: 60 * 1e3, // 1 minute expect: { toMatchSnapshot: { // To avoid flaky tests, we allow a small amount of pixel difference. diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts b/frontend/test/storybook/visual-regression/v-button.spec.ts new file mode 100644 index 00000000000..a43aaa5d6d0 --- /dev/null +++ b/frontend/test/storybook/visual-regression/v-button.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from "@playwright/test" + +import { makeGotoWithArgs } from "~~/test/storybook/utils/args" + +import { buttonVariants } from "~/types/button" + +const buttonLocator = "text=Code is Poetry" +const wrapperLocator = "#wrapper" + +test.describe.configure({ mode: "parallel" }) + +const newButtonVariants = buttonVariants.filter( + (name) => + name.startsWith("filled-") || + name.startsWith("bordered-") || + name.startsWith("transparent-") +) + +test.describe("VButton", () => { + const gotoWithArgs = makeGotoWithArgs("components-vbutton--v-button") + const nonPressedVariants = newButtonVariants.filter( + (name) => !name.endsWith("pressed") + ) + for (const variant of nonPressedVariants) { + test(`${variant} resting`, async ({ page }) => { + await gotoWithArgs(page, { variant }) + expect(await page.locator(wrapperLocator).screenshot()).toMatchSnapshot({ + name: `${variant}-resting.png`, + }) + }) + + test(`${variant} hovered`, async ({ page }) => { + await gotoWithArgs(page, { variant }) + await page.hover(buttonLocator) + expect(await page.locator(wrapperLocator).screenshot()).toMatchSnapshot({ + name: `${variant}-hovered.png`, + }) + }) + + test(`${variant} focused`, async ({ page }) => { + await gotoWithArgs(page, { variant }) + await page.focus(buttonLocator) + expect(await page.locator(wrapperLocator).screenshot()).toMatchSnapshot({ + name: `${variant}-focused.png`, + }) + }) + } +}) diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-focused-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-focused-linux.png new file mode 100644 index 00000000000..d18e4dc864d Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-focused-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-hovered-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-hovered-linux.png new file mode 100644 index 00000000000..24f67f909cd Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-hovered-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-resting-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-resting-linux.png new file mode 100644 index 00000000000..5c5eb920b13 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-gray-resting-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-focused-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-focused-linux.png new file mode 100644 index 00000000000..d18e4dc864d Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-focused-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-hovered-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-hovered-linux.png new file mode 100644 index 00000000000..ef67a126ea9 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-hovered-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-resting-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-resting-linux.png new file mode 100644 index 00000000000..756e0a061e7 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/bordered-white-resting-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-focused-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-focused-linux.png new file mode 100644 index 00000000000..709559d9328 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-focused-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-hovered-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-hovered-linux.png new file mode 100644 index 00000000000..b56de720475 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-hovered-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-resting-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-resting-linux.png new file mode 100644 index 00000000000..c41366e55e2 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-dark-resting-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-focused-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-focused-linux.png new file mode 100644 index 00000000000..2067c0479f6 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-focused-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-hovered-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-hovered-linux.png new file mode 100644 index 00000000000..c41366e55e2 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-hovered-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-resting-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-resting-linux.png new file mode 100644 index 00000000000..824cdb7d3a3 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-gray-resting-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-focused-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-focused-linux.png new file mode 100644 index 00000000000..657c7895e2d Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-focused-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-hovered-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-hovered-linux.png new file mode 100644 index 00000000000..8bc5e31a639 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-hovered-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-resting-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-resting-linux.png new file mode 100644 index 00000000000..b7963a9b73a Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-pink-resting-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-focused-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-focused-linux.png new file mode 100644 index 00000000000..ef717e9b169 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-focused-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-hovered-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-hovered-linux.png new file mode 100644 index 00000000000..c41366e55e2 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-hovered-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-resting-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-resting-linux.png new file mode 100644 index 00000000000..756e0a061e7 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/filled-white-resting-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-focused-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-focused-linux.png new file mode 100644 index 00000000000..385589b6340 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-focused-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-hovered-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-hovered-linux.png new file mode 100644 index 00000000000..1dc23f21e0e Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-hovered-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-resting-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-resting-linux.png new file mode 100644 index 00000000000..756e0a061e7 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-dark-resting-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-focused-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-focused-linux.png new file mode 100644 index 00000000000..385589b6340 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-focused-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-hovered-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-hovered-linux.png new file mode 100644 index 00000000000..824cdb7d3a3 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-hovered-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-resting-linux.png b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-resting-linux.png new file mode 100644 index 00000000000..756e0a061e7 Binary files /dev/null and b/frontend/test/storybook/visual-regression/v-button.spec.ts-snapshots/transparent-gray-resting-linux.png differ