Skip to content

Commit

Permalink
Add new buttons variants and sizes (#1003)
Browse files Browse the repository at this point in the history
* Add new VButton sizes and variants

* Add new Storybook tests

* Add border to transparent- buttons

* Update bordered and transparent buttons

* Update stories

* Update snapshots

* Remove pressed variants

* Add missing snapshots

* Fix transparent buttons

* Update paddings

In accordance with #860 (comment)

* Update snapshots

* Update frontend/src/components/VButton.vue

Co-authored-by: Zack Krida <zackkrida@pm.me>

---------

Co-authored-by: Zack Krida <zackkrida@pm.me>
  • Loading branch information
obulat and zackkrida committed Apr 4, 2023
1 parent 4211f16 commit 8eed586
Show file tree
Hide file tree
Showing 29 changed files with 262 additions and 31 deletions.
113 changes: 111 additions & 2 deletions frontend/src/components/VButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -158,6 +167,24 @@ const VButton = defineComponent({
type: String as PropType<ButtonConnections>,
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)
Expand All @@ -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],
Expand Down Expand Up @@ -223,13 +259,26 @@ 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,
typeRef,
isActive,
isConnected,
isPlainDangerous,
isNewVariant,
isFilled,
isBordered,
isTransparent,
}
},
})
Expand All @@ -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;
}
Expand Down
122 changes: 94 additions & 28 deletions frontend/src/components/meta/VButton.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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"

<Meta title="Components/VButton" components={VButton} />

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: `
<div id="wrapper"
class="w-40 h-16 flex items-center justify-center"
:class="args?.variant.startsWith('transparent') ? 'bg-dark-charcoal-06': 'bg-white'">
<VButton v-bind="args" @click="onClick" href="/">
Code is Poetry
</VButton>
</div>
`,
components: { VButton },
methods: {
Expand All @@ -28,6 +50,25 @@ export const Template = (args) => ({
},
})

export const TemplateWithIcons = (args) => ({
template: `
<div class="flex flex-col items-center gap-4 flex-wrap">
<VButton :variant="args.variant" :size="args.size" :has-icon-start="true">
<VIcon :icon-path="replayIcon" />Button
</VButton>
<VButton :variant="args.variant" :size="args.size" has-icon-end>
Button<VIcon :icon-path="externalLinkIcon" />
</VButton>
<VButton :variant="args.variant" :size="args.size" has-icon-start has-icon-end>
<VIcon :icon-path="replayIcon" />Button<VIcon :icon-path="externalLinkIcon" />
</VButton>
</div>`,
components: { VButton, VIcon },
setup() {
return { args, replayIcon, externalLinkIcon }
},
})

# VButton

<Description of={VButton} />
Expand All @@ -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" },
Expand All @@ -69,7 +112,7 @@ export const VariantsTemplate = (args) => ({
<VButton v-for="variant in args.variants"
:variant="variant"
:key="variant"
class="caption-bold"
class="description-bold"
v-bind="args">
{{ capitalize(variant) }}
</VButton>
Expand All @@ -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.

<Canvas>
<Story
name="primary"
args={{ variants: ["primary"] }}
name="filled"
args={{
variants: buttonVariants.filter((variant) =>
variant.startsWith("filled-")
),
}}
argTypes={{
pressed: { control: "boolean" },
size: {
options: buttonSizes,
control: { type: "radio" },
defaultValue: "medium",
},
disabled: { control: "boolean" },
}}
Expand All @@ -109,22 +156,24 @@ this media item' buttons. It is a pink button.
</Story>
</Canvas>

### 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.

<Canvas>
<Story
name="secondary"
args={{ variants: ["secondary-bordered", "secondary-filled", "secondary"] }}
name="bordered"
args={{
variants: buttonVariants.filter((variant) =>
variant.startsWith("bordered-")
),
}}
argTypes={{
pressed: { control: "boolean" },
size: {
options: buttonSizes,
control: { type: "radio" },
defaultValue: "medium",
},
disabled: { control: "boolean" },
}}
Expand All @@ -133,35 +182,52 @@ There are three variants of secondary buttons: filled, bordered and text
</Story>
</Canvas>

### 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.

<Canvas>
<Story
name="action-menu"
name="transparent"
args={{
variants: ["action-menu", "action-menu-bordered", "action-menu-muted"],
variants: buttonVariants.filter((variant) =>
variant.startsWith("transparent-")
),
}}
argTypes={{
pressed: { control: "boolean" },
size: {
options: buttonSizes,
control: { type: "radio" },
defaultValue: "medium",
},
disabled: { control: "boolean" },
}}
>
{VariantsTemplate.bind({})}
</Story>
</Canvas>

### Buttons with Icons

<Canvas>
<Story
name="icons"
argTypes={{
pressed: { control: "boolean" },
size: {
options: buttonSizes,
control: { type: "radio" },
defaultValue: "medium",
},
variant: {
options: buttonVariants,
control: { type: "select" },
defaultValue: "bordered-dark",
},
disabled: { control: "boolean" },
}}
>
{TemplateWithIcons.bind({})}
</Story>
</Canvas>
8 changes: 8 additions & 0 deletions frontend/src/types/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Loading

0 comments on commit 8eed586

Please sign in to comment.