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
@@ -3,9 +3,11 @@ import { PlusIcon, XIcon } from '@modrinth/assets'
import type Stripe from 'stripe'
import { nextTick, ref, useTemplateRef } from 'vue'
import { Button } from '#ui/components/base/buttons'
import { defineMessages, useVIntl } from '../../composables/i18n'
import { commonMessages } from '../../utils'
import { ButtonStyled, NewModal } from '../index'
import { NewModal } from '../index'
import type { AddPaymentMethodProps } from './AddPaymentMethod.vue'
import AddPaymentMethod from './AddPaymentMethod.vue'
@@ -57,18 +59,14 @@ defineExpose({
@stop-loading="loading = false"
/>
<div class="input-group mt-auto pt-4">
<ButtonStyled color="brand">
<button :disabled="loading" @click="addPaymentMethod.submit()">
<PlusIcon />
{{ formatMessage(messages.paymentMethodAdd) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="modal.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="loading" @click="addPaymentMethod.submit()">
<PlusIcon />
{{ formatMessage(messages.paymentMethodAdd) }}
</Button>
<Button @click="modal.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
</div>
</div>
</NewModal>
@@ -12,12 +12,12 @@ import { useQueryClient } from '@tanstack/vue-query'
import type Stripe from 'stripe'
import { computed, nextTick, onBeforeUnmount, ref, toRef, useTemplateRef, watch } from 'vue'
import { Button } from '#ui/components/base/buttons'
import { injectNotificationManager } from '#ui/providers/web-notifications.ts'
import { defineMessage, type MessageDescriptor, useVIntl } from '../../composables/i18n'
import { useStripe } from '../../composables/stripe'
import { commonMessages } from '../../utils'
import { ButtonStyled } from '../index'
import ModalLoadingIndicator from '../modal/ModalLoadingIndicator.vue'
import NewModal from '../modal/NewModal.vue'
import PlanSelector from './ServersPurchase0Plan.vue'
@@ -555,51 +555,50 @@ function goToBreadcrumbStep(id: string) {
</div>
</div>
<div class="flex gap-2 justify-between mt-4">
<ButtonStyled>
<button v-if="previousStep" @click="previousStep && setStep(previousStep, true)">
<LeftArrowIcon /> {{ formatMessage(commonMessages.backButton) }}
</button>
<button v-else-if="currentStep !== 'plan'" @click="modal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled v-if="currentStep !== 'plan'" color="brand">
<button
v-tooltip="
currentStep === 'review' && !acceptedEula && !noPaymentRequired
? 'You must accept the Minecraft EULA to proceed.'
: undefined
"
:disabled="!canProceed"
@click="
noPaymentRequired && currentStep === 'review'
? (async () => {
if (props.onFinalizeNoPaymentChange) {
try {
await props.onFinalizeNoPaymentChange()
} catch (e) {
return
}
<Button v-if="previousStep" @click="previousStep && setStep(previousStep, true)">
<LeftArrowIcon /> {{ formatMessage(commonMessages.backButton) }}
</Button>
<Button v-else-if="currentStep !== 'plan'" @click="modal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button
v-if="currentStep !== 'plan'"
v-tooltip="
currentStep === 'review' && !acceptedEula && !noPaymentRequired
? 'You must accept the Minecraft EULA to proceed.'
: undefined
"
type="colored"
color="brand"
:disabled="!canProceed"
@click="
noPaymentRequired && currentStep === 'review'
? (async () => {
if (props.onFinalizeNoPaymentChange) {
try {
await props.onFinalizeNoPaymentChange()
} catch (e) {
return
}
modal?.hide()
})()
: setStep(nextStep)
"
>
<template v-if="currentStep === 'review'">
<template v-if="noPaymentRequired"><CheckCircleIcon /> Confirm Change</template>
<template v-else>
<SpinnerIcon v-if="completingPurchase" class="animate-spin" />
<CheckCircleIcon v-else />
Subscribe
</template>
</template>
}
modal?.hide()
})()
: setStep(nextStep)
"
>
<template v-if="currentStep === 'review'">
<template v-if="noPaymentRequired"><CheckCircleIcon /> Confirm Change</template>
<template v-else>
{{ formatMessage(commonMessages.nextButton) }} <RightArrowIcon />
<SpinnerIcon v-if="completingPurchase" class="animate-spin" />
<CheckCircleIcon v-else />
Subscribe
</template>
</button>
</ButtonStyled>
</template>
<template v-else>
{{ formatMessage(commonMessages.nextButton) }} <RightArrowIcon />
</template>
</Button>
</div>
</NewModal>
</template>
@@ -78,18 +78,14 @@
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled type="outlined">
<button @click="handleCancel">
<XIcon />
{{ formatMessage(messages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!canResubscribe" @click="handleResubscribe">
<RotateCounterClockwiseIcon />
{{ formatMessage(messages.resubscribeButton) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="handleCancel">
<XIcon />
{{ formatMessage(messages.cancelButton) }}
</Button>
<Button type="colored" color="brand" :disabled="!canResubscribe" @click="handleResubscribe">
<RotateCounterClockwiseIcon />
{{ formatMessage(messages.resubscribeButton) }}
</Button>
</div>
</template>
</NewModal>
@@ -100,12 +96,13 @@ import type { Labrinth } from '@modrinth/api-client'
import { RotateCounterClockwiseIcon, XIcon } from '@modrinth/assets'
import { computed, ref, useTemplateRef } from 'vue'
import { Button } from '#ui/components/base/buttons'
import { injectNotificationManager } from '#ui/providers/web-notifications.ts'
import { useFormatDateTime, useFormatPrice } from '../../composables'
import { defineMessages, useVIntl } from '../../composables/i18n'
import IntlFormatted from '../base/IntlFormatted.vue'
import { ButtonStyled, NewModal } from '../index'
import { NewModal } from '../index'
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -3,7 +3,8 @@ import type { Labrinth } from '@modrinth/api-client'
import { ChevronRightIcon, ExternalIcon, XIcon } from '@modrinth/assets'
import { computed, ref, useTemplateRef } from 'vue'
import ButtonStyled from '../base/ButtonStyled.vue'
import { Button, IconButton } from '#ui/components/base/buttons'
import NewModal from '../modal/NewModal.vue'
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
import PlanSelector from './ServersPurchase0Plan.vue'
@@ -143,11 +144,9 @@ defineExpose({
class="absolute inset-x-0 bottom-0 -m-px z-30 rounded-2xl border border-solid border-surface-5 bg-bg-raised p-6 shadow-2xl"
>
<div class="absolute right-4 top-4">
<ButtonStyled circular type="transparent">
<button aria-label="Close sign in prompt" @click="closeSignInPrompt">
<XIcon />
</button>
</ButtonStyled>
<IconButton type="quiet" label="Close sign in prompt" @click="closeSignInPrompt">
<XIcon />
</IconButton>
</div>
<div class="mx-auto flex max-w-xl flex-col items-center gap-4 text-center">
@@ -155,12 +154,10 @@ defineExpose({
<div class="font-semibold text-contrast">Sign in to continue your purchase</div>
<div class="">You need a Modrinth account to add your billing details.</div>
</div>
<ButtonStyled color="brand">
<button class="mt-2" @click="continueToAuth">
Sign in or create an account
<ExternalIcon class="size-4" />
</button>
</ButtonStyled>
<Button type="colored" color="brand" class="mt-2" @click="continueToAuth">
Sign in or create an account
<ExternalIcon class="size-4" />
</Button>
</div>
</div>
</Transition>
@@ -3,10 +3,11 @@ import type { Labrinth } from '@modrinth/api-client'
import { RightArrowIcon } from '@modrinth/assets'
import { computed } from 'vue'
import { Button } from '#ui/components/base/buttons'
import { useFormatPrice } from '../../composables'
import { defineMessages, useVIntl } from '../../composables/i18n'
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
import ButtonStyled from '../base/ButtonStyled.vue'
import OptionGroup from '../base/OptionGroup.vue'
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
import ServersSpecs from './ServersSpecs.vue'
@@ -218,19 +219,19 @@ function selectCustom() {
</div>
</div>
<div class="w-full">
<ButtonStyled color="blue">
<button
class="w-full"
:disabled="existingPlan?.id === plansByRam.small.id"
@click="selectPlan(plansByRam.small!)"
>
{{
existingPlan?.id === plansByRam.small.id
? formatMessage(messages.yourCurrentPlan)
: formatMessage(messages.selectPlan)
}}
</button>
</ButtonStyled>
<Button
type="colored"
color="blue"
class="w-full"
:disabled="existingPlan?.id === plansByRam.small.id"
@click="selectPlan(plansByRam.small!)"
>
{{
existingPlan?.id === plansByRam.small.id
? formatMessage(messages.yourCurrentPlan)
: formatMessage(messages.selectPlan)
}}
</Button>
</div>
<ServersSpecs
v-if="smallSpecs"
@@ -281,19 +282,19 @@ function selectCustom() {
</div>
</div>
<div class="w-full">
<ButtonStyled color="brand" class="w-full">
<button
class="w-full"
:disabled="existingPlan?.id === plansByRam.medium.id"
@click="selectPlan(plansByRam.medium!)"
>
{{
existingPlan?.id === plansByRam.medium.id
? formatMessage(messages.yourCurrentPlan)
: formatMessage(messages.selectPlan)
}}
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
class="w-full"
:disabled="existingPlan?.id === plansByRam.medium.id"
@click="selectPlan(plansByRam.medium!)"
>
{{
existingPlan?.id === plansByRam.medium.id
? formatMessage(messages.yourCurrentPlan)
: formatMessage(messages.selectPlan)
}}
</Button>
</div>
<ServersSpecs
v-if="mediumSpecs"
@@ -329,19 +330,19 @@ function selectCustom() {
</div>
</div>
<div class="w-full">
<ButtonStyled color="purple" class="w-full">
<button
class="w-full"
:disabled="existingPlan?.id === plansByRam.large.id"
@click="selectPlan(plansByRam.large!)"
>
{{
existingPlan?.id === plansByRam.large.id
? formatMessage(messages.yourCurrentPlan)
: formatMessage(messages.selectPlan)
}}
</button>
</ButtonStyled>
<Button
type="colored"
color="purple"
class="w-full"
:disabled="existingPlan?.id === plansByRam.large.id"
@click="selectPlan(plansByRam.large!)"
>
{{
existingPlan?.id === plansByRam.large.id
? formatMessage(messages.yourCurrentPlan)
: formatMessage(messages.selectPlan)
}}
</Button>
</div>
<ServersSpecs
v-if="largeSpecs"
@@ -366,11 +367,9 @@ function selectCustom() {
</div>
</div>
<div class="flex flex-col items-end gap-2 shrink-0">
<ButtonStyled>
<button class="flex items-center gap-2" @click="selectCustom">
{{ formatMessage(messages.getStarted) }} <RightArrowIcon class="h-4 w-4" />
</button>
</ButtonStyled>
<Button class="flex items-center gap-2" @click="selectCustom">
{{ formatMessage(messages.getStarted) }} <RightArrowIcon class="h-4 w-4" />
</Button>
<div class="text-sm text-secondary whitespace-nowrap">
Starting at {{ formatPrice(customStartingPrice, currency, true) }}/mo
</div>
@@ -15,11 +15,12 @@ import dayjs from 'dayjs'
import type Stripe from 'stripe'
import { computed } from 'vue'
import { Button } from '#ui/components/base/buttons'
import { useFormatPrice } from '../../composables'
import { useVIntl } from '../../composables/i18n'
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
import { regionOverrides } from '../../utils/regions'
import ButtonStyled from '../base/ButtonStyled.vue'
import Checkbox from '../base/Checkbox.vue'
import TagItem from '../base/TagItem.vue'
import ModrinthServersIcon from '../servers/ModrinthServersIcon.vue'
@@ -323,12 +324,10 @@ function setInterval(newInterval: ServerBillingInterval) {
No payment method selected
</div>
</template>
<ButtonStyled size="small" type="transparent">
<button class="ml-auto" @click="emit('changePaymentMethod')">
<template v-if="selectedPaymentMethod || hasPaymentMethod"> <EditIcon /> Change </template>
<template v-else> Select payment method <RightArrowIcon /> </template>
</button>
</ButtonStyled>
<Button type="quiet" size="xs" class="ml-auto !h-6" @click="emit('changePaymentMethod')">
<template v-if="selectedPaymentMethod || hasPaymentMethod"> <EditIcon /> Change </template>
<template v-else> Select payment method <RightArrowIcon /> </template>
</Button>
</div>
<p v-if="!noPaymentRequired" class="m-0 mt-4 text-sm text-secondary">
<template v-if="isUpgrade && (total ?? 0) > 0">