Component: Button migration 1: TextButton (#7537)

## Summary

Setup the variants and migrate existing uses of
TextButton/TextIconButton/IconButton to a single Button component.

Still a work in progress.

## Changes

- **What**: Add a new Button
- **What**: Migrate old buttons
- **What**: Delete old buttons
- **Dependencies**: CVA, upgrade Storybook

## Review Focus

<!-- Critical design decisions or edge cases that need attention -->

<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->

## Screenshots (if applicable)

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7537-WIP-Component-Button-migration-2cb6d73d36508156a81bfc7bbddb36e9)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Alexander Brown
2025-12-16 20:38:24 -08:00
committed by GitHub
parent ab76d02823
commit 8d7dd9ed67
19 changed files with 275 additions and 260 deletions

View File

@@ -0,0 +1,68 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import Button from './Button.vue'
import { FOR_STORIES } from '@/components/ui/button/button.variants'
const { variants, sizes } = FOR_STORIES
const meta: Meta<typeof Button> = {
title: 'Components/Button/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
size: {
control: { type: 'select' },
options: sizes,
defaultValue: 'md'
},
variant: {
control: { type: 'select' },
options: variants,
defaultValue: 'primary'
},
as: { defaultValue: 'button' },
asChild: { defaultValue: false },
default: {
defaultValue: 'Button'
}
},
args: {
variant: 'secondary',
size: 'md',
default: 'Button'
}
}
export default meta
type Story = StoryObj<typeof meta>
export const SingleButton: Story = {
args: {
variant: 'primary',
size: 'lg'
}
}
function generateVariants() {
const variantButtons: string[] = []
for (const variant of variants) {
for (const size of sizes) {
variantButtons.push(
`<Button variant="${variant}" size="${size}">${size === 'icon' ? `<i class="icon-[lucide--settings]" />` : variant}</Button>`
)
}
}
return variantButtons
}
// Note: Keep the number of columns here aligned with the number of sizes above.
export const AllVariants: Story = {
render: () => ({
components: { Button },
template: `
<div class="grid grid-cols-4 gap-4 place-items-center-safe">
${generateVariants().join('\n')}
</div>
`
})
}

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
import type { ButtonVariants } from './button.variants'
import { buttonVariants } from './button.variants'
interface Props extends PrimitiveProps {
variant?: ButtonVariants['variant']
size?: ButtonVariants['size']
class?: HTMLAttributes['class']
}
const { as = 'button', class: customClass = '' } = defineProps<Props>()
</script>
<template>
<Primitive
:as
:as-child
:class="cn(buttonVariants({ variant, size }), customClass)"
>
<slot />
</Primitive>
</template>

View File

@@ -0,0 +1,49 @@
import type { VariantProps } from 'cva'
import { cva } from 'cva'
export const buttonVariants = cva({
base: 'inline-flex items-center justify-center gap-2 cursor-pointer whitespace-nowrap appearance-none border-none rounded-md text-sm font-medium font-inter transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
variants: {
variant: {
secondary:
'bg-secondary-background text-secondary-foreground hover:bg-secondary-background-hover',
primary:
'bg-primary-background text-base-foreground hover:bg-primary-background-hover',
inverted:
'bg-base-foreground text-base-background hover:bg-base-foreground/80',
destructive:
'bg-destructive-background text-base-foreground hover:bg-destructive-background-hover',
textonly:
'text-base-foreground bg-transparent hover:bg-secondary-background-hover',
'muted-textonly':
'text-muted-foreground bg-transparent hover:bg-secondary-background-hover'
},
size: {
sm: 'h-6 rounded-sm px-2 py-1 text-xs',
md: 'h-8 rounded-lg p-2 text-xs',
lg: 'h-10 rounded-lg px-4 py-2 text-sm',
icon: 'size-9'
}
},
defaultVariants: {
variant: 'secondary',
size: 'md'
}
})
export type ButtonVariants = VariantProps<typeof buttonVariants>
const variants = [
'secondary',
'primary',
'inverted',
'destructive',
'textonly',
'muted-textonly'
] as const satisfies Array<ButtonVariants['variant']>
const sizes = ['sm', 'md', 'lg', 'icon'] as const satisfies Array<
ButtonVariants['size']
>
export const FOR_STORIES = { variants, sizes } as const