refactor: Button components (#6929)

* feat: refactor button components

* fix: qa

* fix: storybook

* a11y: pass

* fix: build

* fix: rename default ->md

* fix: lint

* docs: buttons.md

* refactor part 1

* refactor part 2

* fix: undo refactor for fresh restart

* refactor

* Revert "refactor"

This reverts commit 96d65902d7.

* refactor: part 1

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: remove text-contrast

* fix: qa

* fix: v-tooltip

* fix: lint

* fix: split broken

* fix: passkey qa

* fix: qa

* fix: qa

* fix: prepr

* fix: splitbutton

* improve button group seam

---------

Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Calum H.
2026-08-04 13:54:04 -07:00
committed by GitHub
co-authored by Prospector
parent 6cfe999cb3
commit bb2193b6f5
398 changed files with 13688 additions and 13133 deletions
+5 -7
View File
@@ -27,12 +27,10 @@
placeholder="Search affiliates..."
clearable
/>
<ButtonStyled color="brand">
<button @click="createModal?.show">
<PlusIcon />
Create affiliate code
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="createModal?.show">
<PlusIcon />
Create affiliate code
</Button>
</div>
</div>
<Admonition v-if="error" type="critical">
@@ -90,7 +88,7 @@ import {
AffiliateLinkCard,
AffiliateLinkCreateModal,
Avatar,
ButtonStyled,
Button,
ConfirmModal,
injectModrinthClient,
injectNotificationManager,
@@ -33,19 +33,20 @@
<div class="flex items-center justify-between">
<span class="label__title font-semibold">Announcement link (optional)</span>
<ButtonStyled v-if="committedNormalizedAnnouncementUrl" type="transparent" size="small">
<a
:href="committedNormalizedAnnouncementUrl"
target="_blank"
rel="noopener noreferrer"
aria-label="Check announcement link"
title="Check announcement link"
class="text-sm"
>
<ExternalIcon aria-hidden="true" />
Open link
</a>
</ButtonStyled>
<ButtonLink
v-if="committedNormalizedAnnouncementUrl"
type="quiet"
size="xs"
:href="committedNormalizedAnnouncementUrl"
target="_blank"
rel="noopener noreferrer"
aria-label="Check announcement link"
title="Check announcement link"
class="!h-6 text-sm"
>
<ExternalIcon aria-hidden="true" />
Open link
</ButtonLink>
</div>
<div class="flex items-center gap-2">
<StyledInput
@@ -110,15 +111,16 @@
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled type="transparent">
<button @click="eventModal?.hide()">Cancel</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!canSaveEvent || isSaving" @click="saveEvent">
<SaveIcon aria-hidden="true" />
{{ modalMode === 'create' ? 'Create event' : 'Save' }}
</button>
</ButtonStyled>
<Button type="quiet" @click="eventModal?.hide()">Cancel</Button>
<Button
type="colored"
color="brand"
:disabled="!canSaveEvent || isSaving"
@click="saveEvent"
>
<SaveIcon aria-hidden="true" />
{{ modalMode === 'create' ? 'Create event' : 'Save' }}
</Button>
</div>
</template>
</NewModal>
@@ -137,12 +139,10 @@
clearable
wrapper-class="w-full sm:w-72"
/>
<ButtonStyled color="brand">
<button :disabled="isSaving" @click="openCreateModal">
<PlusIcon aria-hidden="true" />
New event
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="isSaving" @click="openCreateModal">
<PlusIcon aria-hidden="true" />
New event
</Button>
</div>
</div>
@@ -196,21 +196,23 @@
<template #cell-actions="{ row }">
<div class="flex justify-end gap-2">
<ButtonStyled circular type="outlined" color="red">
<button
:aria-label="`Delete ${row.title}`"
:disabled="isDeletingEvent(row.id)"
@click="openDeleteEventModal(row)"
>
<TrashIcon aria-hidden="true" />
</button>
</ButtonStyled>
<ButtonStyled type="outlined">
<button :disabled="isSaving || isDeletingEvent(row.id)" @click="openEditModal(row)">
Edit
<EditIcon aria-hidden="true" />
</button>
</ButtonStyled>
<IconButton
type="outlined"
:label="`Delete ${row.title}`"
:disabled="isDeletingEvent(row.id)"
class="!text-red !shadow-[inset_0_0_0_1px_var(--color-red)] [&>svg]:!text-red"
@click="openDeleteEventModal(row)"
>
<TrashIcon aria-hidden="true" />
</IconButton>
<Button
type="outlined"
:disabled="isSaving || isDeletingEvent(row.id)"
@click="openEditModal(row)"
>
Edit
<EditIcon aria-hidden="true" />
</Button>
</div>
</template>
@@ -239,8 +241,8 @@ import {
SpinnerIcon,
TrashIcon,
} from '@modrinth/assets'
import { Button, ButtonLink, IconButton } from '@modrinth/ui'
import {
ButtonStyled,
ConfirmModal,
DatePicker,
injectModrinthClient,
@@ -43,18 +43,14 @@
<Toggle id="unprovision" v-model="unprovision" />
</div>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button :disabled="refunding" @click="refundCharge">
<CheckIcon aria-hidden="true" />
Refund charge
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="refundModal.hide()">
<XIcon aria-hidden="true" />
Cancel
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="refunding" @click="refundCharge">
<CheckIcon aria-hidden="true" />
Refund charge
</Button>
<Button @click="refundModal.hide()">
<XIcon aria-hidden="true" />
Cancel
</Button>
</div>
</div>
</NewModal>
@@ -82,18 +78,14 @@
<Toggle id="cancel" v-model="cancel" />
</div>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button :disabled="modifying" @click="modifyCharge">
<CheckIcon aria-hidden="true" />
Modify charge
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="modifyModal.hide()">
<XIcon aria-hidden="true" />
Cancel
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="modifying" @click="modifyCharge">
<CheckIcon aria-hidden="true" />
Modify charge
</Button>
<Button @click="modifyModal.hide()">
<XIcon aria-hidden="true" />
Cancel
</Button>
</div>
</div>
</NewModal>
@@ -117,18 +109,14 @@
<Toggle id="sendEmail" v-model="creditSendEmail" />
</div>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button :disabled="crediting" @click="applyCredit">
<CheckIcon aria-hidden="true" />
Apply credit
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="creditModal.hide()">
<XIcon aria-hidden="true" />
Cancel
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="crediting" @click="applyCredit">
<CheckIcon aria-hidden="true" />
Apply credit
</Button>
<Button @click="creditModal.hide()">
<XIcon aria-hidden="true" />
Cancel
</Button>
</div>
</div>
</NewModal>
@@ -141,13 +129,11 @@
<h1 class="m-0 text-2xl font-extrabold">{{ user?.username }}'s subscriptions</h1>
</div>
<div class="flex items-center gap-2">
<ButtonStyled>
<nuxt-link :to="`/user/${user?.id}`">
<UserIcon aria-hidden="true" />
User profile
<ExternalIcon class="h-4 w-4" />
</nuxt-link>
</ButtonStyled>
<ButtonLink :to="`/user/${user?.id}`">
<UserIcon aria-hidden="true" />
User profile
<ExternalIcon class="h-4 w-4" />
</ButtonLink>
</div>
</div>
<div>
@@ -176,25 +162,20 @@
</div>
<div v-if="subscription.metadata?.id" class="flex flex-col items-end gap-2">
<CopyCode :text="subscription.metadata.id" />
<ButtonStyled
<ButtonLink
v-if="
subscription.metadata?.type === 'pyro' || subscription.metadata?.type === 'medal'
"
:to="`/hosting/manage/${subscription.metadata.id}`"
target="_blank"
class="w-fit"
>
<nuxt-link
:to="`/hosting/manage/${subscription.metadata.id}`"
target="_blank"
class="w-fit"
>
<ServerIcon /> Server panel <ExternalIcon class="h-4 w-4" />
</nuxt-link>
</ButtonStyled>
<ButtonStyled>
<button @click="showCreditModal(subscription)">
<CurrencyIcon />
Credit
</button>
</ButtonStyled>
<ServerIcon /> Server panel <ExternalIcon class="h-4 w-4" />
</ButtonLink>
<Button @click="showCreditModal(subscription)">
<CurrencyIcon />
Credit
</Button>
</div>
</div>
<div class="flex flex-col gap-2">
@@ -226,7 +207,8 @@ import {
} from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
ButtonLink,
CopyCode,
defineMessages,
DropdownSelect,
+17 -18
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { CopyIcon, LibraryIcon, PlayIcon, SearchIcon } from '@modrinth/assets'
import { ButtonStyled, Card, StyledInput } from '@modrinth/ui'
import { Button, Card, StyledInput } from '@modrinth/ui'
import { computed, onMounted, ref } from 'vue'
import docs from '~/templates/docs'
@@ -60,12 +60,10 @@ onMounted(() => {
wrapper-class="w-72"
/>
<ButtonStyled color="brand">
<button :disabled="filtered.length === 0" @click="openAll">
<LibraryIcon class="h-4 w-4" aria-hidden="true" />
Open all ({{ counts.shown }})
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="filtered.length === 0" @click="openAll">
<LibraryIcon class="h-4 w-4" aria-hidden="true" />
Open all ({{ counts.shown }})
</Button>
<span class="text-sm text-secondary">
Showing <span class="font-medium text-contrast">{{ counts.shown }}</span> of
@@ -96,18 +94,19 @@ onMounted(() => {
</div>
<div class="mt-auto flex gap-2">
<ButtonStyled color="brand">
<button class="w-full flex-1 justify-center" @click="openPreview(id)">
<PlayIcon class="h-4 w-4" aria-hidden="true" />
Preview
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
class="w-full flex-1 justify-center"
@click="openPreview(id)"
>
<PlayIcon class="h-4 w-4" aria-hidden="true" />
Preview
</Button>
<ButtonStyled>
<button class="justify-center" title="Copy preview URL" @click="copy(id)">
<CopyIcon class="h-4 w-4" aria-hidden="true" />
</button>
</ButtonStyled>
<Button class="justify-center" title="Copy preview URL" @click="copy(id)">
<CopyIcon class="h-4 w-4" aria-hidden="true" />
</Button>
</div>
</li>
</ul>
+13 -21
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { CopyIcon, LibraryIcon, PlayIcon, SearchIcon } from '@modrinth/assets'
import { ButtonStyled, NewModal, StyledInput } from '@modrinth/ui'
import { Button, IconButton, NewModal, StyledInput } from '@modrinth/ui'
import { computed, onMounted, ref } from 'vue'
import emails from '~/templates/emails'
@@ -182,9 +182,7 @@ onMounted(() => {
</div>
<div class="input-group mt-4">
<ButtonStyled type="transparent">
<button @click="closePreview">Close</button>
</ButtonStyled>
<Button type="quiet" @click="closePreview">Close</Button>
</div>
</div>
</div>
@@ -200,12 +198,10 @@ onMounted(() => {
wrapper-class="w-72"
/>
<ButtonStyled color="brand">
<button :disabled="filtered.length === 0" @click="openAll">
<LibraryIcon class="h-4 w-4" aria-hidden="true" />
Open all ({{ counts.shown }})
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="filtered.length === 0" @click="openAll">
<LibraryIcon class="h-4 w-4" aria-hidden="true" />
Open all ({{ counts.shown }})
</Button>
<span class="text-sm text-secondary">
Showing <span class="font-medium text-contrast">{{ counts.shown }}</span> of
@@ -236,18 +232,14 @@ onMounted(() => {
</div>
<div class="mt-auto flex gap-2">
<ButtonStyled color="brand">
<button @click="openPreview(id, $event)">
<PlayIcon aria-hidden="true" />
Preview
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="openPreview(id, $event)">
<PlayIcon aria-hidden="true" />
Preview
</Button>
<ButtonStyled circular type="outlined">
<button title="Copy preview URL" @click="copy(id)">
<CopyIcon aria-hidden="true" />
</button>
</ButtonStyled>
<IconButton type="outlined" label="Copy" title="Copy preview URL" @click="copy(id)">
<CopyIcon aria-hidden="true" />
</IconButton>
</div>
</li>
</ul>
@@ -100,22 +100,24 @@
/>
</div>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button v-if="editingNotice" :disabled="!!noticeSubmitError" @click="() => saveChanges()">
<SaveIcon aria-hidden="true" />
{{ formatMessage(commonMessages.saveChangesButton) }}
</button>
<button v-else :disabled="!!noticeSubmitError" @click="() => createNotice()">
<PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createNotice) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="createNoticeModal?.hide">
<XIcon aria-hidden="true" />
Cancel
</button>
</ButtonStyled>
<Button
v-if="editingNotice"
type="colored"
color="brand"
:disabled="!!noticeSubmitError"
@click="() => saveChanges()"
>
<SaveIcon aria-hidden="true" />
{{ formatMessage(commonMessages.saveChangesButton) }}
</Button>
<Button v-else :disabled="!!noticeSubmitError" @click="() => createNotice()">
<PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createNotice) }}
</Button>
<Button @click="createNoticeModal?.hide">
<XIcon aria-hidden="true" />
Cancel
</Button>
</div>
</div>
</NewModal>
@@ -125,12 +127,10 @@
class="mb-6 flex items-end justify-between border-0 border-b border-solid border-divider pb-4"
>
<h1 class="m-0 text-2xl">Server notices</h1>
<ButtonStyled color="brand">
<button @click="openNewNoticeModal">
<PlusIcon />
{{ formatMessage(messages.createNotice) }}
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="openNewNoticeModal">
<PlusIcon />
{{ formatMessage(messages.createNotice) }}
</Button>
</div>
<div>
<div v-if="!notices || notices.length === 0">
@@ -201,16 +201,12 @@
</TagItem>
</div>
<div class="col-span-2 flex gap-2 md:col-span-1">
<ButtonStyled>
<button @click="() => startEditing(notice)">
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button @click="() => deleteNotice(notice)">
<TrashIcon /> {{ formatMessage(commonMessages.deleteLabel) }}
</button>
</ButtonStyled>
<Button @click="() => startEditing(notice)">
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
</Button>
<Button type="colored" color="red" @click="() => deleteNotice(notice)">
<TrashIcon /> {{ formatMessage(commonMessages.deleteLabel) }}
</Button>
</div>
</div>
<div class="col-span-full grid">
@@ -263,7 +259,7 @@
import type { Archon } from '@modrinth/api-client'
import { EditIcon, PlusIcon, SaveIcon, SettingsIcon, TrashIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
Combobox,
commonMessages,
CopyCode,
@@ -13,12 +13,10 @@
class="mb-6 flex items-end justify-between border-0 border-b border-solid border-divider pb-4"
>
<h1 class="m-0 text-2xl">Server transfers</h1>
<ButtonStyled color="brand">
<button @click="openTransferModal">
<PlusIcon />
New transfer
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="openTransferModal">
<PlusIcon />
New transfer
</Button>
</div>
<div>
<div v-if="loading" class="py-8 text-center text-secondary">Loading transfers...</div>
@@ -67,12 +65,16 @@
<span class="text-sm text-secondary">
{{ batch.log_count }} transfer{{ batch.log_count === 1 ? '' : 's' }}
</span>
<ButtonStyled v-if="canCancel(batch)" color="red" color-fill="text">
<button @click="showCancelModal(batch.id)">
<XCircleIcon />
Cancel
</button>
</ButtonStyled>
<Button
v-if="canCancel(batch)"
type="quiet"
color="red"
class="!text-red [&>svg]:!text-red"
@click="showCancelModal(batch.id)"
>
<XCircleIcon />
Cancel
</Button>
</div>
</div>
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-secondary">
@@ -112,7 +114,7 @@ import type { Archon } from '@modrinth/api-client'
import { PlusIcon, XCircleIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
ConfirmModal,
injectModrinthClient,
injectNotificationManager,
+5 -7
View File
@@ -20,12 +20,10 @@
/>
</div>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button @click="getUserFromEmail">
<MailIcon aria-hidden="true" />
Get user account
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="getUserFromEmail">
<MailIcon aria-hidden="true" />
Get user account
</Button>
</div>
</div>
</div>
@@ -33,7 +31,7 @@
</template>
<script setup lang="ts">
import { MailIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager, StyledInput } from '@modrinth/ui'
import { Button, injectNotificationManager, StyledInput } from '@modrinth/ui'
const { addNotification } = injectNotificationManager()