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
+57 -49
View File
@@ -29,24 +29,25 @@ import {
import {
Admonition,
Avatar,
ButtonStyled,
ButtonLink,
commonMessages,
ContentInstallModal,
ContentUpdaterModal,
CreationFlowModal,
defineMessages,
I18nDebugPanel,
IconButton,
IntlFormatted,
LoadingBar,
NewsArticleCard,
NotificationPanel,
OverflowMenu,
PopupNotificationPanel,
provideModalBehavior,
provideModrinthClient,
provideNotificationManager,
providePageContext,
providePopupNotificationManager,
TeleportOverflowMenu,
TextLogo,
useDebugLogger,
useFormatBytes,
@@ -1592,22 +1593,29 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
>
<SettingsIcon />
</NavButton>
<OverflowMenu
<TeleportOverflowMenu
v-if="credentials?.user"
v-tooltip.right="formatMessage(messages.modrinthAccount)"
class="w-12 h-12 text-primary rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast border-0 cursor-pointer"
type="quiet"
size="xl"
label="More options"
:options="[
{
id: 'view-profile',
label: formatMessage(messages.signedInAs, {
username: credentials.user.username,
}),
action: () => router.push(`/user/${encodeURIComponent(credentials.user.username)}`),
},
{
id: 'sign-out',
label: formatMessage(commonMessages.signOutButton),
tone: 'red',
action: () => logOut(),
color: 'danger',
},
]"
placement="right-end"
:distance="4"
>
<Avatar :src="credentials?.user?.avatar_url" alt="" size="32px" circle />
<template #view-profile>
@@ -1630,7 +1638,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<LogOutIcon />
{{ formatMessage(commonMessages.signOutButton) }}
</template>
</OverflowMenu>
</TeleportOverflowMenu>
<NavButton
v-else
v-tooltip.right="formatMessage(messages.signInToModrinthAccount)"
@@ -1643,49 +1651,44 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<div data-tauri-drag-region class="flex min-w-0 flex-1 items-center overflow-hidden p-2">
<TextLogo class="h-7 w-auto shrink-0 text-contrast pointer-events-none" />
<div data-tauri-drag-region class="ml-2 flex shrink-0 items-center gap-2">
<ButtonStyled type="outlined" circular>
<button
class="!h-7 !min-w-7 !w-7 !border !border-surface-4 !p-0 !opacity-100"
:disabled="!canNavigateBack"
aria-label="Go back"
@click="router.back()"
>
<ChevronLeftIcon
class="!size-4 !text-primary"
:class="{ 'opacity-20': !canNavigateBack }"
/>
</button>
</ButtonStyled>
<ButtonStyled type="outlined" circular>
<button
class="!h-7 !min-w-7 !w-7 !border !border-surface-4 !p-0 !opacity-100"
:disabled="!canNavigateForward"
aria-label="Go forward"
@click="router.forward()"
>
<ChevronRightIcon
class="!size-4 !text-primary"
:class="{ 'opacity-20': !canNavigateForward }"
/>
</button>
</ButtonStyled>
<IconButton
type="outlined"
label="Go back"
class="!h-7 !min-w-7 !w-7 !border !border-surface-4 !p-0 !opacity-100"
:disabled="!canNavigateBack"
@click="router.back()"
>
<ChevronLeftIcon
class="!size-4 !text-primary"
:class="{ 'opacity-20': !canNavigateBack }"
/>
</IconButton>
<IconButton
type="outlined"
label="Go forward"
class="!h-7 !min-w-7 !w-7 !border !border-surface-4 !p-0 !opacity-100"
:disabled="!canNavigateForward"
@click="router.forward()"
>
<ChevronRightIcon
class="!size-4 !text-primary"
:class="{ 'opacity-20': !canNavigateForward }"
/>
</IconButton>
</div>
<Breadcrumbs />
</div>
<section data-tauri-drag-region class="flex shrink-0 ml-auto items-center">
<ButtonStyled
<IconButton
v-if="!forceSidebar && themeStore.toggleSidebar"
:type="sidebarToggled ? 'standard' : 'transparent'"
circular
:type="sidebarToggled ? 'base' : 'quiet'"
label="Next image"
class="mr-3 transition-transform"
:class="{ 'rotate-180': !sidebarToggled }"
@click="sidebarToggled = !sidebarToggled"
>
<button
class="mr-3 transition-transform"
:class="{ 'rotate-180': !sidebarToggled }"
@click="sidebarToggled = !sidebarToggled"
>
<RightArrowIcon />
</button>
</ButtonStyled>
<RightArrowIcon />
</IconButton>
<div class="flex mr-3">
<Suspense>
<AppActionBar />
@@ -1794,12 +1797,17 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
:key="`news-${index}`"
:article="item"
/>
<ButtonStyled color="brand" size="large">
<a href="https://modrinth.com/news" target="_blank" class="my-4">
<NewspaperIcon />
{{ formatMessage(messages.viewAllNews) }}
</a>
</ButtonStyled>
<ButtonLink
type="colored"
color="brand"
size="xl"
href="https://modrinth.com/news"
target="_blank"
class="my-4"
>
<NewspaperIcon />
{{ formatMessage(messages.viewAllNews) }}
</ButtonLink>
</div>
</div>
</div>
@@ -4,13 +4,11 @@
class="flex flex-col gap-3 bg-button-bg border border-solid border-surface-5 rounded-xl p-3 mt-2"
>
<span>{{ formatMessage(messages.notSignedIn) }}</span>
<ButtonStyled color="brand">
<button color="primary" :disabled="loginDisabled" @click="login()">
<LogInIcon v-if="!loginDisabled" />
<SpinnerIcon v-else class="animate-spin" />
{{ formatMessage(messages.signInToMinecraft) }}
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="loginDisabled" @click="login()">
<LogInIcon v-if="!loginDisabled" />
<SpinnerIcon v-else class="animate-spin" />
{{ formatMessage(messages.signInToMinecraft) }}
</Button>
</div>
<Accordion
v-else
@@ -60,24 +58,28 @@
{{ account.profile.name }}
</p>
</button>
<ButtonStyled circular color="red" color-fill="none" hover-color-fill="background">
<button
v-tooltip="formatMessage(messages.removeAccount)"
class="mr-2"
@click="logout(account.profile.id)"
>
<TrashIcon />
</button>
</ButtonStyled>
<IconButton
v-tooltip="formatMessage(messages.removeAccount)"
type="quiet"
color="red"
:label="formatMessage(messages.removeAccount)"
class="mr-2 !bg-button-bg !text-primary ![box-shadow:var(--shadow-button)] hover:!bg-red focus-visible:!bg-red hover:!text-[var(--color-accent-contrast)] focus-visible:!text-[var(--color-accent-contrast)]"
@click="logout(account.profile.id)"
>
<TrashIcon />
</IconButton>
</div>
</template>
<div class="flex flex-col gap-2 px-2 pt-2">
<ButtonStyled v-if="accounts.length > 0" class="w-full">
<button :disabled="loginDisabled" @click="login()">
<PlusIcon />
{{ formatMessage(messages.addAccount) }}
</button>
</ButtonStyled>
<Button
v-if="accounts.length > 0"
class="w-full !bg-button-bg !text-primary ![box-shadow:var(--shadow-button)]"
:disabled="loginDisabled"
@click="login()"
>
<PlusIcon />
{{ formatMessage(messages.addAccount) }}
</Button>
</div>
</div>
</Accordion>
@@ -95,8 +97,9 @@ import {
import {
Accordion,
Avatar,
ButtonStyled,
Button,
defineMessages,
IconButton,
injectNotificationManager,
useVIntl,
} from '@modrinth/ui'
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { DropdownIcon, FolderOpenIcon, PlusIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager, OverflowMenu } from '@modrinth/ui'
import { Button, injectNotificationManager, TeleportOverflowMenu } from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog'
import { useRouter } from 'vue-router'
@@ -36,27 +36,26 @@ const handleSearchContent = async () => {
<template>
<div class="joined-buttons">
<ButtonStyled>
<button @click="handleSearchContent">
<PlusIcon />
Install content
</button>
</ButtonStyled>
<ButtonStyled>
<OverflowMenu
:options="[
{
id: 'from_file',
action: handleAddContentFromFile,
},
]"
>
<DropdownIcon />
<template #from_file>
<FolderOpenIcon />
<span class="no-wrap"> Add from file </span>
</template>
</OverflowMenu>
</ButtonStyled>
<Button @click="handleSearchContent">
<PlusIcon />
Install content
</Button>
<TeleportOverflowMenu
label="More options"
:options="[
{
id: 'from_file',
label: 'Add from file',
action: handleAddContentFromFile,
},
]"
class="!w-auto !px-2.5 !rounded-xl"
>
<DropdownIcon />
<template #from_file>
<FolderOpenIcon />
<span class="no-wrap"> Add from file </span>
</template>
</TeleportOverflowMenu>
</div>
</template>
@@ -1,15 +1,15 @@
<template>
<div class="flex gap-2 items-center">
<ButtonStyled
<IconButton
v-if="hasActiveLoadingBars && !hasVisibleActiveDownloadToasts"
v-tooltip="formatMessage(messages.viewActiveDownloads)"
type="quiet"
color="brand"
type="transparent"
circular
:label="formatMessage(messages.viewActiveDownloads)"
@click="openDownloadToast()"
>
<button v-tooltip="formatMessage(messages.viewActiveDownloads)" @click="openDownloadToast()">
<DownloadIcon />
</button>
</ButtonStyled>
<DownloadIcon />
</IconButton>
<div v-if="offline" class="flex items-center gap-1">
<UnplugIcon class="text-secondary" />
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
@@ -36,17 +36,23 @@
@show="showInstances = true"
@hide="showInstances = false"
>
<ButtonStyled type="transparent" circular size="small">
<button
v-tooltip="
showInstances
? formatMessage(messages.hideMoreRunningInstances)
: formatMessage(messages.showMoreRunningInstances)
"
>
<DropdownIcon :class="{ 'rotate-180': !!showInstances }" />
</button>
</ButtonStyled>
<IconButton
v-tooltip="
showInstances
? formatMessage(messages.hideMoreRunningInstances)
: formatMessage(messages.showMoreRunningInstances)
"
class="!size-6"
type="quiet"
size="xs"
:label="
showInstances
? formatMessage(messages.hideMoreRunningInstances)
: formatMessage(messages.showMoreRunningInstances)
"
>
<DropdownIcon :class="{ 'rotate-180': !!showInstances }" />
</IconButton>
<template #popper>
<div class="flex w-[20rem] max-h-[24rem] flex-col gap-2 overflow-auto">
<div
@@ -125,8 +131,8 @@ import {
TerminalSquareIcon,
UnplugIcon,
} from '@modrinth/assets'
import { IconButton } from '@modrinth/ui'
import {
ButtonStyled,
defineMessages,
injectNotificationManager,
injectPopupNotificationManager,
@@ -9,7 +9,13 @@ import {
WrenchIcon,
XIcon,
} from '@modrinth/assets'
import { ButtonStyled, Collapsible, injectNotificationManager } from '@modrinth/ui'
import {
Button,
ButtonLink,
Collapsible,
IconButton,
injectNotificationManager,
} from '@modrinth/ui'
import { computed, ref } from 'vue'
import { ChatIcon } from '@/assets/icons'
@@ -273,12 +279,10 @@ async function copyToClipboard(text) {
</template>
</div>
<div class="flex items-center gap-2">
<ButtonStyled>
<a :href="supportLink" @click="errorModal.hide()"><ChatIcon /> Get support</a>
</ButtonStyled>
<ButtonStyled v-if="closable">
<button @click="errorModal.hide()"><XIcon /> Close</button>
</ButtonStyled>
<ButtonLink :href="supportLink" @click="errorModal.hide()"
><ChatIcon /> Get support</ButtonLink
>
<Button v-if="closable" @click="errorModal.hide()"><XIcon /> Close</Button>
</div>
<template v-if="hasDebugInfo">
<div class="flex flex-col gap-2">
@@ -307,16 +311,15 @@ async function copyToClipboard(text) {
>
{{ debugInfo }}
</div>
<ButtonStyled circular>
<button
v-tooltip="'Copy debug info'"
:disabled="copied"
@click="copyToClipboard(debugInfo)"
>
<template v-if="copied"> <CheckIcon class="text-green" /> </template>
<template v-else> <CopyIcon /> </template>
</button>
</ButtonStyled>
<IconButton
v-tooltip="'Copy debug info'"
:label="'Copy debug info'"
:disabled="copied"
@click="copyToClipboard(debugInfo)"
>
<template v-if="copied"> <CheckIcon class="text-green" /> </template>
<template v-else> <CopyIcon /> </template>
</IconButton>
</div>
</Collapsible>
</div>
@@ -1,7 +1,7 @@
<script setup>
import { XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
commonMessages,
defineMessages,
FileTreeSelect,
@@ -208,18 +208,14 @@ function normalizeExportPath(path) {
</div>
<template #actions>
<div class="flex items-center justify-end gap-2">
<ButtonStyled type="outlined">
<button @click="exportModal.hide">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="exportPack">
<PackageIcon />
{{ formatMessage(messages.exportButton) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="exportModal.hide">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button type="colored" color="brand" @click="exportPack">
<PackageIcon />
{{ formatMessage(messages.exportButton) }}
</Button>
</div>
</template>
</NewModal>
@@ -7,7 +7,7 @@ import {
StopCircleIcon,
TimerIcon,
} from '@modrinth/assets'
import { Avatar, ButtonStyled, injectNotificationManager, useRelativeTime } from '@modrinth/ui'
import { Avatar, IconButton, injectNotificationManager, useRelativeTime } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import { computed, onMounted, onUnmounted, ref } from 'vue'
@@ -168,30 +168,37 @@ onUnmounted(() => unlisten())
<span class="line-clamp-2">{{ instance.name }}</span>
</div>
<div class="flex items-center">
<ButtonStyled v-if="playing" color="red" circular @mousehover="checkProcess">
<button v-tooltip="'Stop'" @click="(e) => stop(e, 'InstanceCard')">
<StopCircleIcon />
</button>
</ButtonStyled>
<ButtonStyled v-else-if="modLoading" color="standard" circular>
<button v-tooltip="'Instance is loading...'" disabled>
<SpinnerIcon class="animate-spin" />
</button>
</ButtonStyled>
<ButtonStyled
v-else-if="!instance.quarantined"
:color="first ? 'brand' : 'standard'"
circular
<IconButton
v-if="playing"
v-tooltip="'Stop'"
type="colored"
color="red"
:label="'Stop'"
@mouseenter="checkProcess"
@click="(e) => stop(e, 'InstanceCard')"
>
<button
v-tooltip="'Play'"
@click="(e) => play(e, 'InstanceCard')"
@mousehover="checkProcess"
>
<!-- Translate for optical centering -->
<PlayIcon class="translate-x-[1px]" />
</button>
</ButtonStyled>
<StopCircleIcon />
</IconButton>
<IconButton
v-else-if="modLoading"
v-tooltip="'Instance is loading...'"
:label="'Instance is loading...'"
disabled
>
<SpinnerIcon class="animate-spin" />
</IconButton>
<IconButton
v-else-if="!instance.quarantined"
v-tooltip="'Play'"
:type="first ? 'colored' : 'base'"
:color="first ? 'brand' : undefined"
label="Play"
@click="(e) => play(e, 'InstanceCard')"
@mouseenter="checkProcess"
>
<!-- Translate for optical centering -->
<PlayIcon class="translate-x-[1px]" />
</IconButton>
</div>
<div class="flex items-center col-span-3 gap-1 text-secondary font-semibold">
<TimerIcon />
@@ -219,47 +226,51 @@ onUnmounted(() => unlisten())
:class="`transition-all ${modLoading || installing ? `brightness-[0.25] scale-[0.85]` : `group-hover:brightness-75`}`"
/>
<div class="absolute inset-0 flex items-center justify-center">
<ButtonStyled v-if="playing" size="large" color="red" circular>
<button
v-tooltip="'Stop'"
:class="{ 'scale-100 opacity-100': playing }"
class="transition-all scale-75 origin-bottom opacity-0 card-shadow"
@click="(e) => stop(e, 'InstanceCard')"
@mousehover="checkProcess"
>
<StopCircleIcon />
</button>
</ButtonStyled>
<IconButton
v-if="playing"
v-tooltip="'Stop'"
type="colored"
color="red"
size="xl"
:label="'Stop'"
:class="{ 'scale-100 opacity-100': playing }"
class="transition-all scale-75 origin-bottom opacity-0 card-shadow"
@click="(e) => stop(e, 'InstanceCard')"
@mouseenter="checkProcess"
>
<StopCircleIcon />
</IconButton>
<SpinnerIcon
v-else-if="modLoading || installing"
v-tooltip="modLoading ? 'Instance is loading...' : 'Installing...'"
class="animate-spin w-8 h-8"
tabindex="-1"
/>
<ButtonStyled
<IconButton
v-else-if="!installed && !instance.quarantined"
size="large"
v-tooltip="'Repair'"
type="colored"
color="brand"
circular
size="xl"
:label="'Repair'"
class="transition-all scale-75 group-hover:scale-100 group-focus-within:scale-100 origin-bottom opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 card-shadow"
@click="(e) => repair(e)"
>
<button
v-tooltip="'Repair'"
class="transition-all scale-75 group-hover:scale-100 group-focus-within:scale-100 origin-bottom opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 card-shadow"
@click="(e) => repair(e)"
>
<DownloadIcon />
</button>
</ButtonStyled>
<ButtonStyled v-else-if="!instance.quarantined" size="large" color="brand" circular>
<button
v-tooltip="'Play'"
class="transition-all scale-75 group-hover:scale-100 group-focus-within:scale-100 origin-bottom opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 card-shadow"
@click="(e) => play(e, 'InstanceCard')"
@mousehover="checkProcess"
>
<PlayIcon class="translate-x-[2px]" />
</button>
</ButtonStyled>
<DownloadIcon />
</IconButton>
<IconButton
v-else-if="!instance.quarantined"
v-tooltip="'Play'"
type="colored"
color="brand"
size="xl"
:label="'Play'"
class="transition-all scale-75 group-hover:scale-100 group-focus-within:scale-100 origin-bottom opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 card-shadow"
@click="(e) => play(e, 'InstanceCard')"
@mouseenter="checkProcess"
>
<PlayIcon class="translate-x-[2px]" />
</IconButton>
</div>
</div>
<div class="flex flex-col gap-1">
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { GameIcon, LeftArrowIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, FormattedTag } from '@modrinth/ui'
import { Avatar, ButtonLink, FormattedTag } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { computed } from 'vue'
@@ -48,9 +48,7 @@ const instanceLink = computed(() => {
</span>
</span>
</router-link>
<ButtonStyled>
<router-link :to="instanceLink"> <LeftArrowIcon /> Back to instance </router-link>
</ButtonStyled>
<ButtonLink :to="instanceLink"> <LeftArrowIcon /> Back to instance </ButtonLink>
</div>
</template>
@@ -14,18 +14,14 @@
</template>
<template #cell-actions="{ row }">
<div class="flex items-center justify-end">
<ButtonStyled v-if="currentSelected.path === row.path">
<button class="!shadow-none" disabled>
<CheckIcon aria-hidden="true" />
{{ formatMessage(messages.selected) }}
</button>
</ButtonStyled>
<ButtonStyled v-else>
<button class="!shadow-none" @click="setJavaInstall(row)">
<PlusIcon aria-hidden="true" />
{{ formatMessage(messages.select) }}
</button>
</ButtonStyled>
<Button v-if="currentSelected.path === row.path" disabled>
<CheckIcon aria-hidden="true" />
{{ formatMessage(messages.selected) }}
</Button>
<Button v-else @click="setJavaInstall(row)">
<PlusIcon aria-hidden="true" />
{{ formatMessage(messages.select) }}
</Button>
</div>
</template>
<template #empty-state>
@@ -35,28 +31,21 @@
</template>
</Table>
<div class="flex justify-end">
<ButtonStyled type="outlined">
<button
class="!shadow-none !border-surface-4 !border"
@click="$refs.detectJavaModal.hide()"
>
<XIcon aria-hidden="true" />
{{ formatMessage(messages.cancel) }}
</button>
</ButtonStyled>
<Button
type="outlined"
class="!border-surface-4 !border"
@click="$refs.detectJavaModal.hide()"
>
<XIcon aria-hidden="true" />
{{ formatMessage(messages.cancel) }}
</Button>
</div>
</div>
</ModalWrapper>
</template>
<script setup>
import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
defineMessages,
injectNotificationManager,
Table,
useVIntl,
} from '@modrinth/ui'
import { Button, defineMessages, injectNotificationManager, Table, useVIntl } from '@modrinth/ui'
import { computed, ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
@@ -17,64 +17,73 @@
}
"
/>
<ButtonStyled
<Button
type="quiet"
:color="
!hoveringTest && !testingJava
? testingJavaSuccess === true
? 'green'
: 'red'
: 'standard'
: undefined
"
color-fill="text"
:aria-label="formatMessage(messages.testJavaInstallation)"
class="!text-[var(--legacy-button-color,var(--color-base))] [&>svg]:!text-[var(--legacy-button-color,var(--color-primary))]"
:disabled="testingJava || props.disabled"
:style="{
'--legacy-button-color':
(!hoveringTest && !testingJava
? testingJavaSuccess === true
? 'green'
: 'red'
: 'standard') &&
(!hoveringTest && !testingJava
? testingJavaSuccess === true
? 'green'
: 'red'
: 'standard') !== 'standard'
? `var(--color-${
!hoveringTest && !testingJava
? testingJavaSuccess === true
? 'green'
: 'red'
: 'standard'
})`
: undefined,
}"
@click="runTest(props.modelValue?.path)"
@mouseenter="!props.disabled && (hoveringTest = true)"
@mouseleave="hoveringTest = false"
>
<button
:aria-label="formatMessage(messages.testJavaInstallation)"
class="!shadow-none"
:disabled="testingJava || props.disabled"
@click="runTest(props.modelValue?.path)"
@mouseenter="!props.disabled && (hoveringTest = true)"
@mouseleave="hoveringTest = false"
>
<SpinnerIcon v-if="testingJava" class="animate-spin h-4 w-4" />
<CheckCircleIcon
v-else-if="testingJavaSuccess === true && !hoveringTest"
class="h-4 w-4"
/>
<XCircleIcon v-else-if="testingJavaSuccess !== true && !hoveringTest" class="h-4 w-4" />
<RefreshCwIcon v-else-if="!props.disabled" class="h-4 w-4" />
</button>
</ButtonStyled>
<SpinnerIcon v-if="testingJava" class="animate-spin h-4 w-4" />
<CheckCircleIcon v-else-if="testingJavaSuccess === true && !hoveringTest" class="h-4 w-4" />
<XCircleIcon v-else-if="testingJavaSuccess !== true && !hoveringTest" class="h-4 w-4" />
<RefreshCwIcon v-else-if="!props.disabled" class="h-4 w-4" />
</Button>
</div>
<span class="installation-buttons">
<ButtonStyled v-if="props.version">
<button
v-tooltip="
testingJavaSuccess === true ? formatMessage(messages.alreadyInstalled) : undefined
"
class="!shadow-none"
:disabled="props.disabled || installingJava || testingJavaSuccess === true"
@click="reinstallJava"
>
<DownloadIcon />
{{
installingJava
? formatMessage(messages.installing)
: formatMessage(messages.installRecommended)
}}
</button>
</ButtonStyled>
<ButtonStyled>
<button class="!shadow-none" :disabled="props.disabled" @click="autoDetect">
<SearchIcon />
{{ formatMessage(messages.detect) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button class="!shadow-none" :disabled="props.disabled" @click="handleJavaFileInput()">
<FolderSearchIcon />
{{ formatMessage(messages.browse) }}
</button>
</ButtonStyled>
<Button
v-if="props.version"
v-tooltip="
testingJavaSuccess === true ? formatMessage(messages.alreadyInstalled) : undefined
"
:disabled="props.disabled || installingJava || testingJavaSuccess === true"
@click="reinstallJava"
>
<DownloadIcon />
{{
installingJava
? formatMessage(messages.installing)
: formatMessage(messages.installRecommended)
}}
</Button>
<Button :disabled="props.disabled" @click="autoDetect">
<SearchIcon />
{{ formatMessage(messages.detect) }}
</Button>
<Button :disabled="props.disabled" @click="handleJavaFileInput()">
<FolderSearchIcon />
{{ formatMessage(messages.browse) }}
</Button>
</span>
</div>
</template>
@@ -90,7 +99,7 @@ import {
XCircleIcon,
} from '@modrinth/assets'
import {
ButtonStyled,
Button,
defineMessages,
injectNotificationManager,
StyledInput,
@@ -1,6 +1,6 @@
<script setup>
import { CheckIcon } from '@modrinth/assets'
import { Badge, ButtonStyled } from '@modrinth/ui'
import { Badge, IconButton } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { SwapIcon } from '@/assets/icons/index.js'
@@ -74,18 +74,16 @@ const onHide = () => {
@click="$router.push(`/project/${version.project_id}/version/${version.id}`)"
>
<div class="table-cell table-text">
<ButtonStyled
circular
:color="version.id === installedVersion ? 'standard' : 'brand'"
<IconButton
:type="version.id === installedVersion ? 'base' : 'colored'"
:color="version.id === installedVersion ? undefined : 'brand'"
label="Switch version"
:disabled="inProgress || installing || version.id === installedVersion"
@click.stop="() => switchVersion(version.id)"
>
<button
:disabled="inProgress || installing || version.id === installedVersion"
@click.stop="() => switchVersion(version.id)"
>
<SwapIcon v-if="version.id !== installedVersion" />
<CheckIcon v-else />
</button>
</ButtonStyled>
<SwapIcon v-if="version.id !== installedVersion" />
<CheckIcon v-else />
</IconButton>
</div>
<div class="name-cell table-cell table-text">
<div class="version-link">
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { NotepadTextIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
import { Button, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
import { type } from '@tauri-apps/plugin-os'
import { $fetch } from 'ofetch'
import { onMounted, ref } from 'vue'
@@ -193,18 +193,14 @@ onMounted(async () => {
{{ formatMessage(messages.surveyFooter) }}
</p>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button @click="openSurvey">
<NotepadTextIcon />
{{ formatMessage(messages.takeSurvey) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="dismissSurvey">
<XIcon />
{{ formatMessage(messages.surveyNoThanks) }}
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="openSurvey">
<NotepadTextIcon />
{{ formatMessage(messages.takeSurvey) }}
</Button>
<Button @click="dismissSurvey">
<XIcon />
{{ formatMessage(messages.surveyNoThanks) }}
</Button>
</div>
</div>
</transition>
@@ -1,5 +1,5 @@
<script setup>
import { ButtonStyled, injectNotificationManager, ProjectCard } from '@modrinth/ui'
import { Button, injectNotificationManager, ProjectCard } from '@modrinth/ui'
import { ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
@@ -69,9 +69,7 @@ async function install() {
</p>
</div>
<div class="button-group">
<ButtonStyled color="brand">
<button @click="install">Install</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="install">Install</Button>
</div>
</div>
</div>
@@ -4,34 +4,38 @@
class="flex items-center gap-2 mr-1.5"
data-tauri-drag-region-exclude
>
<ButtonStyled type="transparent" circular>
<button class="relative expanded-button" @click="() => getCurrentWindow().minimize()">
<MinimizeIcon />
</button>
</ButtonStyled>
<ButtonStyled type="transparent" circular>
<button class="relative expanded-button" @click="() => getCurrentWindow().toggleMaximize()">
<RestoreIcon v-if="isMaximized" />
<MaximizeIcon v-else />
</button>
</ButtonStyled>
<ButtonStyled
type="transparent"
color="red"
color-fill="none"
hover-color-fill="background"
circular
<IconButton
type="quiet"
label="Minimize window"
class="relative expanded-button"
@click="() => getCurrentWindow().minimize()"
>
<button class="relative expanded-button close-button" @click="handleClose">
<XIcon />
</button>
</ButtonStyled>
<MinimizeIcon />
</IconButton>
<IconButton
type="quiet"
label="Toggle maximize window"
class="relative expanded-button"
@click="() => getCurrentWindow().toggleMaximize()"
>
<RestoreIcon v-if="isMaximized" />
<MaximizeIcon v-else />
</IconButton>
<IconButton
type="quiet"
color="red"
label="Close window"
class="relative expanded-button close-button hover:!bg-red focus-visible:!bg-red"
@click="handleClose"
>
<XIcon />
</IconButton>
</section>
</template>
<script setup>
import { MaximizeIcon, MinimizeIcon, RestoreIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import { IconButton } from '@modrinth/ui'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { computed, onMounted, onUnmounted, ref } from 'vue'
@@ -1,31 +1,30 @@
<template>
<ButtonStyled color="brand" type="outlined" hover-color-fill="background">
<button
v-if="showUpdatePill"
type="button"
class="!h-[34px] text-sm !transition-[opacity,transform,background-color,color,filter] !duration-200 ease-out"
:class="{
'opacity-0 scale-[0.96]': finishedDownloading && !animateReadyPill,
'opacity-100 scale-100': finishedDownloading && animateReadyPill,
}"
:disabled="isUpdateDownloading"
:aria-busy="isUpdateDownloading"
@click="handleUpdateClick"
>
<RefreshCwIcon v-if="finishedDownloading" :class="{ 'animate-spin': restarting }" />
<DownloadIcon v-else />
<span v-if="isUpdateDownloading">
{{ formatMessage(messages.downloadingUpdate) }}
<span class="inline-block w-[3ch] text-right tabular-nums">{{ downloadPercent }}%</span>
</span>
<span v-else>{{ updateLabel }}</span>
</button>
</ButtonStyled>
<Button
v-if="showUpdatePill"
type="outlined"
native-type="button"
class="!h-[34px] text-sm !transition-[opacity,transform,background-color,color,filter] !duration-200 ease-out !text-brand [&>svg]:!text-brand !shadow-[inset_0_0_0_1px_var(--color-brand)] hover:!bg-brand focus-visible:!bg-brand hover:!text-[var(--color-accent-contrast)] focus-visible:!text-[var(--color-accent-contrast)]"
:class="{
'opacity-0 scale-[0.96]': finishedDownloading && !animateReadyPill,
'opacity-100 scale-100': finishedDownloading && animateReadyPill,
}"
:disabled="isUpdateDownloading"
:aria-busy="isUpdateDownloading"
@click="handleUpdateClick"
>
<RefreshCwIcon v-if="finishedDownloading" :class="{ 'animate-spin': restarting }" />
<DownloadIcon v-else />
<span v-if="isUpdateDownloading">
{{ formatMessage(messages.downloadingUpdate) }}
<span class="inline-block w-[3ch] text-right tabular-nums">{{ downloadPercent }}%</span>
</span>
<span v-else>{{ updateLabel }}</span>
</Button>
</template>
<script setup lang="ts">
import { DownloadIcon, RefreshCwIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
import { Button, defineMessages, useVIntl } from '@modrinth/ui'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import {
@@ -2,8 +2,9 @@
import { MailIcon, SearchIcon, SendIcon, UserIcon, UserPlusIcon, XIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
defineMessages,
IconButton,
injectNotificationManager,
IntlFormatted,
StyledInput,
@@ -220,26 +221,20 @@ const messages = defineMessages({
</div>
<div class="flex gap-2">
<template v-if="friend.id === userCredentials?.user_id">
<ButtonStyled color="brand">
<button @click="addFriend(friend)">
<UserPlusIcon />
Accept
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="removeFriend(friend)">
<XIcon />
Ignore
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="addFriend(friend)">
<UserPlusIcon />
Accept
</Button>
<Button @click="removeFriend(friend)">
<XIcon />
Ignore
</Button>
</template>
<template v-else>
<ButtonStyled>
<button @click="removeFriend(friend)">
<XIcon />
Cancel
</button>
</ButtonStyled>
<Button @click="removeFriend(friend)">
<XIcon />
Cancel
</Button>
</template>
</div>
</div>
@@ -263,26 +258,28 @@ const messages = defineMessages({
wrapper-class="flex-1"
@keyup.enter="addFriendFromModal"
/>
<ButtonStyled color="brand">
<button :disabled="username.length === 0" @click="addFriendFromModal">
<SendIcon />
{{ formatMessage(messages.sendFriendRequest) }}
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
:disabled="username.length === 0"
@click="addFriendFromModal"
>
<SendIcon />
{{ formatMessage(messages.sendFriendRequest) }}
</Button>
</div>
</div>
</ModalWrapper>
<div v-if="userCredentials && !loading" class="flex gap-1 items-center mb-3 -ml-1">
<template v-if="sortedFriends.length > 0">
<ButtonStyled circular type="transparent">
<button
v-tooltip="formatMessage(messages.addFriend)"
:aria-label="formatMessage(messages.addFriend)"
@click="addFriendModal.show"
>
<UserPlusIcon />
</button>
</ButtonStyled>
<IconButton
v-tooltip="formatMessage(messages.addFriend)"
type="quiet"
:label="formatMessage(messages.addFriend)"
@click="addFriendModal.show"
>
<UserPlusIcon />
</IconButton>
<StyledInput
v-model="search"
:icon="SearchIcon"
@@ -297,23 +294,23 @@ const messages = defineMessages({
<h3 v-else class="w-full text-base text-primary font-medium m-0">
{{ formatMessage(messages.friends) }}
</h3>
<ButtonStyled v-if="incomingRequests.length > 0" circular type="transparent">
<button
v-tooltip="formatMessage(messages.viewFriendRequests, { count: incomingRequests.length })"
class="relative"
:aria-label="formatMessage(messages.viewFriendRequests, { count: incomingRequests.length })"
@click="friendInvitesModal.show"
<IconButton
v-if="incomingRequests.length > 0"
v-tooltip="formatMessage(messages.viewFriendRequests, { count: incomingRequests.length })"
type="quiet"
:label="formatMessage(messages.viewFriendRequests, { count: incomingRequests.length })"
class="relative"
@click="friendInvitesModal.show"
>
<MailIcon />
<span
v-if="incomingRequests.length > 0"
aria-hidden="true"
class="absolute bg-brand text-brand-inverted text-[8px] top-0.5 px-1 right-0.5 min-w-3 h-3 rounded-full flex items-center justify-center font-bold"
>
<MailIcon />
<span
v-if="incomingRequests.length > 0"
aria-hidden="true"
class="absolute bg-brand text-brand-inverted text-[8px] top-0.5 px-1 right-0.5 min-w-3 h-3 rounded-full flex items-center justify-center font-bold"
>
{{ incomingRequests.length }}
</span>
</button>
</ButtonStyled>
{{ incomingRequests.length }}
</span>
</IconButton>
</div>
<div class="flex flex-col gap-3">
<h3 v-if="loading" class="text-base text-primary font-medium m-0">
@@ -3,9 +3,9 @@ import { MoreVerticalIcon, TrashIcon, UserIcon, XIcon } from '@modrinth/assets'
import {
Accordion,
Avatar,
ButtonStyled,
defineMessages,
OverflowMenu,
IconButton,
TeleportOverflowMenu,
useVIntl,
} from '@modrinth/ui'
import { useTemplateRef } from 'vue'
@@ -166,29 +166,35 @@ const messages = defineMessages({
<span v-else-if="friend.status" class="m-0 text-xs">{{ friend.status }}</span>
</div>
</RouterLink>
<ButtonStyled v-if="friend.accepted" circular type="transparent">
<OverflowMenu
class="opacity-0 group-hover:opacity-100 transition-opacity"
:options="[
{
id: 'remove-friend',
action: () => removeFriend(friend),
color: 'red',
},
]"
>
<MoreVerticalIcon />
<template #remove-friend>
<TrashIcon />
{{ formatMessage(messages.removeFriend) }}
</template>
</OverflowMenu>
</ButtonStyled>
<ButtonStyled v-else type="transparent" circular>
<button v-tooltip="formatMessage(messages.cancelRequest)" @click="removeFriend(friend)">
<XIcon />
</button>
</ButtonStyled>
<TeleportOverflowMenu
v-if="friend.accepted"
type="quiet"
label="More options"
class="opacity-0 group-hover:opacity-100 transition-opacity"
:options="[
{
id: 'remove-friend',
label: formatMessage(messages.removeFriend),
action: () => removeFriend(friend),
tone: 'red',
},
]"
>
<MoreVerticalIcon />
<template #remove-friend>
<TrashIcon />
{{ formatMessage(messages.removeFriend) }}
</template>
</TeleportOverflowMenu>
<IconButton
v-else
v-tooltip="formatMessage(messages.cancelRequest)"
type="quiet"
:label="formatMessage(messages.cancelRequest)"
@click="removeFriend(friend)"
>
<XIcon />
</IconButton>
</div>
</div>
</template>
@@ -1,12 +1,6 @@
<script setup>
import { CheckIcon, PlusIcon, SearchIcon } from '@modrinth/assets'
import {
Admonition,
Avatar,
ButtonStyled,
injectNotificationManager,
StyledInput,
} from '@modrinth/ui'
import { Admonition, Avatar, Button, injectNotificationManager, StyledInput } from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import { convertFileSrc } from '@tauri-apps/api/core'
import { computed, ref } from 'vue'
@@ -110,19 +104,15 @@ async function addServer(instance) {
/>
{{ instance.name }}
</router-link>
<ButtonStyled>
<button :disabled="instance.added || instance.adding" @click="addServer(instance)">
<PlusIcon v-if="!instance.added && !instance.adding" />
<CheckIcon v-else-if="instance.added" />
{{ instance.adding ? 'Adding...' : instance.added ? 'Added' : 'Add' }}
</button>
</ButtonStyled>
<Button :disabled="instance.added || instance.adding" @click="addServer(instance)">
<PlusIcon v-if="!instance.added && !instance.adding" />
<CheckIcon v-else-if="instance.added" />
{{ instance.adding ? 'Adding...' : instance.added ? 'Added' : 'Add' }}
</Button>
</div>
</div>
<div class="input-group push-right">
<ButtonStyled>
<button @click="modal.hide()">Cancel</button>
</ButtonStyled>
<Button @click="modal.hide()">Cancel</Button>
</div>
</div>
</ModalWrapper>
@@ -7,7 +7,7 @@ import {
MessagesSquareIcon,
WrenchIcon,
} from '@modrinth/assets'
import { Admonition, ButtonStyled, Collapsible, NewModal } from '@modrinth/ui'
import { Admonition, Button, ButtonLink, Collapsible, IconButton, NewModal } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
@@ -137,16 +137,18 @@ async function copyToClipboard(text: string) {
<!-- Action buttons -->
<div class="flex items-center gap-2">
<ButtonStyled>
<a href="https://support.modrinth.com" class="!w-full" @click="modal?.hide()">
<MessagesSquareIcon /> Contact support
</a>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="loadingSignIn" class="!w-full" @click="signInAgain">
<LogInIcon /> Sign in again
</button>
</ButtonStyled>
<ButtonLink href="https://support.modrinth.com" class="!w-full" @click="modal?.hide()">
<MessagesSquareIcon /> Contact support
</ButtonLink>
<Button
type="colored"
color="brand"
:disabled="loadingSignIn"
class="!w-full"
@click="signInAgain"
>
<LogInIcon /> Sign in again
</Button>
</div>
<div class="flex flex-col gap-2">
@@ -176,16 +178,15 @@ async function copyToClipboard(text: string) {
>
{{ debugInfo }}
</div>
<ButtonStyled circular>
<button
v-tooltip="'Copy debug info'"
:disabled="copied"
@click="copyToClipboard(debugInfo)"
>
<template v-if="copied"> <CheckIcon class="text-green" /> </template>
<template v-else> <CopyIcon /> </template>
</button>
</ButtonStyled>
<IconButton
v-tooltip="'Copy debug info'"
:label="'Copy debug info'"
:disabled="copied"
@click="copyToClipboard(debugInfo)"
>
<template v-if="copied"> <CheckIcon class="text-green" /> </template>
<template v-else> <CopyIcon /> </template>
</IconButton>
</div>
</Collapsible>
</div>
@@ -21,38 +21,33 @@
<div class="flex flex-col gap-6 px-6 pb-6">
<div class="flex justify-end gap-2">
<ButtonStyled>
<a class="w-full !shadow-none" href="https://support.modrinth.com" @click="modal?.hide()">
<MessagesSquareIcon />
{{ formatMessage(messages.getSupport) }}
</a>
</ButtonStyled>
<ButtonStyled color="brand">
<button class="w-full !shadow-none" :disabled="loadingSignIn" @click="signIn">
<SpinnerIcon v-if="loadingSignIn" class="animate-spin" />
<svg
v-else
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect x="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect y="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect
x="10.75"
y="10.75"
width="9.25"
height="9.25"
fill="black"
fill-opacity="0.9"
/>
</svg>
{{ formatMessage(messages.signIn) }}
</button>
</ButtonStyled>
<ButtonLink class="w-full" href="https://support.modrinth.com" @click="modal?.hide()">
<MessagesSquareIcon />
{{ formatMessage(messages.getSupport) }}
</ButtonLink>
<Button
type="colored"
color="brand"
class="w-full"
:disabled="loadingSignIn"
@click="signIn"
>
<SpinnerIcon v-if="loadingSignIn" class="animate-spin" />
<svg
v-else
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect x="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect y="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
<rect x="10.75" y="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
</svg>
{{ formatMessage(messages.signIn) }}
</Button>
</div>
<p class="m-0 text-center text-sm text-secondary">
{{ formatMessage(messages.dontHaveAccount) }}
@@ -69,7 +64,7 @@
<script setup lang="ts">
import { MessagesSquareIcon, SpinnerIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, NewModal, useVIntl } from '@modrinth/ui'
import { Button, ButtonLink, defineMessages, NewModal, useVIntl } from '@modrinth/ui'
import { inject, type Ref, ref } from 'vue'
import steveImage from '@/assets/steve-look-up-left.webp'
@@ -6,18 +6,14 @@
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button @click="modal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button @click="confirm">
<TrashIcon />
{{ formatMessage(messages.deleteButton) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="modal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button type="colored" color="red" @click="confirm">
<TrashIcon />
{{ formatMessage(messages.deleteButton) }}
</Button>
</div>
</template>
</NewModal>
@@ -27,7 +23,7 @@
import { TrashIcon, XIcon } from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
Button,
commonMessages,
defineMessages,
NewModal,
@@ -18,12 +18,10 @@
{{ formatMessage(messages.sharedInstance) }}
</span>
<ButtonStyled type="transparent">
<button @click="openViewContents">
<EyeIcon />
{{ formatMessage(messages.viewContents) }}
</button>
</ButtonStyled>
<Button type="quiet" @click="openViewContents">
<EyeIcon />
{{ formatMessage(messages.viewContents) }}
</Button>
</div>
<div class="flex items-center gap-3 rounded-2xl bg-surface-2 p-3">
@@ -113,40 +111,30 @@
</p>
<div class="flex w-full items-center justify-between gap-2">
<ButtonStyled type="transparent" color="red">
<button @click="handleReport">
<ReportIcon />
{{ formatMessage(commonMessages.reportButton) }}
</button>
</ButtonStyled>
<Button type="quiet" color="red" @click="handleReport">
<ReportIcon />
{{ formatMessage(commonMessages.reportButton) }}
</Button>
<div class="flex items-center gap-2">
<template v-if="hasExternalFiles">
<ButtonStyled type="transparent" color="orange">
<button @click="handleAccept">
{{ formatMessage(messages.installAnyway) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="handleDecline">
<BanIcon />
{{ formatMessage(messages.dontInstall) }}
</button>
</ButtonStyled>
<Button type="quiet" color="orange" @click="handleAccept">
{{ formatMessage(messages.installAnyway) }}
</Button>
<Button type="colored" color="brand" @click="handleDecline">
<BanIcon />
{{ formatMessage(messages.dontInstall) }}
</Button>
</template>
<template v-else>
<ButtonStyled>
<button @click="handleDecline">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="handleAccept">
<DownloadIcon />
{{ formatMessage(messages.installButton) }}
</button>
</ButtonStyled>
<Button @click="handleDecline">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button type="colored" color="brand" @click="handleAccept">
<DownloadIcon />
{{ formatMessage(messages.installButton) }}
</Button>
</template>
</div>
</div>
@@ -163,10 +151,10 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { BanIcon, DownloadIcon, EyeIcon, ReportIcon, XIcon } from '@modrinth/assets'
import { Button } from '@modrinth/ui'
import {
Admonition,
Avatar,
ButtonStyled,
commonMessages,
type ContentItem,
defineMessages,
@@ -10,24 +10,18 @@
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button @click="handleCancel">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="handleGoToInstance">
{{ formatMessage(messages.instance) }}
<RightArrowIcon />
</button>
</ButtonStyled>
<ButtonStyled color="orange">
<button @click="handleCreateAnyway">
<PlusIcon />
{{ formatMessage(messages.create) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="handleCancel">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button @click="handleGoToInstance">
{{ formatMessage(messages.instance) }}
<RightArrowIcon />
</Button>
<Button type="colored" color="orange" @click="handleCreateAnyway">
<PlusIcon />
{{ formatMessage(messages.create) }}
</Button>
</div>
</template>
</NewModal>
@@ -36,7 +30,7 @@
<script setup lang="ts">
import { PlusIcon, RightArrowIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
commonMessages,
defineMessages,
IntlFormatted,
@@ -19,18 +19,20 @@
<div class="flex flex-col gap-6">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<ButtonStyled>
<button class="w-full !shadow-none" type="button" @click="authenticate('sign-up')">
<UserPlusIcon aria-hidden="true" />
{{ formatMessage(messages.createAccountButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button class="w-full" type="button" @click="authenticate('sign-in')">
<LogInIcon aria-hidden="true" />
{{ formatMessage(messages.signInButton) }}
</button>
</ButtonStyled>
<Button class="w-full" native-type="button" @click="authenticate('sign-up')">
<UserPlusIcon aria-hidden="true" />
{{ formatMessage(messages.createAccountButton) }}
</Button>
<Button
type="colored"
color="brand"
class="w-full"
native-type="button"
@click="authenticate('sign-in')"
>
<LogInIcon aria-hidden="true" />
{{ formatMessage(messages.signInButton) }}
</Button>
</div>
<p class="m-0 text-center text-base font-medium leading-6 text-primary">
@@ -69,23 +71,19 @@
<div class="flex flex-col gap-6">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<ButtonStyled type="outlined">
<button class="w-full" type="button" @click="modal?.hide()">
<XIcon aria-hidden="true" />
{{ formatMessage(messages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button
class="w-full !shadow-none"
type="button"
:disabled="reopeningBrowser"
@click="reopenBrowser"
>
<RefreshCwIcon aria-hidden="true" />
{{ formatMessage(messages.openBrowserAgainButton) }}
</button>
</ButtonStyled>
<Button type="outlined" class="w-full" native-type="button" @click="modal?.hide()">
<XIcon aria-hidden="true" />
{{ formatMessage(messages.cancelButton) }}
</Button>
<Button
class="w-full"
native-type="button"
:disabled="reopeningBrowser"
@click="reopenBrowser"
>
<RefreshCwIcon aria-hidden="true" />
{{ formatMessage(messages.openBrowserAgainButton) }}
</Button>
</div>
<p class="m-0 text-center text-base font-medium leading-6 text-primary">
@@ -108,7 +106,7 @@
<script setup lang="ts">
import { LogInIcon, RefreshCwIcon, SpinnerIcon, UserPlusIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, IntlFormatted, NewModal, useVIntl } from '@modrinth/ui'
import { Button, defineMessages, IntlFormatted, NewModal, useVIntl } from '@modrinth/ui'
import { openUrl } from '@tauri-apps/plugin-opener'
import { ref } from 'vue'
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { Settings2Icon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
defineMessages,
injectNotificationManager,
injectPageContext,
@@ -78,12 +78,10 @@ watch(
{{ formatMessage(messages.adsConsentTitle) }}
</h2>
<div class="mt-2 flex flex-col gap-2.5 items-start">
<ButtonStyled>
<button class="!shadow-none" @click="manageAdsPreferences">
<Settings2Icon aria-hidden="true" />
{{ formatMessage(messages.adsConsentManage) }}
</button>
</ButtonStyled>
<Button @click="manageAdsPreferences">
<Settings2Icon aria-hidden="true" />
{{ formatMessage(messages.adsConsentManage) }}
</Button>
<div>
{{ formatMessage(messages.adsConsentIntro) }}
</div>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ButtonStyled, Toggle } from '@modrinth/ui'
import { Button, Toggle } from '@modrinth/ui'
import { ref, watch } from 'vue'
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
@@ -33,14 +33,13 @@ watch(
</h2>
</div>
<div class="flex items-center gap-2">
<ButtonStyled type="transparent">
<button
:disabled="themeStore.getFeatureFlag(option) === DEFAULT_FEATURE_FLAGS[option]"
@click="setFeatureFlag(option, DEFAULT_FEATURE_FLAGS[option])"
>
Reset to default
</button>
</ButtonStyled>
<Button
type="quiet"
:disabled="themeStore.getFeatureFlag(option) === DEFAULT_FEATURE_FLAGS[option]"
@click="setFeatureFlag(option, DEFAULT_FEATURE_FLAGS[option])"
>
Reset to default
</Button>
<Toggle
id="advanced-rendering"
:model-value="themeStore.getFeatureFlag(option)"
@@ -1,8 +1,9 @@
<script setup>
import { BoxIcon, FolderOpenIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
defineMessages,
IconButton,
injectNotificationManager,
Slider,
StyledInput,
@@ -183,16 +184,14 @@ async function findLauncherDir() {
wrapper-class="w-full"
>
<template #right>
<ButtonStyled circular>
<button
v-tooltip="formatMessage(messages.browseAppDirectory)"
:aria-label="formatMessage(messages.browseAppDirectory)"
class="ml-1.5"
@click="findLauncherDir"
>
<FolderSearchIcon aria-hidden="true" />
</button>
</ButtonStyled>
<IconButton
v-tooltip="formatMessage(messages.browseAppDirectory)"
:label="formatMessage(messages.browseAppDirectory)"
class="ml-1.5"
@click="findLauncherDir"
>
<FolderSearchIcon aria-hidden="true" />
</IconButton>
</template>
</StyledInput>
<p class="m-0 leading-tight text-secondary">
@@ -235,10 +234,10 @@ async function findLauncherDir() {
<h2 class="m-0 text-lg font-semibold text-contrast">
{{ formatMessage(messages.appCacheTitle) }}
</h2>
<button id="purge-cache" class="btn min-w-max" @click="handlePurgeCacheClick">
<Button id="purge-cache" class="w-fit" @click="handlePurgeCacheClick">
<TrashIcon aria-hidden="true" />
{{ formatMessage(messages.purgeCache) }}
</button>
</Button>
<p class="m-0 leading-tight text-secondary">
{{ formatMessage(messages.appCacheDescription) }}
</p>
@@ -280,10 +279,10 @@ async function findLauncherDir() {
<h2 class="mt-0 m-0 text-lg font-semibold text-contrast">
{{ formatMessage(messages.appDatabaseBackupsTitle) }}
</h2>
<button id="open-db-backups-folder" class="btn min-w-max" @click="openDbBackupsFolder">
<Button id="open-db-backups-folder" class="w-fit" @click="openDbBackupsFolder">
<FolderOpenIcon aria-hidden="true" />
{{ formatMessage(messages.openBackupsFolder) }}
</button>
</Button>
<p class="m-0 leading-tight text-secondary">
{{ formatMessage(messages.appDatabaseBackupsDescription) }}
</p>
@@ -10,24 +10,18 @@
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled type="outlined">
<button @click="handleCancel">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="handleGoToInstance">
{{ formatMessage(messages.instance) }}
<RightArrowIcon />
</button>
</ButtonStyled>
<ButtonStyled color="orange">
<button @click="handleInstallAnyway">
<DownloadIcon />
{{ formatMessage(messages.installAnyway) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="handleCancel">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button @click="handleGoToInstance">
{{ formatMessage(messages.instance) }}
<RightArrowIcon />
</Button>
<Button type="colored" color="orange" @click="handleInstallAnyway">
<DownloadIcon />
{{ formatMessage(messages.installAnyway) }}
</Button>
</div>
</template>
</NewModal>
@@ -36,7 +30,7 @@
<script setup lang="ts">
import { DownloadIcon, RightArrowIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
commonMessages,
defineMessages,
IntlFormatted,
@@ -193,53 +193,39 @@
{{ formatMessage(messages.reviewedFiles) }}
</p>
<div v-if="!reportMode" class="flex w-full items-center justify-between gap-2">
<ButtonStyled color="red" type="transparent">
<button @click="reportMode = true">
<ReportIcon />{{ formatMessage(commonMessages.reportButton) }}
</button>
</ButtonStyled>
<Button type="quiet" color="red" @click="reportMode = true">
<ReportIcon />{{ formatMessage(commonMessages.reportButton) }}
</Button>
<div class="flex items-center gap-2">
<template v-if="hasExternalFiles">
<ButtonStyled type="transparent" color="orange">
<button @click="accept">
{{ formatMessage(messages.installAnyway) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="handleCancel">
<BanIcon />{{ formatMessage(messages.dontInstall) }}
</button>
</ButtonStyled>
<Button type="quiet" color="orange" @click="accept">
{{ formatMessage(messages.installAnyway) }}
</Button>
<Button type="colored" color="brand" @click="handleCancel">
<BanIcon />{{ formatMessage(messages.dontInstall) }}
</Button>
</template>
<template v-else>
<ButtonStyled type="outlined">
<button class="!border" @click="handleCancel">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="accept">
<DownloadIcon />{{ formatMessage(messages.installButton) }}
</button>
</ButtonStyled>
<Button type="outlined" class="!border" @click="handleCancel">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button type="colored" color="brand" @click="accept">
<DownloadIcon />{{ formatMessage(messages.installButton) }}
</Button>
</template>
</div>
</div>
</div>
<template v-if="reportMode" #actions>
<div class="flex justify-end gap-2">
<ButtonStyled type="outlined">
<button class="!border" :disabled="submitLoading" @click="handleCancel">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!canSubmitReport" @click="submitReport">
<SpinnerIcon v-if="submitLoading" class="animate-spin" />
<SendIcon v-else />
{{ formatMessage(commonMessages.reportButton) }}
</button>
</ButtonStyled>
<Button type="outlined" class="!border" :disabled="submitLoading" @click="handleCancel">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button type="colored" color="brand" :disabled="!canSubmitReport" @click="submitReport">
<SpinnerIcon v-if="submitLoading" class="animate-spin" />
<SendIcon v-else />
{{ formatMessage(commonMessages.reportButton) }}
</Button>
</div>
</template>
</NewModal>
@@ -254,12 +240,12 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { BanIcon, DownloadIcon, ReportIcon, SendIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
import { Button } from '@modrinth/ui'
import {
Admonition,
AutoLink,
Avatar,
blockedUsersQueryKey,
ButtonStyled,
Checkbox,
Combobox,
type ComboboxOption,
@@ -4,12 +4,10 @@
<span class="font-semibold text-contrast">{{
heading ?? formatMessage(messages.sharedInstance)
}}</span>
<ButtonStyled type="transparent">
<button @click="emit('viewContents')">
<EyeIcon />
{{ formatMessage(messages.viewContents) }}
</button>
</ButtonStyled>
<Button type="quiet" @click="emit('viewContents')">
<EyeIcon />
{{ formatMessage(messages.viewContents) }}
</Button>
</div>
<div class="flex items-center gap-3 rounded-2xl bg-surface-2 p-3">
<Avatar
@@ -34,7 +32,7 @@
<script setup lang="ts">
import { EyeIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, defineMessages, formatLoader, useVIntl } from '@modrinth/ui'
import { Avatar, Button, defineMessages, formatLoader, useVIntl } from '@modrinth/ui'
import { computed, toRefs } from 'vue'
import type { SharedInstanceInstallPreview } from '@/helpers/install'
@@ -22,11 +22,9 @@
<div class="flex flex-col gap-4 w-full min-h-[20rem]">
<section v-if="mode === 'edit' && canEditTextureAndModel">
<h2 class="text-base font-semibold mb-2">{{ formatMessage(messages.textureSection) }}</h2>
<ButtonStyled>
<button class="!shadow-none" @click="openTextureFileBrowser">
<UploadIcon /> {{ formatMessage(messages.replaceTextureButton) }}
</button>
</ButtonStyled>
<Button @click="openTextureFileBrowser">
<UploadIcon /> {{ formatMessage(messages.replaceTextureButton) }}
</Button>
<input
ref="textureFileInput"
type="file"
@@ -112,19 +110,21 @@
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button :disabled="isSaving" @click="hide">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button v-tooltip="saveTooltip" :disabled="disableSave || isSaving" @click="save">
<SpinnerIcon v-if="isSaving" class="animate-spin" />
<CheckIcon v-else-if="mode === 'new'" />
<SaveIcon v-else />
{{ formatMessage(mode === 'new' ? messages.addSkinButton : messages.saveSkinButton) }}
</button>
</ButtonStyled>
<Button type="outlined" :disabled="isSaving" @click="hide">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button
v-tooltip="saveTooltip"
type="colored"
color="brand"
:disabled="disableSave || isSaving"
@click="save"
>
<SpinnerIcon v-if="isSaving" class="animate-spin" />
<CheckIcon v-else-if="mode === 'new'" />
<SaveIcon v-else />
{{ formatMessage(mode === 'new' ? messages.addSkinButton : messages.saveSkinButton) }}
</Button>
</div>
</template>
</NewModal>
@@ -133,7 +133,7 @@
<script setup lang="ts">
import { CheckIcon, SaveIcon, SpinnerIcon, UploadIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
CapeButton,
CapeLikeTextButton,
commonMessages,
@@ -2,9 +2,10 @@
import { DropdownIcon, EditIcon, PlusIcon, TrashIcon, UnknownIcon } from '@modrinth/assets'
import {
Accordion,
ButtonStyled,
Button,
commonMessages,
defineMessages,
IconButton,
SkinButton,
SkinLikeTextButton,
useScrollViewport,
@@ -463,25 +464,26 @@ defineExpose({ getAddSkinButtonElement })
@select="emit('select', skin)"
>
<template v-if="!readOnly" #overlay-buttons>
<ButtonStyled color="brand">
<button
:aria-label="formatMessage(messages.editSkinButton)"
class="pointer-events-auto"
@click.stop="(event: MouseEvent) => emit('edit', skin, event)"
>
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
</button>
</ButtonStyled>
<ButtonStyled v-show="!skin.is_equipped" circular color="red">
<button
v-tooltip="formatMessage(messages.deleteSkinButton)"
:aria-label="formatMessage(messages.deleteSkinButton)"
class="!rounded-[100%] pointer-events-auto"
@click.stop="emit('delete', skin)"
>
<TrashIcon />
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
:aria-label="formatMessage(messages.editSkinButton)"
class="pointer-events-auto"
@click.stop="(event: MouseEvent) => emit('edit', skin, event)"
>
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
</Button>
<IconButton
v-show="!skin.is_equipped"
v-tooltip="formatMessage(messages.deleteSkinButton)"
type="colored"
color="red"
:label="formatMessage(messages.deleteSkinButton)"
class="!rounded-[100%] pointer-events-auto"
@click.stop="emit('delete', skin)"
>
<TrashIcon />
</IconButton>
</template>
</SkinButton>
</div>
@@ -503,25 +505,26 @@ defineExpose({ getAddSkinButtonElement })
@select="emit('select', skin)"
>
<template v-if="!readOnly" #overlay-buttons>
<ButtonStyled color="brand">
<button
:aria-label="formatMessage(messages.editSkinButton)"
class="pointer-events-auto"
@click.stop="(event: MouseEvent) => emit('edit', skin, event)"
>
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
</button>
</ButtonStyled>
<ButtonStyled v-show="!skin.is_equipped" circular color="red">
<button
v-tooltip="formatMessage(messages.deleteSkinButton)"
:aria-label="formatMessage(messages.deleteSkinButton)"
class="!rounded-[100%] pointer-events-auto"
@click.stop="emit('delete', skin)"
>
<TrashIcon />
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
:aria-label="formatMessage(messages.editSkinButton)"
class="pointer-events-auto"
@click.stop="(event: MouseEvent) => emit('edit', skin, event)"
>
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
</Button>
<IconButton
v-show="!skin.is_equipped"
v-tooltip="formatMessage(messages.deleteSkinButton)"
type="colored"
color="red"
:label="formatMessage(messages.deleteSkinButton)"
class="!rounded-[100%] pointer-events-auto"
@click.stop="emit('delete', skin)"
>
<TrashIcon />
</IconButton>
</template>
</SkinButton>
</div>
@@ -545,15 +548,15 @@ defineExpose({ getAddSkinButtonElement })
@select="emit('select', skin)"
>
<template #overlay-buttons>
<ButtonStyled color="brand">
<button
:aria-label="formatMessage(messages.editSkinButton)"
class="pointer-events-auto"
@click.stop="(event: MouseEvent) => emit('edit', skin, event)"
>
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
:aria-label="formatMessage(messages.editSkinButton)"
class="pointer-events-auto"
@click.stop="(event: MouseEvent) => emit('edit', skin, event)"
>
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
</Button>
</template>
</SkinButton>
</div>
@@ -9,11 +9,11 @@ import {
} from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
commonMessages,
injectNotificationManager,
OverflowMenu,
SmartClickable,
TeleportOverflowMenu,
useFormatDateTime,
useRelativeTime,
useVIntl,
@@ -185,54 +185,53 @@ onUnmounted(() => {
</div>
</div>
<div class="flex gap-1 justify-end smart-clickable:allow-pointer-events">
<ButtonStyled v-if="playing && !loading" color="red">
<button @click="stop">
<StopCircleIcon aria-hidden="true" />
{{ formatMessage(commonMessages.stopButton) }}
</button>
</ButtonStyled>
<ButtonStyled v-else>
<button
v-tooltip="
instance.quarantined
? 'This instance has been locked'
: playing
? 'Instance is already open'
: null
"
:disabled="instance.quarantined || playing || loading"
@click="play"
>
<SpinnerIcon v-if="loading" class="animate-spin" />
<PlayIcon v-else aria-hidden="true" />
{{ formatMessage(commonMessages.playButton) }}
</button>
</ButtonStyled>
<ButtonStyled circular type="transparent">
<OverflowMenu
:options="[
{
id: 'open-instance',
shown: !!instance.id,
action: () => router.push(encodeURI(`/instance/${instance.id}`)),
},
{
id: 'open-folder',
action: () => showInstanceInFolder(instance.id),
},
]"
>
<MoreVerticalIcon aria-hidden="true" />
<template #open-instance>
<EyeIcon aria-hidden="true" />
View instance
</template>
<template #open-folder>
<FolderOpenIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openFolderButton) }}
</template>
</OverflowMenu>
</ButtonStyled>
<Button v-if="playing && !loading" type="colored" color="red" @click="stop">
<StopCircleIcon aria-hidden="true" />
{{ formatMessage(commonMessages.stopButton) }}
</Button>
<Button
v-else
v-tooltip="
instance.quarantined
? 'This instance has been locked'
: playing
? 'Instance is already open'
: null
"
:disabled="instance.quarantined || playing || loading"
@click="play"
>
<SpinnerIcon v-if="loading" class="animate-spin" />
<PlayIcon v-else aria-hidden="true" />
{{ formatMessage(commonMessages.playButton) }}
</Button>
<TeleportOverflowMenu
type="quiet"
label="More options"
:options="[
{
id: 'open-instance',
label: 'View instance',
shown: !!instance.id,
action: () => router.push(encodeURI(`/instance/${instance.id}`)),
},
{
id: 'open-folder',
label: formatMessage(commonMessages.openFolderButton),
action: () => showInstanceInFolder(instance.id),
},
]"
>
<MoreVerticalIcon aria-hidden="true" />
<template #open-instance>
<EyeIcon aria-hidden="true" />
View instance
</template>
<template #open-folder>
<FolderOpenIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openFolderButton) }}
</template>
</TeleportOverflowMenu>
</div>
</div>
</SmartClickable>
@@ -21,13 +21,13 @@ import {
import type { MessageDescriptor } from '@modrinth/ui'
import {
Avatar,
ButtonStyled,
Button,
commonMessages,
defineMessages,
injectNotificationManager,
OverflowMenu,
SmartClickable,
TagItem,
TeleportOverflowMenu,
useFormatDateTime,
useFormatNumber,
useRelativeTime,
@@ -412,177 +412,184 @@ const messages = defineMessages({
</template>
</div>
<div class="flex gap-1 justify-end smart-clickable:allow-pointer-events">
<ButtonStyled
<Button
v-if="(playingWorld || (locked && playingInstance)) && !startingInstance"
type="colored"
color="red"
@click="emit('stop')"
>
<button @click="emit('stop')">
<StopCircleIcon aria-hidden="true" />
{{ formatMessage(commonMessages.stopButton) }}
</button>
</ButtonStyled>
<ButtonStyled v-else>
<button
v-tooltip="
quarantined
? 'This instance has been locked'
: world.type === 'server'
? !supportsServerQuickPlay
? formatMessage(messages.noServerQuickPlay)
: playingOtherWorld
? formatMessage(messages.gameAlreadyOpen)
: !serverStatus
? formatMessage(messages.noContact)
: serverIncompatible
? formatMessage(messages.incompatibleServer)
: null
: !supportsWorldQuickPlay
? formatMessage(messages.noSingleplayerQuickPlay)
: playingOtherWorld || locked
? formatMessage(messages.gameAlreadyOpen)
: null
"
:disabled="
quarantined ||
playingOtherWorld ||
startingInstance ||
(world.type == 'server' && !supportsServerQuickPlay) ||
(world.type == 'singleplayer' && !supportsWorldQuickPlay)
"
@click="emit('play')"
>
<SpinnerIcon v-if="startingInstance && playingWorld" class="animate-spin" />
<PlayIcon v-else aria-hidden="true" />
{{ formatMessage(commonMessages.playButton) }}
</button>
</ButtonStyled>
<ButtonStyled circular type="transparent">
<OverflowMenu
:options="[
{
id: 'play-instance',
shown: !!instanceId,
disabled: playingInstance || quarantined,
action: () => emit('play-instance'),
<StopCircleIcon aria-hidden="true" />
{{ formatMessage(commonMessages.stopButton) }}
</Button>
<Button
v-else
v-tooltip="
quarantined
? 'This instance has been locked'
: world.type === 'server'
? !supportsServerQuickPlay
? formatMessage(messages.noServerQuickPlay)
: playingOtherWorld
? formatMessage(messages.gameAlreadyOpen)
: !serverStatus
? formatMessage(messages.noContact)
: serverIncompatible
? formatMessage(messages.incompatibleServer)
: null
: !supportsWorldQuickPlay
? formatMessage(messages.noSingleplayerQuickPlay)
: playingOtherWorld || locked
? formatMessage(messages.gameAlreadyOpen)
: null
"
:disabled="
quarantined ||
playingOtherWorld ||
startingInstance ||
(world.type == 'server' && !supportsServerQuickPlay) ||
(world.type == 'singleplayer' && !supportsWorldQuickPlay)
"
@click="emit('play')"
>
<SpinnerIcon v-if="startingInstance && playingWorld" class="animate-spin" />
<PlayIcon v-else aria-hidden="true" />
{{ formatMessage(commonMessages.playButton) }}
</Button>
<TeleportOverflowMenu
type="quiet"
label="More options"
:options="[
{
id: 'play-instance',
label: formatMessage(messages.playInstance),
shown: !!instanceId,
disabled: playingInstance || quarantined,
action: () => emit('play-instance'),
},
{
id: 'open-instance',
label: formatMessage(messages.viewInstance),
shown: !!instanceId,
action: () => router.push(`/instance/${encodeURIComponent(instanceId)}`),
},
{
id: 'refresh',
label: formatMessage(commonMessages.refreshButton),
shown: world.type === 'server',
action: () => emit('refresh'),
},
{
id: 'copy-address',
label: formatMessage(messages.copyAddress),
shown: world.type === 'server',
action: () => copyToClipboard((world as ServerWorld).address),
},
{
id: 'edit',
label: formatMessage(commonMessages.editButton),
action: () => emit('edit'),
shown: !instanceId,
disabled: locked || managed,
tooltip: locked
? formatMessage(messages.worldInUse)
: managed
? formatMessage(messages.linkedServer)
: undefined,
},
{
id: 'open-folder',
label: formatMessage(commonMessages.openFolderButton),
shown: world.type === 'singleplayer',
action: () => (world.type === 'singleplayer' ? emit('open-folder', world) : {}),
},
{
type: 'divider',
shown: !!instanceId,
},
{
id: 'dont-show-on-home',
label: formatMessage(messages.dontShowOnHome),
shown: !!instanceId,
action: () => {
set_world_display_status(
instanceId,
world.type,
getWorldIdentifier(world),
'hidden',
).then(() => {
emit('update')
})
},
{
id: 'open-instance',
shown: !!instanceId,
action: () => router.push(`/instance/${encodeURIComponent(instanceId)}`),
},
{
id: 'refresh',
shown: world.type === 'server',
action: () => emit('refresh'),
},
{
id: 'copy-address',
shown: world.type === 'server',
action: () => copyToClipboard((world as ServerWorld).address),
},
{
id: 'edit',
action: () => emit('edit'),
shown: !instanceId,
disabled: locked || managed,
tooltip: locked
? formatMessage(messages.worldInUse)
: managed
? formatMessage(messages.linkedServer)
: undefined,
},
{
id: 'open-folder',
shown: world.type === 'singleplayer',
action: () => (world.type === 'singleplayer' ? emit('open-folder', world) : {}),
},
{
divider: true,
shown: !!instanceId,
},
{
id: 'dont-show-on-home',
shown: !!instanceId,
action: () => {
set_world_display_status(
instanceId,
world.type,
getWorldIdentifier(world),
'hidden',
).then(() => {
emit('update')
})
},
},
{
id: 'create-shortcut',
shown: !!shortcutInstanceId && !quarantined,
action: () => createShortcut(),
},
{
divider: true,
shown: !instanceId,
},
{
id: 'delete',
color: 'red',
hoverFilled: true,
action: () => emit('delete'),
shown: !instanceId,
disabled: locked || managed,
tooltip: locked
? formatMessage(messages.worldInUse)
: managed
? formatMessage(messages.linkedServer)
: undefined,
},
]"
>
<MoreVerticalIcon aria-hidden="true" />
<template #play-instance>
<PlayIcon aria-hidden="true" />
{{ formatMessage(messages.playInstance) }}
</template>
<template #open-instance>
<EyeIcon aria-hidden="true" />
{{ formatMessage(messages.viewInstance) }}
</template>
<template #edit>
<EditIcon aria-hidden="true" />
{{ formatMessage(commonMessages.editButton) }}
</template>
<template #open-folder>
<FolderOpenIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openFolderButton) }}
</template>
<template #copy-address>
<ClipboardCopyIcon aria-hidden="true" />
{{ formatMessage(messages.copyAddress) }}
</template>
<template #refresh>
<UpdatedIcon aria-hidden="true" />
{{ formatMessage(commonMessages.refreshButton) }}
</template>
<template #create-shortcut>
<ExternalIcon aria-hidden="true" />
{{ formatMessage(messages.createShortcut) }}
</template>
<template #dont-show-on-home>
<XIcon aria-hidden="true" />
{{ formatMessage(messages.dontShowOnHome) }}
</template>
<template #delete>
<TrashIcon aria-hidden="true" />
{{
formatMessage(
world.type === 'server'
? commonMessages.removeButton
: commonMessages.deleteLabel,
)
}}
</template>
</OverflowMenu>
</ButtonStyled>
},
{
id: 'create-shortcut',
label: formatMessage(messages.createShortcut),
shown: !!shortcutInstanceId && !quarantined,
action: () => createShortcut(),
},
{
type: 'divider',
shown: !instanceId,
},
{
id: 'delete',
label: formatMessage(
world.type === 'server' ? commonMessages.removeButton : commonMessages.deleteLabel,
),
tone: 'red',
action: () => emit('delete'),
shown: !instanceId,
disabled: locked || managed,
tooltip: locked
? formatMessage(messages.worldInUse)
: managed
? formatMessage(messages.linkedServer)
: undefined,
},
]"
>
<MoreVerticalIcon aria-hidden="true" />
<template #play-instance>
<PlayIcon aria-hidden="true" />
{{ formatMessage(messages.playInstance) }}
</template>
<template #open-instance>
<EyeIcon aria-hidden="true" />
{{ formatMessage(messages.viewInstance) }}
</template>
<template #edit>
<EditIcon aria-hidden="true" />
{{ formatMessage(commonMessages.editButton) }}
</template>
<template #open-folder>
<FolderOpenIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openFolderButton) }}
</template>
<template #copy-address>
<ClipboardCopyIcon aria-hidden="true" />
{{ formatMessage(messages.copyAddress) }}
</template>
<template #refresh>
<UpdatedIcon aria-hidden="true" />
{{ formatMessage(commonMessages.refreshButton) }}
</template>
<template #create-shortcut>
<ExternalIcon aria-hidden="true" />
{{ formatMessage(messages.createShortcut) }}
</template>
<template #dont-show-on-home>
<XIcon aria-hidden="true" />
{{ formatMessage(messages.dontShowOnHome) }}
</template>
<template #delete>
<TrashIcon aria-hidden="true" />
{{
formatMessage(
world.type === 'server' ? commonMessages.removeButton : commonMessages.deleteLabel,
)
}}
</template>
</TeleportOverflowMenu>
</div>
</div>
</SmartClickable>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
commonMessages,
defineMessages,
injectNotificationManager,
@@ -96,24 +96,18 @@ defineExpose({ show, hide })
/>
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button :disabled="!address" @click="addServer(false)">
<PlusIcon />
{{ formatMessage(messages.addServer) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!address" @click="addServer(true)">
<PlayIcon />
{{ formatMessage(messages.addAndPlay) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button :disabled="!address" @click="addServer(false)">
<PlusIcon />
{{ formatMessage(messages.addServer) }}
</Button>
<Button type="colored" color="brand" :disabled="!address" @click="addServer(true)">
<PlayIcon />
{{ formatMessage(messages.addAndPlay) }}
</Button>
</div>
</template>
</NewModal>
@@ -2,7 +2,7 @@
import { TrashIcon, XIcon } from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
Button,
commonMessages,
defineMessages,
NewModal,
@@ -106,18 +106,19 @@ defineExpose({ show, hide })
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button @click="hide">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button :disabled="!isServer && !isSingleplayer" @click="confirm">
<TrashIcon />
{{ formatMessage(actionMessage) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="hide">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button
type="colored"
color="red"
:disabled="!isServer && !isSingleplayer"
@click="confirm"
>
<TrashIcon />
{{ formatMessage(actionMessage) }}
</Button>
</div>
</template>
</NewModal>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { SaveIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
commonMessages,
defineMessage,
injectNotificationManager,
@@ -105,18 +105,14 @@ const titleMessage = defineMessage({
<HideFromHomeOption v-model="hideFromHome" class="mt-3" />
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!address" @click="saveServer">
<SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button type="colored" color="brand" :disabled="!address" @click="saveServer">
<SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }}
</Button>
</div>
</template>
</NewModal>
@@ -2,7 +2,7 @@
import { ChevronRightIcon, SaveIcon, UndoIcon, XIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
commonMessages,
defineMessages,
injectNotificationManager,
@@ -113,24 +113,18 @@ const messages = defineMessages({
<HideFromHomeOption v-model="hideFromHome" class="mt-3" />
</div>
<div class="flex gap-2 mt-4">
<ButtonStyled color="brand">
<button @click="saveWorld">
<SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button :disabled="removeIcon || !icon" @click="removeIcon = true">
<UndoIcon />
{{ formatMessage(messages.resetIcon) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="saveWorld">
<SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }}
</Button>
<Button :disabled="removeIcon || !icon" @click="removeIcon = true">
<UndoIcon />
{{ formatMessage(messages.resetIcon) }}
</Button>
<Button @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
</div>
</ModalWrapper>
</template>
@@ -1229,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "Version {version} was successfully installed!"
},
"app.user.project.install-to-instance": {
"message": "Install to instance"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
+49 -39
View File
@@ -10,7 +10,7 @@ import {
SpinnerIcon,
} from '@modrinth/assets'
import {
ButtonStyled,
Button,
commonMessages,
ConfirmModal,
defineMessages,
@@ -1112,13 +1112,15 @@ await loadSkins()
class="skin-preview-actions flex w-full items-center justify-center gap-1.5"
:class="selectedSkinHasEarsFeatures ? 'flex-nowrap' : 'flex-wrap'"
>
<button
<Button
v-tooltip="
selectedSkinHasEarsFeatures
? formatMessage(commonMessages.resetButton)
: undefined
"
class="skin-preview-action-button flex h-10 min-w-0 cursor-pointer items-center justify-center gap-2 rounded-[14px] border-0 bg-surface-4 px-4 py-2.5 text-base font-semibold leading-5 text-contrast shadow-md transition-[filter,transform] duration-200 enabled:hover:brightness-[--hover-brightness] enabled:focus-visible:brightness-[--hover-brightness] enabled:active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:size-5 [&>svg]:shrink-0"
type="base"
size="lg"
class="skin-preview-action-button"
:disabled="isApplyingSkin || isSkinManagementReadOnly"
:aria-label="formatMessage(commonMessages.resetButton)"
@click="resetSelectedSkin"
@@ -1127,12 +1129,15 @@ await loadSkins()
<span class="skin-preview-action-label">
{{ formatMessage(commonMessages.resetButton) }}
</span>
</button>
<button
</Button>
<Button
v-tooltip="
selectedSkinHasEarsFeatures ? formatMessage(messages.applyButton) : undefined
"
class="skin-preview-action-button flex h-10 min-w-0 cursor-pointer items-center justify-center gap-2 rounded-[14px] border-0 bg-brand px-4 py-2.5 text-base font-semibold leading-5 text-[rgba(0,0,0,0.9)] shadow-md transition-[filter,transform] duration-200 enabled:hover:brightness-[--hover-brightness] enabled:focus-visible:brightness-[--hover-brightness] enabled:active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:size-5 [&>svg]:shrink-0"
type="colored"
color="brand"
size="lg"
class="skin-preview-action-button"
:disabled="isApplyingSkin || isSkinManagementReadOnly"
:aria-label="formatMessage(messages.applyButton)"
@click="applySelectedSkin"
@@ -1142,17 +1147,18 @@ await loadSkins()
<span class="skin-preview-action-label">
{{ formatMessage(messages.applyButton) }}
</span>
</button>
</Button>
</div>
<button
<Button
v-else
class="flex h-10 min-w-0 cursor-pointer items-center justify-center gap-2 rounded-[14px] border-0 bg-surface-4 px-4 py-2.5 text-base font-semibold leading-5 shadow-md transition-[filter,transform] duration-200 enabled:hover:brightness-[--hover-brightness] enabled:focus-visible:brightness-[--hover-brightness] enabled:active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:size-5 [&>svg]:shrink-0"
type="base"
size="lg"
:disabled="!selectedSkin || isSkinManagementReadOnly"
@click="(e: MouseEvent) => selectedSkin && editSkinModal?.show(e, selectedSkin)"
>
<EditIcon />
{{ formatMessage(messages.editSkinButton) }}
</button>
</Button>
<div
v-if="selectedSkinHasEarsFeatures"
@@ -1181,28 +1187,28 @@ await loadSkins()
>Ears</router-link
>
</div>
<ButtonStyled type="outlined">
<button
class="ears-feature-toggle-button !h-10 !rounded-[14px] !px-4 shadow-md"
:aria-pressed="earsFeaturesEnabled"
:aria-label="
formatMessage(
earsFeaturesEnabled
? messages.toggleEarsFeaturesOff
: messages.toggleEarsFeaturesOn,
)
"
@click="earsFeaturesEnabled = !earsFeaturesEnabled"
>
{{
formatMessage(
earsFeaturesEnabled
? messages.toggleEarsFeaturesOff
: messages.toggleEarsFeaturesOn,
)
}}
</button>
</ButtonStyled>
<Button
type="outlined"
size="lg"
class="ears-feature-toggle-button shadow-md"
:aria-pressed="earsFeaturesEnabled"
:aria-label="
formatMessage(
earsFeaturesEnabled
? messages.toggleEarsFeaturesOff
: messages.toggleEarsFeaturesOn,
)
"
@click="earsFeaturesEnabled = !earsFeaturesEnabled"
>
{{
formatMessage(
earsFeaturesEnabled
? messages.toggleEarsFeaturesOff
: messages.toggleEarsFeaturesOn,
)
}}
</Button>
<Toggle
v-model="earsFeaturesEnabled"
v-tooltip="
@@ -1279,13 +1285,17 @@ await loadSkins()
<p class="text-lg m-0">
{{ formatMessage(messages.signInDescription) }}
</p>
<ButtonStyled v-show="accountsCard" color="brand" :disabled="accountsCard.loginDisabled">
<button :disabled="accountsCard.loginDisabled" @click="login">
<LogInIcon v-if="!accountsCard.loginDisabled" />
<SpinnerIcon v-else class="animate-spin" />
{{ formatMessage(messages.signInButton) }}
</button>
</ButtonStyled>
<Button
v-show="accountsCard"
type="colored"
color="brand"
:disabled="accountsCard.loginDisabled"
@click="login"
>
<LogInIcon v-if="!accountsCard.loginDisabled" />
<SpinnerIcon v-else class="animate-spin" />
{{ formatMessage(messages.signInButton) }}
</Button>
</div>
</div>
</div>
+82 -4
View File
@@ -1,5 +1,5 @@
<template>
<div class="w-full px-2 pt-2">
<div class="w-full p-2">
<UserProfilePageLayout
:user-id="userId"
:project-type="projectType"
@@ -8,16 +8,47 @@
project-link-mode="app"
:edit-profile-link="openProfileSettings"
external-navigation
/>
>
<template #project-actions="{ project }">
<Button
type="outlined"
class="!text-brand [&>svg]:!text-brand !shadow-[inset_0_0_0_1px_var(--color-brand)]"
:disabled="isProjectInstalling(project.id)"
@click.stop="installProject(project)"
>
<SpinnerIcon v-if="isProjectInstalling(project.id)" class="animate-spin" />
<DownloadIcon v-else-if="project.project_type === 'modpack'" />
<PlusIcon v-else />
{{
formatMessage(
isProjectInstalling(project.id)
? commonMessages.installingLabel
: project.project_type === 'modpack'
? commonMessages.installButton
: messages.installToInstance,
)
}}
</Button>
</template>
</UserProfilePageLayout>
</div>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { provideUserProfile, UserProfilePageLayout } from '@modrinth/ui'
import { DownloadIcon, PlusIcon, SpinnerIcon } from '@modrinth/assets'
import {
Button,
commonMessages,
defineMessages,
injectNotificationManager,
provideUserProfile,
UserProfilePageLayout,
useVIntl,
} from '@modrinth/ui'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, inject, ref, watch } from 'vue'
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
import {
block_user,
@@ -31,10 +62,57 @@ import {
} from '@/helpers/users'
import { appSettingsModalOpenProfileKey } from '@/providers/app-settings-modal'
import { useBreadcrumb } from '@/providers/breadcrumbs'
import { injectContentInstall } from '@/providers/content-install'
const route = useRoute()
const router = useRouter()
const openProfileSettings = inject(appSettingsModalOpenProfileKey, () => {})
const queryClient = useQueryClient()
const { formatMessage } = useVIntl()
const { handleError } = injectNotificationManager()
const { install: installVersion } = injectContentInstall()
const installingProjectIds = ref(new Set<string>())
const messages = defineMessages({
installToInstance: {
id: 'app.user.project.install-to-instance',
defaultMessage: 'Install to instance',
},
})
function isProjectInstalling(projectId: string): boolean {
return installingProjectIds.value.has(projectId)
}
function setProjectInstalling(projectId: string, installing: boolean): void {
const next = new Set(installingProjectIds.value)
if (installing) {
next.add(projectId)
} else {
next.delete(projectId)
}
installingProjectIds.value = next
}
async function installProject(project: Labrinth.Projects.v2.Project): Promise<void> {
if (isProjectInstalling(project.id)) return
setProjectInstalling(project.id, true)
try {
await installVersion(
project.id,
null,
null,
'UserPage',
() => setProjectInstalling(project.id, false),
(instanceId) => router.push(`/instance/${encodeURIComponent(instanceId)}`),
)
} catch (error) {
setProjectInstalling(project.id, false)
handleError(error)
}
}
const userProfile = provideUserProfile({
getUser: get_user_profile,
getProjects: get_user_projects,
@@ -6,23 +6,27 @@
>
{{ formatMessage(messages.sharedInstanceChangesBody) }}
<template #actions>
<ButtonStyled color="blue">
<button class="!h-10" :disabled="isPublishButtonDisabled" @click="reviewChanges">
<SpinnerIcon
v-if="isReviewingPublish || isPublishing"
class="animate-spin"
aria-hidden="true"
/>
<UploadIcon v-else aria-hidden="true" />
{{
isPublishing
? formatMessage(messages.sharedInstancePublishingButton)
: isReviewingPublish
? formatMessage(messages.sharedInstanceReviewingButton)
: formatMessage(messages.sharedInstancePublishButton)
}}
</button>
</ButtonStyled>
<Button
type="colored"
color="blue"
size="lg"
:disabled="isPublishButtonDisabled"
@click="reviewChanges"
>
<SpinnerIcon
v-if="isReviewingPublish || isPublishing"
class="animate-spin"
aria-hidden="true"
/>
<UploadIcon v-else aria-hidden="true" />
{{
isPublishing
? formatMessage(messages.sharedInstancePublishingButton)
: isReviewingPublish
? formatMessage(messages.sharedInstanceReviewingButton)
: formatMessage(messages.sharedInstancePublishButton)
}}
</Button>
</template>
</Admonition>
@@ -36,7 +40,7 @@
<script setup lang="ts">
import { SpinnerIcon, UploadIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, useVIntl } from '@modrinth/ui'
import { Admonition, Button, useVIntl } from '@modrinth/ui'
import { computed, ref } from 'vue'
import SharedInstancePublishModal from '@/components/ui/shared-instances/SharedInstancePublishModal.vue'
@@ -8,19 +8,17 @@
>
{{ formatSharedInstanceUnavailable(reason ?? null, manager) }}
<template v-if="reason === 'quarantined'" #actions>
<ButtonStyled color="orange">
<button class="!h-10" @click="emit('delete')">
<TrashIcon aria-hidden="true" />
{{ formatMessage(messages.deleteInstance) }}
</button>
</ButtonStyled>
<Button type="colored" color="orange" size="lg" @click="emit('delete')">
<TrashIcon aria-hidden="true" />
{{ formatMessage(messages.deleteInstance) }}
</Button>
</template>
</Admonition>
</template>
<script setup lang="ts">
import { TrashIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
import { Admonition, Button, defineMessages, useVIntl } from '@modrinth/ui'
import type { SharedInstanceUnavailableReason } from '@/helpers/install'
import {
@@ -6,19 +6,17 @@
>
{{ formatMessage(messages.sharedInstanceUpdateAvailableBody, { name: instanceName }) }}
<template #actions>
<ButtonStyled color="blue">
<button class="!h-10" @click="emit('review', $event)">
<DownloadIcon aria-hidden="true" />
{{ formatMessage(messages.sharedInstanceReviewUpdateButton) }}
</button>
</ButtonStyled>
<Button type="colored" color="blue" size="lg" @click="emit('review', $event)">
<DownloadIcon aria-hidden="true" />
{{ formatMessage(messages.sharedInstanceReviewUpdateButton) }}
</Button>
</template>
</Admonition>
</template>
<script setup lang="ts">
import { DownloadIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, useVIntl } from '@modrinth/ui'
import { Admonition, Button, useVIntl } from '@modrinth/ui'
import { instanceAdmonitionsMessages as messages } from './messages'
@@ -85,66 +85,98 @@
<template #actions>
<PageHeaderActions>
<ButtonStyled v-if="isInstalling" color="brand" size="large">
<button type="button" disabled>
{{ formatMessage(commonMessages.installingLabel) }}
</button>
</ButtonStyled>
<ButtonStyled v-else-if="playing" color="red" size="large">
<button type="button" :disabled="stopping" @click="emit('stop')">
<StopCircleIcon />
{{
stopping ? formatMessage(messages.stopping) : formatMessage(commonMessages.stopButton)
}}
</button>
</ButtonStyled>
<ButtonStyled v-else-if="instance.quarantined" color="brand" size="large">
<button v-tooltip="formatMessage(messages.lockedPlayTooltip)" type="button" disabled>
<PlayIcon />
{{ formatMessage(commonMessages.playButton) }}
</button>
</ButtonStyled>
<ButtonStyled v-else-if="instance.install_stage !== 'installed'" color="brand" size="large">
<button type="button" @click="emit('repair')">
<DownloadIcon />
{{ formatMessage(messages.repair) }}
</button>
</ButtonStyled>
<JoinedButtons
v-else-if="!loading && isServerInstance"
:actions="serverPlayActions"
<Button
v-if="isInstalling"
type="colored"
color="brand"
size="large"
/>
<ButtonStyled v-else-if="!loading" color="brand" size="large">
<button type="button" @click="emit('play')">
<PlayIcon />
{{ formatMessage(commonMessages.playButton) }}
</button>
</ButtonStyled>
<ButtonStyled v-else color="brand" size="large">
<button type="button" disabled>{{ formatMessage(messages.starting) }}</button>
</ButtonStyled>
size="xl"
native-type="button"
disabled
>
{{ formatMessage(commonMessages.installingLabel) }}
</Button>
<Button
v-else-if="playing"
type="colored"
color="red"
size="xl"
native-type="button"
:disabled="stopping"
@click="emit('stop')"
>
<StopCircleIcon />
{{
stopping ? formatMessage(messages.stopping) : formatMessage(commonMessages.stopButton)
}}
</Button>
<Button
v-else-if="instance.quarantined"
v-tooltip="formatMessage(messages.lockedPlayTooltip)"
type="colored"
color="brand"
size="xl"
native-type="button"
disabled
>
<PlayIcon />
{{ formatMessage(commonMessages.playButton) }}
</Button>
<Button
v-else-if="instance.install_stage !== 'installed'"
type="colored"
color="brand"
size="xl"
native-type="button"
@click="emit('repair')"
>
<DownloadIcon />
{{ formatMessage(messages.repair) }}
</Button>
<SplitButton
v-else-if="!loading && isServerInstance"
type="colored"
color="brand"
size="xl"
:options="serverPlayOptions"
:menu-label="formatMessage(messages.launchInstance)"
@click="emit('playServer')"
>
<PlayIcon />
{{ formatMessage(commonMessages.playButton) }}
</SplitButton>
<Button
v-else-if="!loading"
type="colored"
color="brand"
size="xl"
native-type="button"
@click="emit('play')"
>
<PlayIcon />
{{ formatMessage(commonMessages.playButton) }}
</Button>
<Button v-else type="colored" color="brand" size="xl" native-type="button" disabled>{{
formatMessage(messages.starting)
}}</Button>
<ButtonStyled circular size="large">
<button
v-tooltip="formatMessage(messages.instanceSettings)"
type="button"
:aria-label="formatMessage(messages.instanceSettings)"
@click="emit('settings')"
>
<SettingsIcon />
</button>
</ButtonStyled>
<ButtonStyled circular size="large" type="transparent">
<TeleportOverflowMenu
:options="moreActions"
:tooltip="formatMessage(messages.moreActions)"
:aria-label="formatMessage(messages.moreActions)"
>
<MoreVerticalIcon />
</TeleportOverflowMenu>
</ButtonStyled>
<IconButton
v-tooltip="formatMessage(messages.instanceSettings)"
size="xl"
:label="formatMessage(messages.instanceSettings)"
native-type="button"
@click="emit('settings')"
>
<SettingsIcon />
</IconButton>
<TeleportOverflowMenu
type="quiet"
size="xl"
:label="formatMessage(messages.moreActions)"
:tooltip="formatMessage(messages.moreActions)"
:options="moreActions"
>
<MoreVerticalIcon />
</TeleportOverflowMenu>
</PageHeaderActions>
</template>
</PageHeader>
@@ -167,23 +199,20 @@ import {
TimerIcon,
UnknownIcon,
} from '@modrinth/assets'
import { Button, IconButton, SplitButton, TeleportOverflowMenu } from '@modrinth/ui'
import {
Avatar,
ButtonStyled,
commonMessages,
defineMessages,
formatLoaderLabel,
type JoinedButtonAction,
JoinedButtons,
LoaderIcon as ServerLoaderIcon,
type OverflowMenuOption,
PageHeader,
PageHeaderActions,
PageHeaderBadgeItem,
PageHeaderMetadata,
PageHeaderMetadataItem,
type ServerLoader,
TeleportOverflowMenu,
type TeleportOverflowMenuItem,
useVIntl,
} from '@modrinth/ui'
import { computed } from 'vue'
@@ -350,13 +379,7 @@ const playtimeLabel = computed(() => {
const seconds = Math.floor(props.timePlayed)
return `${seconds} second${seconds > 1 ? 's' : ''}`
})
const serverPlayActions = computed<JoinedButtonAction[]>(() => [
{
id: 'join_server',
label: formatMessage(commonMessages.playButton),
icon: PlayIcon,
action: () => emit('playServer'),
},
const serverPlayOptions = computed<OverflowMenuOption[]>(() => [
{
id: 'launch_instance',
label: formatMessage(messages.launchInstance),
@@ -364,8 +387,8 @@ const serverPlayActions = computed<JoinedButtonAction[]>(() => [
action: () => emit('play'),
},
])
const moreActions = computed<TeleportOverflowMenuItem[]>(() => {
const actions: TeleportOverflowMenuItem[] = [
const moreActions = computed<OverflowMenuOption[]>(() => {
const actions: OverflowMenuOption[] = [
{
id: 'open-folder',
label: formatMessage(messages.openFolder),
@@ -393,12 +416,12 @@ const moreActions = computed<TeleportOverflowMenuItem[]>(() => {
if (props.instance.shared_instance?.role === 'member') {
actions.push(
{ divider: true },
{ type: 'divider' },
{
id: 'report-shared-instance',
label: formatMessage(commonMessages.reportButton),
icon: ReportIcon,
color: 'red',
tone: 'red',
action: (event) => emit('report', event),
},
)
@@ -10,18 +10,14 @@
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled type="outlined">
<button @click="modal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button @click="confirm">
<XIcon />
{{ formatMessage(messages.revokeButton) }}
</button>
</ButtonStyled>
<Button type="outlined" @click="modal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button type="colored" color="red" @click="confirm">
<XIcon />
{{ formatMessage(messages.revokeButton) }}
</Button>
</div>
</template>
</NewModal>
@@ -31,7 +27,7 @@
import { XIcon } from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
Button,
commonMessages,
defineMessages,
IntlFormatted,
@@ -2,13 +2,13 @@
import { CopyIcon, EditIcon, PlusIcon, SpinnerIcon, TrashIcon, UploadIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
Checkbox,
Chips,
defineMessages,
injectNotificationManager,
OverflowMenu,
StyledInput,
TeleportOverflowMenu,
useVIntl,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
@@ -298,17 +298,24 @@ const messages = defineMessages({
<div class="flex flex-col gap-1">
<span class="text-lg font-semibold text-contrast">Icon</span>
<div class="group relative w-fit">
<OverflowMenu
v-tooltip="formatMessage(messages.editIcon)"
class="bg-transparent border-none appearance-none p-0 m-0 cursor-pointer group-active:scale-95 transition-transform"
<TeleportOverflowMenu
:label="formatMessage(messages.editIcon)"
:tooltip="formatMessage(messages.editIcon)"
:icon-only="false"
type="quiet"
interaction="none"
class="m-0 !h-auto cursor-pointer appearance-none border-none bg-transparent !p-0 transition-transform group-active:scale-95"
:options="[
{
id: 'select',
label: icon
? formatMessage(messages.replaceIcon)
: formatMessage(messages.selectIcon),
action: () => setIcon(),
},
{
id: 'remove',
color: 'danger',
label: formatMessage(messages.removeIcon),
action: () => resetIcon(),
shown: !!icon,
},
@@ -331,7 +338,7 @@ const messages = defineMessages({
{{ icon ? formatMessage(messages.replaceIcon) : formatMessage(messages.selectIcon) }}
</template>
<template #remove> <TrashIcon /> {{ formatMessage(messages.removeIcon) }} </template>
</OverflowMenu>
</TeleportOverflowMenu>
</div>
</div>
</div>
@@ -352,17 +359,15 @@ const messages = defineMessages({
<h2 id="duplicate-instance-label" class="m-0 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.duplicateInstance) }}
</h2>
<ButtonStyled>
<button
v-tooltip="installing ? formatMessage(messages.duplicateButtonTooltipInstalling) : null"
aria-labelledby="duplicate-instance-label"
:disabled="installing"
class="w-max !shadow-none"
@click="duplicateInstance"
>
<CopyIcon /> {{ formatMessage(messages.duplicateButton) }}
</button>
</ButtonStyled>
<Button
v-tooltip="installing ? formatMessage(messages.duplicateButtonTooltipInstalling) : null"
aria-labelledby="duplicate-instance-label"
:disabled="installing"
class="w-max"
@click="duplicateInstance"
>
<CopyIcon /> {{ formatMessage(messages.duplicateButton) }}
</Button>
<p class="m-0">
{{ formatMessage(messages.duplicateInstanceDescription) }}
</p>
@@ -388,11 +393,9 @@ const messages = defineMessages({
class="w-full max-w-[300px]"
@submit="() => addCategory"
/>
<ButtonStyled>
<button class="w-fit !shadow-none" @click="() => addCategory()">
<PlusIcon /> {{ formatMessage(messages.libraryGroupsCreate) }}
</button>
</ButtonStyled>
<Button class="w-fit" @click="() => addCategory()">
<PlusIcon /> {{ formatMessage(messages.libraryGroupsCreate) }}
</Button>
</div>
</div>
<p class="m-0">
@@ -421,22 +424,22 @@ const messages = defineMessages({
<h2 id="delete-instance-label" class="m-0 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.deleteInstance) }}
</h2>
<ButtonStyled color="red">
<button
aria-labelledby="delete-instance-label"
:disabled="removing"
class="w-fit !shadow-none"
@click="deleteConfirmModal.show()"
>
<SpinnerIcon v-if="removing" class="animate-spin" />
<TrashIcon v-else />
{{
removing
? formatMessage(messages.deletingInstanceButton)
: formatMessage(messages.deleteInstanceButton)
}}
</button>
</ButtonStyled>
<Button
type="colored"
color="red"
aria-labelledby="delete-instance-label"
:disabled="removing"
class="w-fit"
@click="deleteConfirmModal.show()"
>
<SpinnerIcon v-if="removing" class="animate-spin" />
<TrashIcon v-else />
{{
removing
? formatMessage(messages.deletingInstanceButton)
: formatMessage(messages.deleteInstanceButton)
}}
</Button>
<p class="m-0">
{{ formatMessage(messages.deleteInstanceDescription) }}
</p>
@@ -9,7 +9,7 @@ import {
XCircleIcon,
} from '@modrinth/assets'
import {
ButtonStyled,
Button,
Checkbox,
defineMessages,
injectNotificationManager,
@@ -224,45 +224,60 @@ const messages = defineMessages({
wrapper-class="flex-1 min-w-0"
@update:model-value="(val) => (javaPath = String(val))"
/>
<ButtonStyled
<Button
type="quiet"
:color="
!hoveringTest && !testingJava
? javaTestResult === true
? 'green'
: 'red'
: 'standard'
: undefined
"
color-fill="text"
:disabled="!overrideJavaInstall || testingJava"
:style="{
'--legacy-button-color':
(!hoveringTest && !testingJava
? javaTestResult === true
? 'green'
: 'red'
: 'standard') &&
(!hoveringTest && !testingJava
? javaTestResult === true
? 'green'
: 'red'
: 'standard') !== 'standard'
? `var(--color-${
!hoveringTest && !testingJava
? javaTestResult === true
? 'green'
: 'red'
: 'standard'
})`
: undefined,
}"
class="!text-[var(--legacy-button-color,var(--color-base))] [&>svg]:!text-[var(--legacy-button-color,var(--color-primary))]"
@click="testJavaInstallation(activePath, optimalJava?.parsed_version, true)"
@mouseenter="overrideJavaInstall && (hoveringTest = true)"
@mouseleave="hoveringTest = false"
>
<button
:disabled="!overrideJavaInstall || testingJava"
@click="testJavaInstallation(activePath, optimalJava?.parsed_version, true)"
@mouseenter="overrideJavaInstall && (hoveringTest = true)"
@mouseleave="hoveringTest = false"
>
<SpinnerIcon v-if="testingJava" class="animate-spin h-4 w-4" />
<CheckCircleIcon
v-else-if="javaTestResult === true && !hoveringTest"
class="h-4 w-4"
/>
<XCircleIcon v-else-if="javaTestResult !== true && !hoveringTest" class="h-4 w-4" />
<RefreshCwIcon v-else-if="overrideJavaInstall" class="h-4 w-4" />
</button>
</ButtonStyled>
<SpinnerIcon v-if="testingJava" class="animate-spin h-4 w-4" />
<CheckCircleIcon
v-else-if="javaTestResult === true && !hoveringTest"
class="h-4 w-4"
/>
<XCircleIcon v-else-if="javaTestResult !== true && !hoveringTest" class="h-4 w-4" />
<RefreshCwIcon v-else-if="overrideJavaInstall" class="h-4 w-4" />
</Button>
</div>
<div v-if="overrideJavaInstall" class="flex gap-2">
<ButtonStyled>
<button @click="handleDetectJava">
<SearchIcon />
Detect
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="handleBrowseJava">
<FolderSearchIcon />
Browse
</button>
</ButtonStyled>
<Button @click="handleDetectJava">
<SearchIcon />
Detect
</Button>
<Button @click="handleBrowseJava">
<FolderSearchIcon />
Browse
</Button>
</div>
</div>
</div>
@@ -2,13 +2,11 @@
<div v-if="canUnpublish" class="flex flex-col gap-2.5">
<span class="text-lg font-semibold text-contrast">{{ formatMessage(messages.title) }}</span>
<div>
<ButtonStyled color="orange">
<button :disabled="busy" @click="unpublishModal?.show()">
<SpinnerIcon v-if="unpublishing" class="animate-spin" />
<UnlinkIcon v-else class="size-5" />
{{ formatMessage(unpublishing ? messages.unpublishingButton : messages.unpublishButton) }}
</button>
</ButtonStyled>
<Button type="colored" color="orange" :disabled="busy" @click="unpublishModal?.show()">
<SpinnerIcon v-if="unpublishing" class="animate-spin" />
<UnlinkIcon v-else class="size-5" />
{{ formatMessage(unpublishing ? messages.unpublishingButton : messages.unpublishButton) }}
</Button>
</div>
<span class="text-primary">{{ formatMessage(messages.unpublishDescription) }}</span>
</div>
@@ -17,13 +15,11 @@
formatMessage(messages.linkedTitle)
}}</span>
<div>
<ButtonStyled color="orange">
<button :disabled="busy" @click="unlinkModal?.show()">
<SpinnerIcon v-if="unlinking" class="animate-spin" />
<UnlinkIcon v-else class="size-5" />
{{ formatMessage(unlinking ? messages.unlinkingButton : messages.unlinkButton) }}
</button>
</ButtonStyled>
<Button type="colored" color="orange" :disabled="busy" @click="unlinkModal?.show()">
<SpinnerIcon v-if="unlinking" class="animate-spin" />
<UnlinkIcon v-else class="size-5" />
{{ formatMessage(unlinking ? messages.unlinkingButton : messages.unlinkButton) }}
</Button>
</div>
<span class="text-primary">{{ formatMessage(messages.unlinkDescription) }}</span>
</div>
@@ -39,15 +35,11 @@
}}</Admonition>
<template #actions
><div class="flex justify-end gap-2">
<ButtonStyled type="outlined"
><button class="!border" @click="unpublishModal?.hide()">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }}
</button></ButtonStyled
><ButtonStyled color="orange"
><button :disabled="busy" @click="confirmUnpublish">
<UnlinkIcon />{{ formatMessage(messages.unpublishButton) }}
</button></ButtonStyled
>
<Button type="outlined" class="!border" @click="unpublishModal?.hide()">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }} </Button
><Button type="colored" color="orange" :disabled="busy" @click="confirmUnpublish">
<UnlinkIcon />{{ formatMessage(messages.unpublishButton) }}
</Button>
</div></template
>
</NewModal>
@@ -70,15 +62,16 @@
</div>
<template #actions
><div class="flex justify-end gap-2">
<ButtonStyled type="outlined"
><button class="!border" @click="unlinkModal?.hide()">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }}
</button></ButtonStyled
><ButtonStyled color="orange"
><button :disabled="busy || backupBusy" @click="confirmUnlink">
<UnlinkIcon />{{ formatMessage(messages.unlinkButton) }}
</button></ButtonStyled
<Button type="outlined" class="!border" @click="unlinkModal?.hide()">
<XIcon />{{ formatMessage(commonMessages.cancelButton) }} </Button
><Button
type="colored"
color="orange"
:disabled="busy || backupBusy"
@click="confirmUnlink"
>
<UnlinkIcon />{{ formatMessage(messages.unlinkButton) }}
</Button>
</div></template
>
</NewModal>
@@ -88,7 +81,7 @@
import { SpinnerIcon, UnlinkIcon, XIcon } from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
Button,
commonMessages,
defineMessages,
InlineBackupCreator,
@@ -40,29 +40,28 @@
</template>
<template #cell-actions="{ row }">
<div class="flex justify-end">
<ButtonStyled circular type="transparent">
<button
v-tooltip="formatMessage(messages.revokeInvite)"
:aria-label="
formatMessage(messages.revokeInviteWithCode, {
code: row.id,
})
<IconButton
v-tooltip="formatMessage(messages.revokeInvite)"
type="quiet"
:label="
formatMessage(messages.revokeInviteWithCode, {
code: row.id,
})
"
:disabled="revokeInviteMutation.isPending.value || isBusy"
class="text-secondary hover:!filter-none hover:text-red focus-visible:!filter-none"
@click="revokeInviteModal?.show(row.id)"
>
<SpinnerIcon
v-if="
revokeInviteMutation.isPending.value &&
revokeInviteMutation.variables.value?.inviteId === row.id
"
:disabled="revokeInviteMutation.isPending.value || isBusy"
class="text-secondary hover:!filter-none hover:text-red focus-visible:!filter-none"
@click="revokeInviteModal?.show(row.id)"
>
<SpinnerIcon
v-if="
revokeInviteMutation.isPending.value &&
revokeInviteMutation.variables.value?.inviteId === row.id
"
class="animate-spin"
aria-hidden="true"
/>
<XIcon v-else aria-hidden="true" />
</button>
</ButtonStyled>
class="animate-spin"
aria-hidden="true"
/>
<XIcon v-else aria-hidden="true" />
</IconButton>
</div>
</template>
</Table>
@@ -81,8 +80,8 @@
<script setup lang="ts">
import { SpinnerIcon, XIcon } from '@modrinth/assets'
import { IconButton } from '@modrinth/ui'
import {
ButtonStyled,
CopyCode,
defineMessages,
Table,
@@ -1307,14 +1307,16 @@ function getOverflowOptions(item: ContentItem): OverflowMenuOption[] {
const options: OverflowMenuOption[] = []
options.push({
id: formatMessage(commonMessages.showFileButton),
id: 'show-file',
label: formatMessage(commonMessages.showFileButton),
icon: FolderOpenIcon,
action: () => highlightModInInstance(instance.value.id, item.file_path),
})
if (item.project?.slug) {
options.push({
id: formatMessage(commonMessages.copyLinkButton),
id: 'copy-link',
label: formatMessage(commonMessages.copyLinkButton),
icon: ClipboardCopyIcon,
action: async () => {
await navigator.clipboard.writeText(
@@ -96,11 +96,9 @@
</span>
</template>
<template #actions>
<ButtonStyled color="brand"
><button class="!h-10" @click="signInToShare">
<LogInIcon aria-hidden="true" />{{ formatMessage(lockedActionButton) }}
</button></ButtonStyled
>
<Button type="colored" color="brand" size="lg" @click="signInToShare">
<LogInIcon aria-hidden="true" />{{ formatMessage(lockedActionButton) }}
</Button>
</template>
</SharedInstanceShareEmptyState>
@@ -110,21 +108,21 @@
:description="formatMessage(messages.noFriendsInvitedDescription)"
>
<template #actions>
<ButtonStyled color="brand"
><button
class="!h-10"
:disabled="inviteLink.pending.value || !hasRemainingUserSlots"
@click="showInvitePlayers($event)"
>
<SpinnerIcon
v-if="inviteLink.pending.value"
class="animate-spin"
aria-hidden="true"
/><UserPlusIcon v-else aria-hidden="true" />{{
formatMessage(messages.inviteFriendsButton)
}}
</button></ButtonStyled
<Button
type="colored"
color="brand"
size="lg"
:disabled="inviteLink.pending.value || !hasRemainingUserSlots"
@click="showInvitePlayers($event)"
>
<SpinnerIcon
v-if="inviteLink.pending.value"
class="animate-spin"
aria-hidden="true"
/><UserPlusIcon v-else aria-hidden="true" />{{
formatMessage(messages.inviteFriendsButton)
}}
</Button>
</template>
</SharedInstanceShareEmptyState>
</div>
@@ -134,7 +132,7 @@
import { LogInIcon, SpinnerIcon, UserPlusIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
ConfirmUnlinkModal,
defineMessages,
injectAuth,
@@ -11,28 +11,29 @@
clearable
/>
<template v-if="!actionsLocked">
<ButtonStyled type="outlined">
<button
class="flex !h-10 shrink-0 items-center gap-2 !border"
:disabled="pushUpdateDisabled"
@click="management.pushUpdate($event)"
>
<SpinnerIcon v-if="pushUpdatePending" class="animate-spin" aria-hidden="true" />
<UploadIcon v-else aria-hidden="true" />
{{ formatMessage(messages.pushUpdate) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button
class="flex !h-10 shrink-0 items-center gap-2"
:disabled="invitePending || inviteDisabled"
@click="management.invite($event)"
>
<SpinnerIcon v-if="invitePending" class="animate-spin" aria-hidden="true" />
<UserPlusIcon v-else aria-hidden="true" />
Invite friends
</button>
</ButtonStyled>
<Button
type="outlined"
size="lg"
class="shrink-0 !border"
:disabled="pushUpdateDisabled"
@click="management.pushUpdate($event)"
>
<SpinnerIcon v-if="pushUpdatePending" class="animate-spin" aria-hidden="true" />
<UploadIcon v-else aria-hidden="true" />
{{ formatMessage(messages.pushUpdate) }}
</Button>
<Button
type="colored"
color="brand"
size="lg"
class="shrink-0"
:disabled="invitePending || inviteDisabled"
@click="management.invite($event)"
>
<SpinnerIcon v-if="invitePending" class="animate-spin" aria-hidden="true" />
<UserPlusIcon v-else aria-hidden="true" />
Invite friends
</Button>
</template>
</div>
<div v-if="hasMultipleMethods" class="flex flex-wrap items-center gap-1.5">
@@ -120,15 +121,15 @@
</template>
<template #cell-actions="{ row }">
<div v-if="!actionsLocked" class="flex items-center justify-end">
<ButtonStyled circular type="transparent"
><button
v-tooltip="'Revoke access'"
:aria-label="`Revoke access for ${row.username}`"
class="text-secondary hover:!filter-none hover:text-red focus-visible:!filter-none"
@click="management.remove(row)"
>
<XIcon aria-hidden="true" /></button
></ButtonStyled>
<IconButton
v-tooltip="'Revoke access'"
type="quiet"
:label="`Revoke access for ${row.username}`"
class="text-secondary hover:!filter-none hover:text-red focus-visible:!filter-none"
@click="management.remove(row)"
>
<XIcon aria-hidden="true"
/></IconButton>
</div>
</template>
</Table>
@@ -145,10 +146,10 @@ import {
UserPlusIcon,
XIcon,
} from '@modrinth/assets'
import { Button, IconButton } from '@modrinth/ui'
import {
AutoLink,
Avatar,
ButtonStyled,
defineMessages,
type SortDirection,
StyledInput,
@@ -32,16 +32,12 @@
</ul>
</div>
<div class="flex justify-end gap-2 pt-1">
<ButtonStyled type="outlined"
><button class="!border !border-surface-5" @click="modal?.hide()">
<XIcon aria-hidden="true" />{{ formatMessage(commonMessages.cancelButton) }}
</button></ButtonStyled
>
<ButtonStyled color="orange"
><button :disabled="!row" @click="confirm">
<UserXIcon aria-hidden="true" />{{ formatMessage(messages.removeButton) }}
</button></ButtonStyled
>
<Button type="outlined" class="!border !border-surface-5" @click="modal?.hide()">
<XIcon aria-hidden="true" />{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button type="colored" color="orange" :disabled="!row" @click="confirm">
<UserXIcon aria-hidden="true" />{{ formatMessage(messages.removeButton) }}
</Button>
</div>
</div>
</NewModal>
@@ -52,7 +48,7 @@ import { UserXIcon, XIcon } from '@modrinth/assets'
import {
Admonition,
Avatar,
ButtonStyled,
Button,
commonMessages,
defineMessages,
NewModal,
@@ -35,18 +35,14 @@
"
/>
<div class="flex gap-2">
<ButtonStyled type="outlined">
<button class="!h-10" @click="addServerModal?.show()">
<PlusIcon class="size-5" />
{{ formatMessage(messages.addServer) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button class="!h-10 flex items-center gap-2" @click="instancePage.browseServers">
<CompassIcon class="size-5" />
<span>{{ formatMessage(messages.browseServers) }}</span>
</button>
</ButtonStyled>
<Button type="outlined" size="lg" @click="addServerModal?.show()">
<PlusIcon class="size-5" />
{{ formatMessage(messages.addServer) }}
</Button>
<Button type="colored" color="brand" size="lg" @click="instancePage.browseServers">
<CompassIcon class="size-5" />
<span>{{ formatMessage(messages.browseServers) }}</span>
</Button>
</div>
</div>
<div class="flex flex-wrap items-center justify-between gap-2">
@@ -67,16 +63,17 @@
{{ option.label }}
</button>
</div>
<ButtonStyled type="transparent" hover-color-fill="none">
<button :disabled="refreshingAll" @click="refreshAllWorlds">
<RefreshCwIcon :class="refreshingAll ? 'animate-spin' : ''" />
{{
formatMessage(
refreshingAll ? messages.refreshingButton : commonMessages.refreshButton,
)
}}
</button>
</ButtonStyled>
<Button
type="quiet"
:disabled="refreshingAll"
class="hover:!bg-transparent focus-visible:!bg-transparent"
@click="refreshAllWorlds"
>
<RefreshCwIcon :class="refreshingAll ? 'animate-spin' : ''" />
{{
formatMessage(refreshingAll ? messages.refreshingButton : commonMessages.refreshButton)
}}
</Button>
</div>
<div class="flex flex-col w-full gap-2">
<WorldItem
@@ -122,26 +119,22 @@
:description="formatMessage(messages.noWorldsDescription)"
>
<template #actions>
<ButtonStyled type="outlined">
<button class="!h-10" @click="addServerModal?.show()">
<PlusIcon class="size-5" />
{{ formatMessage(messages.addServer) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button class="!h-10 flex items-center gap-2" @click="instancePage.browseServers">
<CompassIcon class="size-5" />
<span>{{ formatMessage(messages.browseServers) }}</span>
</button>
</ButtonStyled>
<Button type="outlined" size="lg" @click="addServerModal?.show()">
<PlusIcon class="size-5" />
{{ formatMessage(messages.addServer) }}
</Button>
<Button type="colored" color="brand" size="lg" @click="instancePage.browseServers">
<CompassIcon class="size-5" />
<span>{{ formatMessage(messages.browseServers) }}</span>
</Button>
</template>
</EmptyState>
</ReadyTransition>
</template>
<script setup lang="ts">
import { CompassIcon, FilterIcon, PlusIcon, RefreshCwIcon, SearchIcon } from '@modrinth/assets'
import { Button } from '@modrinth/ui'
import {
ButtonStyled,
commonMessages,
defineMessages,
EmptyState,
+9 -13
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { LibraryIcon, PlusIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager, NavTabs } from '@modrinth/ui'
import { Button, injectNotificationManager, NavTabs } from '@modrinth/ui'
import { inject, onUnmounted, ref, shallowRef } from 'vue'
import { useRoute } from 'vue-router'
@@ -53,12 +53,10 @@ onUnmounted(() => {
{ label: 'Saved', href: `/library/saved`, shown: false },
]"
/>
<ButtonStyled color="brand">
<button :disabled="offline" @click="showCreationModal?.()">
<PlusIcon />
New instance
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="offline" @click="showCreationModal?.()">
<PlusIcon />
New instance
</Button>
</div>
<template v-if="instances && instances.length > 0">
<RouterView v-if="route.path.startsWith('/library')" :instances="instances" />
@@ -68,12 +66,10 @@ onUnmounted(() => {
<NewInstanceImage />
</div>
<h3>No instances found</h3>
<ButtonStyled color="brand">
<button :disabled="offline" @click="showCreationModal?.()">
<PlusIcon />
Create new instance
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="offline" @click="showCreationModal?.()">
<PlusIcon />
Create new instance
</Button>
</div>
</div>
</template>
+35 -35
View File
@@ -39,40 +39,40 @@
</div>
<div class="controls">
<div class="buttons">
<ButtonStyled circular>
<button class="close" @click="hideImage">
<XIcon aria-hidden="true" />
</button>
</ButtonStyled>
<ButtonStyled circular>
<a
class="open btn icon-only"
target="_blank"
:href="
expandedGalleryItem.raw_url
? expandedGalleryItem.raw_url
: 'https://cdn.modrinth.com/placeholder-banner.svg'
"
>
<ExternalIcon aria-hidden="true" />
</a>
</ButtonStyled>
<ButtonStyled circular>
<button @click="zoomedIn = !zoomedIn">
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" />
</button>
</ButtonStyled>
<ButtonStyled v-if="filteredGallery.length > 1" circular>
<button class="previous" @click="previousImage()">
<LeftArrowIcon aria-hidden="true" />
</button>
</ButtonStyled>
<ButtonStyled v-if="filteredGallery.length > 1" circular>
<button class="next" @click="nextImage()">
<RightArrowIcon aria-hidden="true" />
</button>
</ButtonStyled>
<IconButton label="Close" class="close" @click="hideImage">
<XIcon aria-hidden="true" />
</IconButton>
<ButtonLink
class="open btn icon-only !w-9 !px-0 !rounded-full"
target="_blank"
:href="
expandedGalleryItem.raw_url
? expandedGalleryItem.raw_url
: 'https://cdn.modrinth.com/placeholder-banner.svg'
"
>
<ExternalIcon aria-hidden="true" />
</ButtonLink>
<IconButton label="Toggle zoom" @click="zoomedIn = !zoomedIn">
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" />
</IconButton>
<IconButton
v-if="filteredGallery.length > 1"
label="Previous image"
class="previous"
@click="previousImage()"
>
<LeftArrowIcon aria-hidden="true" />
</IconButton>
<IconButton
v-if="filteredGallery.length > 1"
label="Next image"
class="next"
@click="nextImage()"
>
<RightArrowIcon aria-hidden="true" />
</IconButton>
</div>
</div>
</div>
@@ -90,7 +90,7 @@ import {
RightArrowIcon,
XIcon,
} from '@modrinth/assets'
import { ButtonStyled, Card, useFormatDateTime } from '@modrinth/ui'
import { ButtonLink, Card, IconButton, useFormatDateTime } from '@modrinth/ui'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
+102 -84
View File
@@ -70,87 +70,104 @@
>
<template #actions>
<template v-if="isServerProject">
<ButtonStyled v-if="serverPlaying" color="red" size="large">
<button type="button" @click="handleStopServer">
<StopCircleIcon />
{{ formatMessage(commonMessages.stopButton) }}
</button>
</ButtonStyled>
<ButtonStyled v-else color="brand" size="large">
<button type="button" :disabled="serverInstallLoading" @click="handleClickPlay">
<PlayIcon />
{{
serverInstallLoading
? formatMessage(commonMessages.installingLabel)
: formatMessage(commonMessages.playButton)
}}
</button>
</ButtonStyled>
<ButtonStyled circular size="large">
<button
v-tooltip="formatMessage(commonMessages.addServerToInstanceButton)"
type="button"
:aria-label="formatMessage(commonMessages.addServerToInstanceButton)"
@click="handleAddServerToInstance"
>
<PlusIcon />
</button>
</ButtonStyled>
<ButtonStyled circular size="large" type="transparent">
<TeleportOverflowMenu
:options="serverProjectHeaderMoreActions"
tooltip="More options"
aria-label="More options"
>
<MoreVerticalIcon />
</TeleportOverflowMenu>
</ButtonStyled>
<Button
v-if="serverPlaying"
type="colored"
color="red"
size="xl"
native-type="button"
@click="handleStopServer"
>
<StopCircleIcon />
{{ formatMessage(commonMessages.stopButton) }}
</Button>
<Button
v-else
type="colored"
color="brand"
size="xl"
native-type="button"
:disabled="serverInstallLoading"
@click="handleClickPlay"
>
<PlayIcon />
{{
serverInstallLoading
? formatMessage(commonMessages.installingLabel)
: formatMessage(commonMessages.playButton)
}}
</Button>
<IconButton
v-tooltip="formatMessage(commonMessages.addServerToInstanceButton)"
size="xl"
:label="formatMessage(commonMessages.addServerToInstanceButton)"
native-type="button"
@click="handleAddServerToInstance"
>
<PlusIcon />
</IconButton>
<TeleportOverflowMenu
type="quiet"
size="xl"
label="More options"
:options="serverProjectHeaderMoreActions"
>
<MoreVerticalIcon />
</TeleportOverflowMenu>
</template>
<template v-else>
<ButtonStyled v-if="showSwitchVersion && onVersionsPage" size="large">
<button v-tooltip="formatMessage(messages.alreadyInstalled)" type="button" disabled>
<CheckIcon />
{{ formatMessage(commonMessages.installedLabel) }}
</button>
</ButtonStyled>
<ButtonStyled v-else-if="showSwitchVersion" size="large">
<button type="button" @click="goToVersions">
<SwapIcon />
{{ formatMessage(messages.switchVersion) }}
</button>
</ButtonStyled>
<ButtonStyled v-else color="brand" size="large">
<button
v-tooltip="
installButtonInstalled ? formatMessage(messages.alreadyInstalled) : undefined
"
type="button"
:disabled="installButtonDisabled"
@click="install(null)"
>
<component :is="installButtonIcon" :class="installButtonIconClass" />
{{
installButtonInstalled
? formatMessage(commonMessages.installedLabel)
: installButtonValidating
? formatMessage(commonMessages.validatingLabel)
: installButtonLoading
? formatMessage(commonMessages.installingLabel)
: serverProjectSelected
? formatMessage(commonMessages.selectedLabel)
: formatMessage(commonMessages.installButton)
}}
</button>
</ButtonStyled>
<ButtonStyled circular size="large" type="transparent">
<TeleportOverflowMenu
:options="projectHeaderMoreActions"
tooltip="More options"
aria-label="More options"
>
<MoreVerticalIcon />
</TeleportOverflowMenu>
</ButtonStyled>
<Button
v-if="showSwitchVersion && onVersionsPage"
v-tooltip="formatMessage(messages.alreadyInstalled)"
size="xl"
native-type="button"
disabled
>
<CheckIcon />
{{ formatMessage(commonMessages.installedLabel) }}
</Button>
<Button
v-else-if="showSwitchVersion"
size="xl"
native-type="button"
@click="goToVersions"
>
<SwapIcon />
{{ formatMessage(messages.switchVersion) }}
</Button>
<Button
v-else
v-tooltip="
installButtonInstalled ? formatMessage(messages.alreadyInstalled) : undefined
"
type="colored"
color="brand"
size="xl"
native-type="button"
:disabled="installButtonDisabled"
@click="install(null)"
>
<component :is="installButtonIcon" :class="installButtonIconClass" />
{{
installButtonInstalled
? formatMessage(commonMessages.installedLabel)
: installButtonValidating
? formatMessage(commonMessages.validatingLabel)
: installButtonLoading
? formatMessage(commonMessages.installingLabel)
: serverProjectSelected
? formatMessage(commonMessages.selectedLabel)
: formatMessage(commonMessages.installButton)
}}
</Button>
<TeleportOverflowMenu
type="quiet"
size="xl"
label="More options"
:options="projectHeaderMoreActions"
>
<MoreVerticalIcon />
</TeleportOverflowMenu>
</template>
</template>
</ProjectPageHeader>
@@ -241,11 +258,12 @@ import {
} from '@modrinth/assets'
import {
BrowseInstallHeader,
ButtonStyled,
Button,
commonMessages,
CreationFlowModal,
defineMessages,
getTargetInstallPreferences,
IconButton,
injectNotificationManager,
NavTabs,
ProjectBackgroundGradient,
@@ -571,13 +589,13 @@ const serverProjectHeaderMoreActions = computed(() => [
action: openProjectInBrowser,
},
{
divider: true,
type: 'divider',
},
{
id: 'report',
label: formatMessage(commonMessages.reportButton),
icon: ReportIcon,
color: 'red',
tone: 'red',
action: reportProject,
},
])
@@ -605,13 +623,13 @@ const projectHeaderMoreActions = computed(() => [
action: openProjectInBrowser,
},
{
divider: true,
type: 'divider',
},
{
id: 'report',
label: formatMessage(commonMessages.reportButton),
icon: ReportIcon,
color: 'red',
tone: 'red',
action: reportProject,
},
])
+55 -56
View File
@@ -15,62 +15,62 @@
:dependency-link-creator="createDependencyLink"
>
<template #headerActions>
<ButtonStyled color="brand">
<button
:disabled="installing || (installed && installedVersion === version.id)"
@click="() => version && install(version.id)"
>
<DownloadIcon v-if="!installed" />
<SwapIcon v-else-if="installedVersion !== version.id" />
<CheckIcon v-else />
{{
installing
? formatMessage(messages.installing)
: installed && installedVersion === version.id
? formatMessage(commonMessages.installedLabel)
: installed
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(commonMessages.installButton)
}}
</button>
</ButtonStyled>
<ButtonStyled type="outlined" circular>
<OverflowMenu
v-tooltip="formatMessage(commonMessages.moreOptionsButton)"
:options="[
{
id: 'open-in-browser',
link: `https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`,
external: true,
},
{
id: 'report',
color: 'red',
hoverFilled: true,
link: `https://modrinth.com/report?item=version&itemID=${version.id}`,
external: true,
},
]"
aria-label="More options"
>
<MoreVerticalIcon aria-hidden="true" />
<template #open-in-browser>
<ExternalIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #report>
<ReportIcon aria-hidden="true" /> {{ formatMessage(commonMessages.reportButton) }}
</template>
</OverflowMenu>
</ButtonStyled>
<Button
type="colored"
color="brand"
:disabled="installing || (installed && installedVersion === version.id)"
@click="() => version && install(version.id)"
>
<DownloadIcon v-if="!installed" />
<SwapIcon v-else-if="installedVersion !== version.id" />
<CheckIcon v-else />
{{
installing
? formatMessage(messages.installing)
: installed && installedVersion === version.id
? formatMessage(commonMessages.installedLabel)
: installed
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(commonMessages.installButton)
}}
</Button>
<TeleportOverflowMenu
type="outlined"
label="More options"
:tooltip="formatMessage(commonMessages.moreOptionsButton)"
:options="[
{
id: 'open-in-browser',
label: formatMessage(commonMessages.openInBrowserButton),
type: 'link',
href: `https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`,
target: '_blank',
},
{
id: 'report',
label: formatMessage(commonMessages.reportButton),
type: 'link',
tone: 'red',
href: `https://modrinth.com/report?item=version&itemID=${version.id}`,
target: '_blank',
},
]"
>
<MoreVerticalIcon aria-hidden="true" />
<template #open-in-browser>
<ExternalIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #report>
<ReportIcon aria-hidden="true" /> {{ formatMessage(commonMessages.reportButton) }}
</template>
</TeleportOverflowMenu>
</template>
<template #supplementaryResourceActions="{ file }">
<ButtonStyled>
<a :href="file.url" :download="file.filename" target="_blank">
<DownloadIcon aria-hidden="true" />
{{ formatMessage(messages.downloadInBrowser) }}
</a>
</ButtonStyled>
<ButtonLink :href="file.url" :download="file.filename" target="_blank">
<DownloadIcon aria-hidden="true" />
{{ formatMessage(messages.downloadInBrowser) }}
</ButtonLink>
</template>
</VersionPage>
</div>
@@ -87,12 +87,11 @@ import {
ReportIcon,
VersionIcon,
} from '@modrinth/assets'
import { Button, ButtonLink, TeleportOverflowMenu } from '@modrinth/ui'
import {
ButtonStyled,
commonMessages,
defineMessages,
type DependencyContext,
OverflowMenu,
useVIntl,
VersionPage,
} from '@modrinth/ui'
@@ -9,76 +9,53 @@
:version-link="(version) => buildProjectHref(`/project/${project.id}/version/${version.id}`)"
>
<template #actions="{ version }">
<ButtonStyled
circular
type="transparent"
:color="installed && version.id === installedVersion ? 'standard' : 'green'"
<IconButton
v-tooltip="
!installed
? formatMessage(commonMessages.installButton)
: version.id !== installedVersion
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(messages.alreadyInstalled)
"
type="quiet"
:color="installed && version.id === installedVersion ? undefined : 'green'"
:label="
!installed
? formatMessage(commonMessages.installButton)
: version.id !== installedVersion
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(messages.alreadyInstalled)
"
:disabled="installing || (installed && version.id === installedVersion)"
@click.stop="() => install(version.id)"
>
<button
v-tooltip="
!installed
? formatMessage(commonMessages.installButton)
: version.id !== installedVersion
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(messages.alreadyInstalled)
"
:disabled="installing || (installed && version.id === installedVersion)"
@click.stop="() => install(version.id)"
>
<DownloadIcon v-if="!installed" />
<SwapIcon v-else-if="installed && version.id !== installedVersion" />
<CheckIcon v-else />
</button>
</ButtonStyled>
<ButtonStyled circular type="transparent">
<OverflowMenu
v-if="false"
:options="[
{
id: 'install-elsewhere',
action: () => {},
shown: false && !!instance,
color: 'primary',
hoverFilled: true,
},
{
id: 'open-in-browser',
link: `https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`,
},
]"
aria-label="More options"
>
<MoreVerticalIcon aria-hidden="true" />
<template #install-elsewhere>
<DownloadIcon aria-hidden="true" />
Add to another instance
</template>
<template #open-in-browser>
<ExternalIcon /> {{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
</OverflowMenu>
<a
v-else
v-tooltip="formatMessage(commonMessages.openInBrowserButton)"
:href="`https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`"
target="_blank"
>
<ExternalIcon />
</a>
</ButtonStyled>
<DownloadIcon v-if="!installed" />
<SwapIcon v-else-if="installed && version.id !== installedVersion" />
<CheckIcon v-else />
</IconButton>
<ButtonLink
v-tooltip="formatMessage(commonMessages.openInBrowserButton)"
type="quiet"
:href="`https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`"
target="_blank"
:aria-label="formatMessage(commonMessages.openInBrowserButton)"
class="!w-9 !px-0 !rounded-full"
>
<ExternalIcon />
</ButtonLink>
</template>
</ProjectPageVersions>
</div>
</template>
<script setup>
import { CheckIcon, DownloadIcon, ExternalIcon, MoreVerticalIcon } from '@modrinth/assets'
import { CheckIcon, DownloadIcon, ExternalIcon } from '@modrinth/assets'
import {
ButtonStyled,
ButtonLink,
commonMessages,
defineMessages,
IconButton,
injectNotificationManager,
OverflowMenu,
ProjectPageVersions,
useVIntl,
} from '@modrinth/ui'
@@ -93,7 +93,7 @@
}
}
:where(button, .button, .iconified-button) {
:where(button:not([data-button]), .button, .iconified-button) {
width: fit-content;
}
@@ -12,23 +12,19 @@
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled type="transparent">
<button @click="modal?.hide()">
{{ formatMessage(analyticsChartMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="orange">
<button class="!shadow-none" @click="confirm">
{{ formatMessage(analyticsChartMessages.showAll) }}
</button>
</ButtonStyled>
<Button type="quiet" @click="modal?.hide()">
{{ formatMessage(analyticsChartMessages.cancelButton) }}
</Button>
<Button type="colored" color="orange" @click="confirm">
{{ formatMessage(analyticsChartMessages.showAll) }}
</Button>
</div>
</template>
</NewModal>
</template>
<script setup lang="ts">
import { ButtonStyled, NewModal, useVIntl } from '@modrinth/ui'
import { Button, NewModal, useVIntl } from '@modrinth/ui'
import { analyticsChartMessages } from '../../analytics-messages'
@@ -31,23 +31,22 @@
wrapper-class="w-full sm:w-64"
@focusin="selectSearchInputText"
/>
<ButtonStyled>
<OverflowMenu
class="!shadow-none"
:options="csvExportOptions"
:disabled="isDataLoading || filteredRows.length === 0"
>
<DownloadIcon />
{{ formatMessage(analyticsTableMessages.exportCsvButton) }}
<DropdownIcon />
<template #cumulative-csv>
{{ formatMessage(analyticsTableMessages.cumulativeCsv) }}
</template>
<template #grouped-csv>
{{ formatMessage(analyticsTableMessages.groupedCsv, { groupBy: groupByLabel }) }}
</template>
</OverflowMenu>
</ButtonStyled>
<TeleportOverflowMenu
label="More options"
class="!w-auto !rounded-xl !px-2.5"
:options="csvExportOptions"
:disabled="isDataLoading || filteredRows.length === 0"
>
<DownloadIcon />
{{ formatMessage(analyticsTableMessages.exportCsvButton) }}
<DropdownIcon />
<template #cumulative-csv>
{{ formatMessage(analyticsTableMessages.cumulativeCsv) }}
</template>
<template #grouped-csv>
{{ formatMessage(analyticsTableMessages.groupedCsv, { groupBy: groupByLabel }) }}
</template>
</TeleportOverflowMenu>
</div>
</div>
</template>
@@ -191,9 +190,8 @@
<script setup lang="ts">
import { DownloadIcon, DropdownIcon, SearchIcon, UserIcon } from '@modrinth/assets'
import { TeleportOverflowMenu } from '@modrinth/ui'
import {
ButtonStyled,
OverflowMenu,
type OverflowMenuOption,
Pagination,
StyledInput,
@@ -370,10 +368,12 @@ const csvExportOptions = computed<OverflowMenuOption[]>(() => {
return [
{
id: 'cumulative-csv',
label: formatMessage(analyticsTableMessages.cumulativeCsv),
action: () => downloadCsv('breakdown_only'),
},
{
id: 'grouped-csv',
label: formatMessage(analyticsTableMessages.groupedCsv, { groupBy: groupByLabel.value }),
action: () => downloadCsv('date_breakdown'),
},
]
@@ -384,6 +384,10 @@ const csvExportOptions = computed<OverflowMenuOption[]>(() => {
return [
{
id: mode === 'date_breakdown' ? 'grouped-csv' : 'cumulative-csv',
label:
mode === 'date_breakdown'
? formatMessage(analyticsTableMessages.groupedCsv, { groupBy: groupByLabel.value })
: formatMessage(analyticsTableMessages.cumulativeCsv),
action: () => downloadCsv(mode),
},
]
@@ -6,25 +6,23 @@
{{ formatMessage(analyticsMessages.title) }}
</span>
<div class="flex flex-wrap items-center justify-end gap-2">
<ButtonStyled type="transparent">
<button
type="button"
:disabled="isAnalyticsQueryBuilderDefault"
@click="resetAnalyticsQueryBuilder"
>
{{ formatMessage(analyticsMessages.resetButton) }}
</button>
</ButtonStyled>
<ButtonStyled type="outlined">
<button
type="button"
:disabled="projects.length === 0 || !fetchRequest || isRefetching"
@click="refreshAnalyticsQuery"
>
<RefreshCwIcon :class="isRefetching ? 'animate-spin' : ''" />
{{ formatMessage(analyticsMessages.refreshButton) }}
</button>
</ButtonStyled>
<Button
type="quiet"
native-type="button"
:disabled="isAnalyticsQueryBuilderDefault"
@click="resetAnalyticsQueryBuilder"
>
{{ formatMessage(analyticsMessages.resetButton) }}
</Button>
<Button
type="outlined"
native-type="button"
:disabled="projects.length === 0 || !fetchRequest || isRefetching"
@click="refreshAnalyticsQuery"
>
<RefreshCwIcon :class="isRefetching ? 'animate-spin' : ''" />
{{ formatMessage(analyticsMessages.refreshButton) }}
</Button>
</div>
</div>
<QueryBuilder />
@@ -37,7 +35,7 @@
<script setup lang="ts">
import { RefreshCwIcon } from '@modrinth/assets'
import { ButtonStyled, injectProjectPageContext, useVIntl } from '@modrinth/ui'
import { Button, injectProjectPageContext, useVIntl } from '@modrinth/ui'
import {
createAnalyticsDashboardContext,
@@ -7,6 +7,7 @@
:show-preview-filter-icon="showPreviewFilterIcon"
:preview-trigger-class="previewTriggerClass"
:add-button-class="addButtonClass"
:add-button-size="addButtonSize"
:clear-label="formatMessage(analyticsMessages.resetButton)"
:add-label="resolvedAddLabel"
checkbox-position="right"
@@ -199,6 +200,7 @@
import { BoxIcon, UserIcon } from '@modrinth/assets'
import {
buildDependentsSearchFilters,
type ButtonSize,
DropdownFilterBar,
type DropdownFilterBarCategory,
type DropdownFilterBarOption,
@@ -273,6 +275,7 @@ const props = withDefaults(
showPreviewFilterIcon?: boolean
previewTriggerClass?: string
addButtonClass?: string
addButtonSize?: ButtonSize
showClearAction?: boolean
}>(),
{
@@ -9,6 +9,9 @@
:min-date="ANALYTICS_START_DATE_INPUT_VALUE"
:now-timestamp="queryRefreshTimestamp"
:trigger-class="triggerClass"
:trigger-type="triggerType"
:trigger-size="triggerSize"
:trigger-interaction="triggerInteraction"
@open="handleTimeframeOpen"
@commit="handleTimeframeCommit"
@apply="handleTimeframeApply"
@@ -23,6 +26,9 @@
<script setup lang="ts">
import {
type ButtonInteraction,
type ButtonSize,
type ButtonType,
type ComboboxOption,
TimeFramePicker as BaseTimeFramePicker,
type TimeFramePickerSelection,
@@ -58,6 +64,9 @@ const {
defineProps<{
triggerClass?: string
triggerType?: ButtonType
triggerSize?: ButtonSize
triggerInteraction?: ButtonInteraction
}>()
const draftSelectedGroupBy = ref(selectedGroupBy.value)
@@ -16,6 +16,7 @@
:max-tag-rows="1"
:trigger-class="analyticsQueryChipTriggerClass"
fit-content
trigger-type="base"
checkbox-position="right"
show-selection-actions
@open="handleProjectSelectOpen"
@@ -32,7 +33,7 @@
decoding="async"
/>
<LayersIcon v-else class="size-5 shrink-0 text-primary" />
<span class="min-w-0 flex-1 truncate px-0.5 font-semibold text-primary">
<span class="min-w-0 flex-1 truncate px-0.5 font-semibold text-inherit">
{{ selectedProjectLabel }}
</span>
<ChevronLeftIcon
@@ -145,6 +146,7 @@
:dropdown-min-width="QUERY_BUILDER_DROPDOWN_MIN_WIDTH"
:display-value="selectedGroupByLabel"
:trigger-class="analyticsQueryChipTriggerClass"
trigger-type="base"
>
<template #prefix>
<ClockIcon class="size-5 shrink-0 text-primary" />
@@ -160,6 +162,7 @@
:dropdown-min-width="QUERY_BUILDER_DROPDOWN_MIN_WIDTH"
:trigger-class="analyticsQueryChipTriggerClass"
fit-content
trigger-type="base"
checkbox-position="right"
:placeholder="formatMessage(analyticsMessages.none)"
show-selection-actions
@@ -170,7 +173,7 @@
<div class="flex min-h-7 min-w-0 max-w-full flex-1 items-center gap-1.5 pr-1">
<BlocksIcon class="size-5 shrink-0 text-primary" />
<span
class="min-w-0 flex-1 truncate px-0.5 font-semibold text-primary"
class="min-w-0 flex-1 truncate px-0.5 font-semibold text-inherit"
:title="mobileSelectedBreakdownLabel"
>
{{ mobileSelectedBreakdownLabel }}
@@ -191,6 +194,7 @@
show-preview-filter-icon
:show-clear-action="false"
:add-button-class="analyticsQueryAddFilterButtonClass"
add-button-size="lg"
/>
</div>
@@ -214,6 +218,7 @@
:searchable="projectOptions.length > 6"
fuzzy-search
:max-tag-rows="1"
trigger-type="base"
checkbox-position="right"
show-selection-actions
@open="handleProjectSelectOpen"
@@ -243,7 +248,7 @@
class="size-5 shrink-0 text-primary"
/>
<BoxIcon v-else class="size-5 shrink-0 text-primary" />
<span class="min-w-0 flex-1 truncate px-1.5 font-semibold text-primary">
<span class="min-w-0 flex-1 truncate px-1.5 font-semibold text-inherit">
{{ selectedProjectLabel }}
</span>
</div>
@@ -372,18 +377,19 @@
:options="groupByOptions"
:max-height="QUERY_BUILDER_DROPDOWN_MAX_HEIGHT"
:dropdown-min-width="QUERY_BUILDER_DROPDOWN_MIN_WIDTH"
trigger-type="base"
/>
</div>
</div>
<ButtonStyled v-if="!isTimeframeAndGroupByDefault" type="transparent">
<button
type="button"
:disabled="isTimeframeAndGroupByDefault"
@click="resetTimeframeAndGroupBy"
>
{{ formatMessage(analyticsMessages.resetButton) }}
</button>
</ButtonStyled>
<Button
v-if="!isTimeframeAndGroupByDefault"
type="quiet"
native-type="button"
:disabled="isTimeframeAndGroupByDefault"
@click="resetTimeframeAndGroupBy"
>
{{ formatMessage(analyticsMessages.resetButton) }}
</Button>
</div>
<div class="flex flex-wrap items-start gap-2">
@@ -403,6 +409,7 @@
:max-height="QUERY_BUILDER_DROPDOWN_MAX_HEIGHT"
:dropdown-width="QUERY_BUILDER_DROPDOWN_MIN_WIDTH"
:dropdown-min-width="QUERY_BUILDER_DROPDOWN_MIN_WIDTH"
trigger-type="base"
checkbox-position="right"
:placeholder="formatMessage(analyticsMessages.none)"
show-selection-actions
@@ -412,7 +419,7 @@
<template #input-content="{ isOpen, openDirection }">
<div class="flex min-h-7 min-w-0 flex-1 items-center gap-1.5 pr-1">
<span
class="min-w-0 flex-1 truncate font-semibold text-primary"
class="min-w-0 flex-1 truncate font-semibold text-inherit"
:title="selectedBreakdownLabel"
>
{{ selectedBreakdownLabel }}
@@ -453,8 +460,8 @@ import {
LayersIcon,
UserIcon,
} from '@modrinth/assets'
import { Button } from '@modrinth/ui'
import {
ButtonStyled,
Combobox,
type ComboboxOption,
MultiSelect,
@@ -504,7 +511,7 @@ import TimeFramePicker from './TimeframePicker.vue'
const QUERY_BUILDER_DROPDOWN_MAX_HEIGHT = 500
const QUERY_BUILDER_DROPDOWN_MIN_WIDTH = '14rem'
const analyticsQueryChipTriggerClass = 'h-10 '
const analyticsQueryAddFilterButtonClass = '!h-10 max-w-full !w-max !px-3.5 flex !gap-2'
const analyticsQueryAddFilterButtonClass = 'max-w-full !w-max !px-3.5 flex !gap-2'
const projectOptionCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
type ProjectSelectionPreset = 'user' | 'all'
@@ -12,12 +12,16 @@
{{ formatMessage(analyticsStatCardMessages.monetizationBannerBody) }}
</div>
<template #actions>
<ButtonStyled color="blue">
<a href="https://modrinth.com/legal/cmp-info" target="_blank" class="w-fit !px-4">
{{ formatMessage(analyticsStatCardMessages.monetizationBannerLearnMore) }}
<RightArrowIcon aria-hidden="true" />
</a>
</ButtonStyled>
<ButtonLink
type="colored"
color="blue"
href="https://modrinth.com/legal/cmp-info"
target="_blank"
class="w-fit !px-4"
>
{{ formatMessage(analyticsStatCardMessages.monetizationBannerLearnMore) }}
<RightArrowIcon aria-hidden="true" />
</ButtonLink>
</template>
</Admonition>
<div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
@@ -39,7 +43,7 @@
<script setup lang="ts">
import { RightArrowIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, useFormatNumber, useVIntl } from '@modrinth/ui'
import { Admonition, ButtonLink, useFormatNumber, useVIntl } from '@modrinth/ui'
import { useLocalStorage } from '@vueuse/core'
import {
+20 -9
View File
@@ -1,13 +1,24 @@
<template>
<div>
<ButtonStyled v-if="!!slots.title" :type="type">
<button class="!w-full" @click="() => (isOpen ? close() : open())">
<slot name="title" /><DropdownIcon
class="ml-auto size-5 text-contrast transition-transform duration-300"
:class="{ 'rotate-180': isOpen }"
/>
</button>
</ButtonStyled>
<Button
v-if="!!slots.title"
:type="
type === 'transparent'
? 'quiet'
: type === 'standard'
? 'base'
: type === 'highlight' || type === 'highlight-colored-text' || type === 'chip'
? 'base'
: type
"
class="!w-full"
@click="() => (isOpen ? close() : open())"
>
<slot name="title" /><DropdownIcon
class="ml-auto size-5 text-contrast transition-transform duration-300"
:class="{ 'rotate-180': isOpen }"
/>
</Button>
<div class="accordion-content" :class="{ open: isOpen }">
<div>
<div :class="{ 'mt-2': !!slots.title }" v-bind="$attrs" :inert="!isOpen">
@@ -20,7 +31,7 @@
<script setup lang="ts">
import { DropdownIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import { Button } from '@modrinth/ui'
const props = withDefaults(
defineProps<{
@@ -40,12 +40,10 @@
</div>
</div>
<div>
<ButtonStyled color="red">
<button @click="emit('revoke', authorization.app_id)">
<XCircleIcon />
{{ formatMessage(messages.revokeAction) }}
</button>
</ButtonStyled>
<Button type="colored" color="red" @click="emit('revoke', authorization.app_id)">
<XCircleIcon />
{{ formatMessage(messages.revokeAction) }}
</Button>
</div>
</div>
<div v-if="showUnofficialDisclosure" class="flex items-center gap-1 text-orange">
@@ -82,7 +80,7 @@ import type { Labrinth } from '@modrinth/api-client'
import { BadgeCheckIcon, CheckIcon, IssuesIcon, XCircleIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
commonMessages,
defineMessages,
IntlFormatted,
@@ -57,18 +57,14 @@
</div>
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled>
<button @click="hide">
<XIcon />
Cancel
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button :disabled="!isConfirmEnabled" @click="onConfirmClick">
<TransferIcon />
Transfer ownership
</button>
</ButtonStyled>
<Button @click="hide">
<XIcon />
Cancel
</Button>
<Button type="colored" color="red" :disabled="!isConfirmEnabled" @click="onConfirmClick">
<TransferIcon />
Transfer ownership
</Button>
</div>
</template>
</NewModal>
@@ -76,7 +72,7 @@
<script setup lang="ts">
import { RightArrowIcon, TransferIcon, XIcon } from '@modrinth/assets'
import { Admonition, Avatar, ButtonStyled, NewModal, StyledInput } from '@modrinth/ui'
import { Admonition, Avatar, Button, NewModal, StyledInput } from '@modrinth/ui'
import { computed, ref } from 'vue'
const props = defineProps<{
@@ -64,18 +64,14 @@
</div>
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled>
<button @click="hide">
<XIcon />
Cancel
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button :disabled="!isConfirmEnabled" @click="onConfirmClick">
<TransferIcon />
Transfer ownership
</button>
</ButtonStyled>
<Button @click="hide">
<XIcon />
Cancel
</Button>
<Button type="colored" color="red" :disabled="!isConfirmEnabled" @click="onConfirmClick">
<TransferIcon />
Transfer ownership
</Button>
</div>
</template>
</NewModal>
@@ -83,7 +79,7 @@
<script setup lang="ts">
import { RightArrowIcon, TransferIcon, XIcon } from '@modrinth/assets'
import { Admonition, Avatar, ButtonStyled, NewModal, StyledInput } from '@modrinth/ui'
import { Admonition, Avatar, Button, NewModal, StyledInput } from '@modrinth/ui'
import { computed, ref } from 'vue'
const props = defineProps<{
@@ -7,9 +7,9 @@ import {
ToggleRightIcon,
TwitterIcon,
} from '@modrinth/assets'
import { ButtonLink } from '@modrinth/ui'
import {
AutoLink,
ButtonStyled,
defineMessage,
defineMessages,
injectNotificationManager,
@@ -271,31 +271,30 @@ function developerModeIncrement() {
class="text-logo button-base h-6 w-auto text-contrast lg:h-8"
@click="developerModeIncrement()"
/>
<ButtonStyled v-if="flags.developerMode" circular type="transparent" color="brand">
<nuxt-link
v-tooltip="formatMessage(commonSettingsMessages.featureFlags)"
to="/settings/flags"
>
<ToggleRightIcon />
</nuxt-link>
</ButtonStyled>
<ButtonLink
v-if="flags.developerMode"
v-tooltip="formatMessage(commonSettingsMessages.featureFlags)"
type="quiet"
color="brand"
to="/settings/flags"
class="!w-9 !rounded-full !px-0"
>
<ToggleRightIcon />
</ButtonLink>
</div>
<div class="flex flex-wrap justify-center gap-px sm:-mx-2">
<ButtonStyled
<ButtonLink
v-for="(social, index) in socialLinks"
:key="`footer-social-${index}`"
circular
type="transparent"
v-tooltip="formatMessage(social.label)"
type="quiet"
:href="social.href"
target="_blank"
:rel="`noopener${social.rel ? ` ${social.rel}` : ''}`"
class="!w-9 !rounded-full !px-0"
>
<a
v-tooltip="formatMessage(social.label)"
:href="social.href"
target="_blank"
:rel="`noopener${social.rel ? ` ${social.rel}` : ''}`"
>
<component :is="social.icon" class="h-5 w-5" />
</a>
</ButtonStyled>
<component :is="social.icon" class="h-5 w-5" />
</ButtonLink>
</div>
<div class="mt-auto flex flex-wrap justify-center gap-3 md:flex-col">
<p class="m-0">
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { CheckIcon, MailIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, injectModrinthClient, useVIntl } from '@modrinth/ui'
import { Button, defineMessages, injectModrinthClient, useVIntl } from '@modrinth/ui'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
@@ -61,12 +61,16 @@ async function subscribe() {
</script>
<template>
<ButtonStyled v-if="isSuccess && showSubscribeButton" color="brand" type="outlined">
<button v-tooltip="formatMessage(messages.tooltipSubscribe)" @click="subscribe">
<template v-if="!showSubscriptionConfirmation">
<MailIcon /> {{ formatMessage(messages.subscribe) }}
</template>
<template v-else> <CheckIcon /> {{ formatMessage(messages.subscribed) }} </template>
</button>
</ButtonStyled>
<Button
v-if="isSuccess && showSubscribeButton"
v-tooltip="formatMessage(messages.tooltipSubscribe)"
type="outlined"
class="!text-brand !shadow-[inset_0_0_0_1px_var(--color-brand)] [&>svg]:!text-brand"
@click="subscribe"
>
<template v-if="!showSubscriptionConfirmation">
<MailIcon /> {{ formatMessage(messages.subscribe) }}
</template>
<template v-else> <CheckIcon /> {{ formatMessage(messages.subscribed) }} </template>
</Button>
</template>
@@ -46,18 +46,18 @@
class="flex flex-wrap items-center gap-3"
:class="{ 'gap-2': compact }"
>
<ButtonStyled color="brand">
<button @click="performActionByTitle(notification, 'Accept')">
<CheckIcon />
Accept
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button @click="performActionByTitle(notification, 'Deny')">
<XIcon />
Decline
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
@click="performActionByTitle(notification, 'Accept')"
>
<CheckIcon />
Accept
</Button>
<Button type="colored" color="red" @click="performActionByTitle(notification, 'Deny')">
<XIcon />
Decline
</Button>
</div>
<div
class="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[var(--color-text-secondary)]"
@@ -276,9 +276,53 @@
</span>
<div v-if="compact" class="notification__actions">
<template v-if="type === 'team_invite' || type === 'organization_invite'">
<ButtonStyled circular color="brand" type="transparent">
<button
v-tooltip="`Accept`"
<IconButton
v-tooltip="`Accept`"
type="quiet"
color="brand"
:label="`Accept`"
@click="
() => {
acceptTeamInvite(notification.body.team_id)
read()
}
"
>
<CheckIcon />
</IconButton>
<IconButton
v-tooltip="`Decline`"
type="quiet"
color="red"
:label="`Decline`"
@click="
() => {
removeSelfFromTeam(notification.body.team_id)
read()
}
"
>
<XIcon />
</IconButton>
</template>
<IconButton
v-else-if="!notification.read"
v-tooltip="`Mark as read`"
type="quiet"
:label="`Mark as read`"
@click="read()"
>
<XIcon />
</IconButton>
</div>
<div v-else class="notification__actions">
<div v-if="type !== null" class="input-group">
<template
v-if="(type === 'team_invite' || type === 'organization_invite') && !notification.read"
>
<Button
type="colored"
color="brand"
@click="
() => {
acceptTeamInvite(notification.body.team_id)
@@ -287,11 +331,11 @@
"
>
<CheckIcon />
</button>
</ButtonStyled>
<ButtonStyled circular color="red" type="transparent">
<button
v-tooltip="`Decline`"
Accept
</Button>
<Button
type="colored"
color="red"
@click="
() => {
removeSelfFromTeam(notification.body.team_id)
@@ -300,75 +344,40 @@
"
>
<XIcon />
</button>
</ButtonStyled>
</template>
<ButtonStyled v-else-if="!notification.read" circular type="transparent">
<button v-tooltip="`Mark as read`" @click="read()">
<XIcon />
</button>
</ButtonStyled>
</div>
<div v-else class="notification__actions">
<div v-if="type !== null" class="input-group">
<template
v-if="(type === 'team_invite' || type === 'organization_invite') && !notification.read"
>
<ButtonStyled color="brand">
<button
@click="
() => {
acceptTeamInvite(notification.body.team_id)
read()
}
"
>
<CheckIcon />
Accept
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button
@click="
() => {
removeSelfFromTeam(notification.body.team_id)
read()
}
"
>
<XIcon />
Decline
</button>
</ButtonStyled>
Decline
</Button>
</template>
<ButtonStyled v-else-if="!notification.read">
<button @click="read()">
<CheckIcon />
Mark as read
</button>
</ButtonStyled>
<Button v-else-if="!notification.read" @click="read()">
<CheckIcon />
Mark as read
</Button>
<CopyCode v-if="flags.developerMode" :text="notification.id" />
</div>
<div v-else class="input-group">
<ButtonStyled v-if="notification.link && notification.link !== '#'">
<nuxt-link :to="notification.link" target="_blank">
<ExternalIcon />
Open link
</nuxt-link>
</ButtonStyled>
<ButtonStyled v-for="(action, actionIndex) in notification.actions" :key="actionIndex">
<button @click="performAction(notification, actionIndex)">
<CheckIcon v-if="action.title === 'Accept'" />
<XIcon v-else-if="action.title === 'Deny'" />
{{ action.title }}
</button>
</ButtonStyled>
<ButtonStyled v-if="notification.actions.length === 0 && !notification.read">
<button @click="performAction(notification, null)">
<CheckIcon />
Mark as read
</button>
</ButtonStyled>
<ButtonLink
v-if="notification.link && notification.link !== '#'"
:to="notification.link"
target="_blank"
>
<ExternalIcon />
Open link
</ButtonLink>
<Button
v-for="(action, actionIndex) in notification.actions"
:key="actionIndex"
@click="performAction(notification, actionIndex)"
>
<CheckIcon v-if="action.title === 'Accept'" />
<XIcon v-else-if="action.title === 'Deny'" />
{{ action.title }}
</Button>
<Button
v-if="notification.actions.length === 0 && !notification.read"
@click="performAction(notification, null)"
>
<CheckIcon />
Mark as read
</Button>
<CopyCode v-if="flags.developerMode" :text="notification.id" />
</div>
</div>
@@ -390,10 +399,12 @@ import {
} from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
ButtonLink,
Categories,
CopyCode,
DoubleIcon,
IconButton,
injectModrinthClient,
injectNotificationManager,
ProjectStatusBadge,
@@ -38,21 +38,19 @@
<template #actions>
<PageHeaderActions>
<ButtonStyled v-if="canManage" size="large">
<nuxt-link :to="`/organization/${organization.slug}/settings`">
<SettingsIcon />
{{ formatMessage(messages.manage) }}
</nuxt-link>
</ButtonStyled>
<ButtonStyled circular size="large" type="transparent">
<TeleportOverflowMenu
:options="moreActions"
:tooltip="formatMessage(commonMessages.moreOptionsButton)"
:aria-label="formatMessage(commonMessages.moreOptionsButton)"
>
<MoreVerticalIcon />
</TeleportOverflowMenu>
</ButtonStyled>
<ButtonLink v-if="canManage" size="xl" :to="`/organization/${organization.slug}/settings`">
<SettingsIcon />
{{ formatMessage(messages.manage) }}
</ButtonLink>
<TeleportOverflowMenu
type="quiet"
size="xl"
:label="formatMessage(commonMessages.moreOptionsButton)"
:tooltip="formatMessage(commonMessages.moreOptionsButton)"
:options="moreActions"
>
<MoreVerticalIcon />
</TeleportOverflowMenu>
</PageHeaderActions>
</template>
</PageHeader>
@@ -68,18 +66,17 @@ import {
SettingsIcon,
UsersIcon,
} from '@modrinth/assets'
import { ButtonLink, TeleportOverflowMenu } from '@modrinth/ui'
import {
Avatar,
ButtonStyled,
commonMessages,
defineMessages,
type OverflowMenuOption,
PageHeader,
PageHeaderActions,
PageHeaderBadgeItem,
PageHeaderMetadata,
PageHeaderMetadataNumberItem,
TeleportOverflowMenu,
type TeleportOverflowMenuItem,
useFormatNumber,
useVIntl,
} from '@modrinth/ui'
@@ -135,7 +132,7 @@ const emit = defineEmits<{
const { formatMessage } = useVIntl()
const formatNumber = useFormatNumber()
const moreActions = computed<TeleportOverflowMenuItem[]>(() => [
const moreActions = computed<OverflowMenuOption[]>(() => [
{
id: 'manage-projects',
label: formatMessage(messages.manageProjects),
@@ -143,10 +140,7 @@ const moreActions = computed<TeleportOverflowMenuItem[]>(() => [
action: () => emit('manageProjects'),
shown: props.canManage,
},
{
divider: true,
shown: props.canManage,
},
{ type: 'divider', shown: props.canManage },
{
id: 'copy-id',
label: formatMessage(commonMessages.copyIdButton),
@@ -65,38 +65,37 @@
</div>
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled type="outlined">
<button @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="selectedProjects.length === 0" @click="submitTransfer()">
<TransferIcon />
{{
formatMessage(messages.transferSelectedProjects, {
count: selectedProjects.length,
})
}}
</button>
</ButtonStyled>
<Button type="outlined" @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button
type="colored"
color="brand"
:disabled="selectedProjects.length === 0"
@click="submitTransfer()"
>
<TransferIcon />
{{
formatMessage(messages.transferSelectedProjects, {
count: selectedProjects.length,
})
}}
</Button>
</div>
</template>
</NewModal>
<ButtonStyled>
<button @click="show($event)">
<TransferIcon />
<span>{{ formatMessage(messages.transferProjectsTitle) }}</span>
</button>
</ButtonStyled>
<Button @click="show($event)">
<TransferIcon />
<span>{{ formatMessage(messages.transferProjectsTitle) }}</span>
</Button>
</div>
</template>
<script setup>
import { BoxIcon, TransferIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
Checkbox,
commonMessages,
CopyCode,
@@ -1,63 +1,67 @@
<template>
<ButtonStyled size="large" circular>
<PopoutMenu
v-if="authUser"
:tooltip="
saved ? formatMessage(commonMessages.savedLabel) : formatMessage(commonMessages.saveButton)
"
from="top-right"
:aria-label="formatMessage(commonMessages.saveButton)"
:dropdown-id="`${baseId}-save`"
>
<TeleportPopoutMenu
v-if="authUser"
icon-only
size="xl"
:label="
saved ? formatMessage(commonMessages.savedLabel) : formatMessage(commonMessages.saveButton)
"
:tooltip="
saved ? formatMessage(commonMessages.savedLabel) : formatMessage(commonMessages.saveButton)
"
placement="top-end"
>
<template #trigger>
<BookmarkIcon aria-hidden="true" :fill="saved ? 'currentColor' : 'none'" />
<template #menu>
<StyledInput
v-model="displayCollectionsSearch"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
wrapper-class="menu-search"
/>
<div v-if="filteredCollections.length > 0" class="collections-list text-primary">
<Checkbox
v-for="option in filteredCollections"
:key="option.id"
:model-value="option.projects.includes(projectId)"
class="popout-checkbox"
@update:model-value="() => collectProject(option, projectId)"
>
{{ option.name }}
</Checkbox>
</div>
</template>
<template #panel>
<StyledInput
v-model="displayCollectionsSearch"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
wrapper-class="menu-search"
/>
<div v-if="filteredCollections.length > 0" class="collections-list text-primary">
<Checkbox
v-for="option in filteredCollections"
:key="option.id"
:model-value="option.projects.includes(projectId)"
class="popout-checkbox"
@update:model-value="() => collectProject(option, projectId)"
>
{{ option.name }}
</Checkbox>
</div>
<div v-else class="menu-text">
<p class="popout-text">{{ noCollectionsLabel }}</p>
</div>
<ButtonStyled>
<button class="mx-3 mb-3" @click="createCollection">
<PlusIcon aria-hidden="true" />
{{ createNewCollectionLabel }}
</button>
</ButtonStyled>
</template>
</PopoutMenu>
<nuxt-link
v-else
v-tooltip="formatMessage(commonMessages.saveButton)"
:to="signInRoute"
:aria-label="formatMessage(commonMessages.saveButton)"
>
<BookmarkIcon aria-hidden="true" />
</nuxt-link>
</ButtonStyled>
<div v-else class="menu-text">
<p class="popout-text">{{ noCollectionsLabel }}</p>
</div>
<Button class="mx-3 mb-3" @click="createCollection">
<PlusIcon aria-hidden="true" />
{{ createNewCollectionLabel }}
</Button>
</template>
</TeleportPopoutMenu>
<ButtonLink
v-else
v-tooltip="formatMessage(commonMessages.saveButton)"
size="xl"
:to="signInRoute"
:aria-label="formatMessage(commonMessages.saveButton)"
class="!w-12 !rounded-full !px-0"
>
<BookmarkIcon aria-hidden="true" />
</ButtonLink>
</template>
<script setup lang="ts">
import { BookmarkIcon, PlusIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
ButtonLink,
Checkbox,
commonMessages,
PopoutMenu,
StyledInput,
TeleportPopoutMenu,
useVIntl,
} from '@modrinth/ui'
import { computed, ref } from 'vue'
@@ -48,37 +48,34 @@
</span>
</div>
</div>
<ButtonStyled
<ButtonLink
v-if="primaryFile && showDownload"
:color="color"
:type="type"
:circular="circular"
v-tooltip="circular ? formatMessage(messages.download) : null"
:type="downloadButtonType"
:color="downloadButtonColor"
:href="primaryFileDownloadUrl"
:download="primaryFile.filename"
:aria-label="
formatMessage(messages.downloadVersion, {
version: version.version_number,
})
"
:class="circular ? '!w-9 !rounded-full !px-0' : undefined"
@click="emit('download')"
>
<a
v-tooltip="circular ? formatMessage(messages.download) : null"
:href="primaryFileDownloadUrl"
:download="primaryFile.filename"
:aria-label="
formatMessage(messages.downloadVersion, {
version: version.version_number,
})
"
@click="emit('download')"
>
<DownloadIcon aria-hidden="true" />
<template v-if="!circular">
{{ formatMessage(messages.download) }}
</template>
</a>
</ButtonStyled>
<DownloadIcon aria-hidden="true" />
<template v-if="!circular">
{{ formatMessage(messages.download) }}
</template>
</ButtonLink>
</div>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { DownloadIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
import { ButtonLink } from '@modrinth/ui'
import {
ButtonStyled,
type CdnDownloadReason,
defineMessages,
truncatedTooltip,
@@ -161,6 +158,17 @@ const publishedLabel = computed(() =>
capitalizeString(formatRelativeTime(props.version.date_published)),
)
const publishedTooltip = computed(() => formatDateTime(props.version.date_published))
const downloadButtonType = computed(() => {
if (props.type === 'transparent') return 'quiet'
return props.color === 'brand' ? 'colored' : 'base'
})
const downloadButtonColor = computed(() =>
downloadButtonType.value === 'colored' || downloadButtonType.value === 'quiet'
? props.color === 'brand'
? 'brand'
: undefined
: undefined,
)
const primaryFileSizeLabel = computed(() => {
if (!primaryFile.value) return ''
return formatBytes(primaryFile.value.size)
@@ -56,26 +56,27 @@
{{ metadataLabel }}
</span>
</span>
<ButtonStyled v-if="dependency.downloadHref" circular type="transparent">
<a
v-tooltip="downloadTooltip"
:href="dependency.downloadHref"
:download="dependency.filename"
:aria-label="downloadTooltip"
@click="emit('download')"
>
<DownloadIcon aria-hidden="true" class="size-6 text-secondary" />
</a>
</ButtonStyled>
<ButtonStyled v-else circular type="transparent">
<button
v-tooltip="dependency.unavailableTooltip"
disabled
:aria-label="dependency.unavailableTooltip"
>
<DownloadIcon aria-hidden="true" class="size-6 text-secondary" />
</button>
</ButtonStyled>
<ButtonLink
v-if="dependency.downloadHref"
v-tooltip="downloadTooltip"
type="quiet"
:href="dependency.downloadHref"
:download="dependency.filename"
:aria-label="downloadTooltip"
class="!w-9 !rounded-full !px-0"
@click="emit('download')"
>
<DownloadIcon aria-hidden="true" class="size-6 text-secondary" />
</ButtonLink>
<IconButton
v-else
v-tooltip="dependency.unavailableTooltip"
type="quiet"
:label="dependency.unavailableTooltip"
disabled
>
<DownloadIcon aria-hidden="true" class="size-6 text-secondary" />
</IconButton>
</div>
<div
v-for="childDependency in dependency.dependencies"
@@ -100,8 +101,9 @@
import { DownloadIcon, PackageIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
ButtonLink,
defineMessages,
IconButton,
TagItem,
truncatedTooltip,
useFormatBytes,
@@ -7,18 +7,18 @@
class="modrinth-app-section contents"
>
<div class="flex flex-col items-center">
<ButtonStyled color="brand">
<a
class="!min-h-10 w-fit no-underline"
:href="`modrinth://mod/${project.slug}`"
@click="installWithApp"
>
<ModrinthIcon aria-hidden="true" />
<span class="min-w-0 text-center">
{{ formatMessage(messages.installWithModrinthApp) }}
</span>
</a>
</ButtonStyled>
<ButtonLink
type="colored"
color="brand"
class="!min-h-10 w-fit no-underline"
:href="`modrinth://mod/${project.slug}`"
@click="installWithApp"
>
<ModrinthIcon aria-hidden="true" />
<span class="min-w-0 text-center">
{{ formatMessage(messages.installWithModrinthApp) }}
</span>
</ButtonLink>
<Accordion ref="getModrinthAppAccordion">
<nuxt-link class="mt-2 flex justify-center text-brand-blue hover:underline" to="/app">
{{ formatMessage(messages.dontHaveModrinthApp) }}
@@ -39,7 +39,7 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { ModrinthIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
import { ButtonLink, defineMessages, useVIntl } from '@modrinth/ui'
import type { DisplayProjectType } from '@modrinth/utils'
import { ref } from 'vue'
@@ -49,42 +49,50 @@
</template>
<template v-if="showDependencyDownloadActions" #actions>
<div class="flex flex-wrap justify-end gap-2 p-2">
<ButtonStyled>
<button
class="!shadow-none"
:disabled="!!downloadingActionType || !dependencyDownloadFilesLoaded"
@click="downloadSelectedVersionZip"
>
<SpinnerIcon
v-if="downloadingActionType === 'zip'"
aria-hidden="true"
class="animate-spin"
/>
<DownloadIcon v-else aria-hidden="true" />
{{ formatMessage(messages.downloadAsZip) }}
</button>
</ButtonStyled>
<JoinedButtons
v-if="hasRecommendedDownloadFiles"
color="brand"
:actions="downloadWithRecommendedActions"
<Button
:disabled="!!downloadingActionType || !dependencyDownloadFilesLoaded"
/>
<ButtonStyled v-else color="brand">
<button
class="!shadow-none"
:disabled="!!downloadingActionType || !dependencyDownloadFilesLoaded"
@click="downloadFilesWithDependencies"
>
<SpinnerIcon
v-if="downloadingActionType === 'dependencies'"
aria-hidden="true"
class="animate-spin"
/>
<DownloadIcon v-else aria-hidden="true" />
{{ formatMessage(messages.downloadWithDependencies) }}
</button>
</ButtonStyled>
@click="downloadSelectedVersionZip"
>
<SpinnerIcon
v-if="downloadingActionType === 'zip'"
aria-hidden="true"
class="animate-spin"
/>
<DownloadIcon v-else aria-hidden="true" />
{{ formatMessage(messages.downloadAsZip) }}
</Button>
<SplitButton
v-if="hasRecommendedDownloadFiles"
type="colored"
color="brand"
:options="downloadWithRecommendedOptions"
:menu-label="formatMessage(messages.downloadWithRecommended)"
:disabled="!!downloadingActionType || !dependencyDownloadFilesLoaded"
@click="downloadFilesWithDependencies"
>
<SpinnerIcon
v-if="downloadingActionType === 'dependencies'"
aria-hidden="true"
class="animate-spin"
/>
<DownloadIcon v-else aria-hidden="true" />
{{ formatMessage(messages.downloadWithDependencies) }}
</SplitButton>
<Button
v-else
type="colored"
color="brand"
:disabled="!!downloadingActionType || !dependencyDownloadFilesLoaded"
@click="downloadFilesWithDependencies"
>
<SpinnerIcon
v-if="downloadingActionType === 'dependencies'"
aria-hidden="true"
class="animate-spin"
/>
<DownloadIcon v-else aria-hidden="true" />
{{ formatMessage(messages.downloadWithDependencies) }}
</Button>
</div>
</template>
</NewModal>
@@ -95,15 +103,15 @@ import type { Labrinth } from '@modrinth/api-client'
import { DownloadIcon, SpinnerIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Button,
type CdnDownloadReason,
defineMessages,
injectModrinthClient,
injectNotificationManager,
type JoinedButtonAction,
JoinedButtons,
NewModal,
type OverflowMenuOption,
ServersPromo,
SplitButton,
truncatedTooltip,
useDebugLogger,
useVIntl,
@@ -342,13 +350,7 @@ const showDependencyDownloadActions = computed(
hasRecommendedDownloadFiles.value),
)
const downloadWithRecommendedActions = computed<JoinedButtonAction[]>(() => [
{
id: 'download-with-dependencies',
label: formatMessage(messages.downloadWithDependencies),
icon: downloadingActionType.value === 'dependencies' ? SpinnerIcon : DownloadIcon,
action: () => void downloadFilesWithDependencies(),
},
const downloadWithRecommendedOptions = computed<OverflowMenuOption[]>(() => [
{
id: 'download-with-recommended',
label: formatMessage(messages.downloadWithRecommended),
@@ -6,26 +6,22 @@
</p>
<p v-else>{{ getFormattedMessage(messages.invitationNoRole) }}</p>
<div class="input-group">
<ButtonStyled color="brand">
<button class="brand-button" @click="acceptInvite()">
<CheckIcon />
{{ getFormattedMessage(commonMessages.acceptButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button @click="declineInvite">
<XIcon />
{{ getFormattedMessage(commonMessages.declineButton) }}
</button>
</ButtonStyled>
<Button type="colored" color="brand" class="brand-button" @click="acceptInvite()">
<CheckIcon />
{{ getFormattedMessage(commonMessages.acceptButton) }}
</Button>
<Button type="colored" color="red" @click="declineInvite">
<XIcon />
{{ getFormattedMessage(commonMessages.declineButton) }}
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import { CheckIcon, XIcon } from '@modrinth/assets'
import { Button } from '@modrinth/ui'
import {
ButtonStyled,
commonMessages,
defineMessages,
injectNotificationManager,
@@ -1,58 +1,53 @@
<template>
<div class="flex gap-2">
<ButtonStyled circular>
<a
v-tooltip="`Share on Bluesky`"
:href="`https://bsky.app/intent/compose?text=${encodedUrl}`"
target="_blank"
>
<BlueskyIcon />
</a>
</ButtonStyled>
<ButtonStyled circular>
<a
v-tooltip="`Share on Mastodon`"
:href="`https://tootpick.org/#text=${encodedUrl}`"
target="_blank"
>
<MastodonIcon />
</a>
</ButtonStyled>
<ButtonStyled circular>
<a
v-tooltip="`Share on X`"
:href="`https://www.x.com/intent/post?url=${encodedUrl}`"
target="_blank"
>
<TwitterIcon />
</a>
</ButtonStyled>
<ButtonStyled circular>
<a
v-tooltip="`Share via email`"
:href="`mailto:${encodedTitle ? `?subject=${encodedTitle}&` : `?`}body=${encodedUrl}`"
target="_blank"
>
<MailIcon />
</a>
</ButtonStyled>
<ButtonStyled circular>
<button
v-tooltip="copied ? `Copied to clipboard` : `Copy link`"
:disabled="copied"
class="relative grid place-items-center overflow-hidden"
@click="copyToClipboard(url)"
>
<CheckIcon
class="absolute transition-all ease-in-out"
:class="copied ? 'translate-y-0' : 'translate-y-7'"
/>
<LinkIcon
class="absolute transition-all ease-in-out"
:class="copied ? '-translate-y-7' : 'translate-y-0'"
/>
</button>
</ButtonStyled>
<ButtonLink
v-tooltip="`Share on Bluesky`"
:href="`https://bsky.app/intent/compose?text=${encodedUrl}`"
target="_blank"
class="!w-9 !rounded-full !px-0"
>
<BlueskyIcon />
</ButtonLink>
<ButtonLink
v-tooltip="`Share on Mastodon`"
:href="`https://tootpick.org/#text=${encodedUrl}`"
target="_blank"
class="!w-9 !rounded-full !px-0"
>
<MastodonIcon />
</ButtonLink>
<ButtonLink
v-tooltip="`Share on X`"
:href="`https://www.x.com/intent/post?url=${encodedUrl}`"
target="_blank"
class="!w-9 !rounded-full !px-0"
>
<TwitterIcon />
</ButtonLink>
<ButtonLink
v-tooltip="`Share via email`"
:href="`mailto:${encodedTitle ? `?subject=${encodedTitle}&` : `?`}body=${encodedUrl}`"
target="_blank"
class="!w-9 !rounded-full !px-0"
>
<MailIcon />
</ButtonLink>
<IconButton
v-tooltip="copied ? `Copied to clipboard` : `Copy link`"
:label="copied ? `Copied to clipboard` : `Copy link`"
:disabled="copied"
class="relative grid place-items-center overflow-hidden"
@click="copyToClipboard(url)"
>
<CheckIcon
class="absolute transition-all ease-in-out"
:class="copied ? 'translate-y-0' : 'translate-y-7'"
/>
<LinkIcon
class="absolute transition-all ease-in-out"
:class="copied ? '-translate-y-7' : 'translate-y-0'"
/>
</IconButton>
</div>
</template>
@@ -65,7 +60,7 @@ import {
MastodonIcon,
TwitterIcon,
} from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import { ButtonLink, IconButton } from '@modrinth/ui'
const props = defineProps<{
title?: string
@@ -102,23 +102,21 @@
formatMessage(messages.openingAutomatically)
}}</span>
<div v-else class="grid w-full grid-cols-1 gap-2 sm:grid-cols-2">
<ButtonStyled>
<button class="flex-1" type="button" @click="hide">
<XIcon />
{{ formatMessage(commonMessages.closeButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<a
class="flex-1"
href="https://modrinth.com/app"
target="_blank"
rel="noopener noreferrer"
>
<DownloadIcon />
{{ formatMessage(messages.getApp) }}
</a>
</ButtonStyled>
<Button class="flex-1" native-type="button" @click="hide">
<XIcon />
{{ formatMessage(commonMessages.closeButton) }}
</Button>
<ButtonLink
type="colored"
color="brand"
class="flex-1"
href="https://modrinth.com/app"
target="_blank"
rel="noopener noreferrer"
>
<DownloadIcon />
{{ formatMessage(messages.getApp) }}
</ButtonLink>
</div>
</div>
</div>
@@ -127,7 +125,7 @@
<script setup lang="ts">
import { CheckIcon, DownloadIcon, XIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
import { Avatar, Button, ButtonLink, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
import { computed, nextTick, onUnmounted, ref } from 'vue'
const { formatMessage } = useVIntl()
@@ -87,29 +87,23 @@
</div>
</div>
<div class="flex gap-2">
<ButtonStyled v-if="isRefunded">
<div class="button-like disabled"><CheckIcon /> Charge refunded</div>
</ButtonStyled>
<ButtonStyled
<Button v-if="isRefunded" disabled><CheckIcon /> Charge refunded</Button>
<Button
v-else-if="charge.status === 'succeeded' && charge.type !== 'refund'"
color="red"
color-fill="text"
class="!text-red [&>svg]:!text-red"
@click="emit('refund', charge)"
>
<button @click="emit('refund', charge)">
<CurrencyIcon />
Refund options
</button>
</ButtonStyled>
<ButtonStyled
<CurrencyIcon />
Refund options
</Button>
<Button
v-else-if="charge.status === 'failed' || charge.status === 'open'"
color="red"
color-fill="text"
class="!text-red [&>svg]:!text-red"
@click="emit('modify', charge, subscription)"
>
<button @click="emit('modify', charge, subscription)">
<CurrencyIcon />
Modify charge
</button>
</ButtonStyled>
<CurrencyIcon />
Modify charge
</Button>
</div>
</div>
</div>
@@ -117,7 +111,7 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { CheckIcon, CurrencyIcon } from '@modrinth/assets'
import { ButtonStyled, useFormatDateTime, useFormatPrice, useRelativeTime } from '@modrinth/ui'
import { Button, useFormatDateTime, useFormatPrice, useRelativeTime } from '@modrinth/ui'
import dayjs from 'dayjs'
import { products } from '~/generated/state.json'
@@ -3,7 +3,7 @@ import type { Archon } from '@modrinth/api-client'
import { PlusIcon, XIcon } from '@modrinth/assets'
import {
Accordion,
ButtonStyled,
Button,
injectModrinthClient,
injectNotificationManager,
NewModal,
@@ -181,24 +181,33 @@ defineExpose({ show, hide })
wrapper-class="w-full"
autocomplete="off"
/>
<ButtonStyled color="green" color-fill="text">
<button class="shrink-0" @click="() => assign(true)">
<PlusIcon />
Add server
</button>
</ButtonStyled>
<ButtonStyled color="blue" color-fill="text">
<button class="shrink-0" @click="() => assign(false)">
<PlusIcon />
Add node
</button>
</ButtonStyled>
<ButtonStyled color="red" color-fill="text">
<button class="shrink-0" @click="() => unassignDetect()">
<XIcon />
Remove
</button>
</ButtonStyled>
<Button
type="quiet"
color="green"
class="shrink-0 !text-green [&>svg]:!text-green"
@click="() => assign(true)"
>
<PlusIcon />
Add server
</Button>
<Button
type="quiet"
color="blue"
class="shrink-0 !text-blue [&>svg]:!text-blue"
@click="() => assign(false)"
>
<PlusIcon />
Add node
</Button>
<Button
type="quiet"
color="red"
class="shrink-0 !text-red [&>svg]:!text-red"
@click="() => unassignDetect()"
>
<XIcon />
Remove
</Button>
</div>
</div>
</div>
@@ -42,12 +42,15 @@
wrapper-class="w-32"
autocomplete="off"
/>
<ButtonStyled color="blue" color-fill="text">
<button class="shrink-0" @click="addNode">
<PlusIcon />
Add
</button>
</ButtonStyled>
<Button
type="quiet"
color="blue"
class="shrink-0 !text-blue [&>svg]:!text-blue"
@click="addNode"
>
<PlusIcon />
Add
</Button>
</div>
<div v-if="selectedNodes.length" class="mt-1 flex flex-wrap gap-2">
<TagItem v-for="h in selectedNodes" :key="`node-${h}`" :action="() => removeNode(h)">
@@ -111,18 +114,14 @@
</div>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button :disabled="applyDisabled" @click="apply">
<CheckIcon aria-hidden="true" />
Apply credits
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="modal?.hide?.()">
<XIcon aria-hidden="true" />
Cancel
</button>
</ButtonStyled>
<Button type="colored" color="brand" :disabled="applyDisabled" @click="apply">
<CheckIcon aria-hidden="true" />
Apply credits
</Button>
<Button @click="modal?.hide?.()">
<XIcon aria-hidden="true" />
Cancel
</Button>
</div>
</div>
</NewModal>
@@ -131,7 +130,7 @@
<script setup lang="ts">
import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
Combobox,
injectModrinthClient,
injectNotificationManager,
@@ -56,12 +56,15 @@
placeholder="us-vin200, us-vin201"
@keydown.enter.prevent="addNodes"
/>
<ButtonStyled color="blue" color-fill="text">
<button class="shrink-0" @click="addNodes">
<PlusIcon />
Add
</button>
</ButtonStyled>
<Button
type="quiet"
color="blue"
class="shrink-0 !text-blue [&>svg]:!text-blue"
@click="addNodes"
>
<PlusIcon />
Add
</Button>
</div>
<div v-if="selectedNodes.length" class="mt-1 flex flex-wrap gap-2">
<TagItem v-for="h in selectedNodes" :key="`node-${h}`" :action="() => removeNode(h)">
@@ -122,12 +125,15 @@
placeholder="ovh-gen4"
@keydown.enter.prevent="addTag"
/>
<ButtonStyled color="blue" color-fill="text">
<button class="shrink-0" @click="addTag">
<PlusIcon />
Add
</button>
</ButtonStyled>
<Button
type="quiet"
color="blue"
class="shrink-0 !text-blue [&>svg]:!text-blue"
@click="addTag"
>
<PlusIcon />
Add
</Button>
</div>
<div v-if="selectedTags.length" class="mt-1 flex flex-wrap gap-2">
<TagItem v-for="t in selectedTags" :key="`tag-${t}`" :action="() => removeTag(t)">
@@ -175,18 +181,19 @@
</div>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button :disabled="submitDisabled || submitting" @click="submit">
<SendIcon aria-hidden="true" />
{{ submitting ? 'Scheduling...' : 'Schedule transfer' }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="modal?.hide?.()">
<XIcon aria-hidden="true" />
Cancel
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
:disabled="submitDisabled || submitting"
@click="submit"
>
<SendIcon aria-hidden="true" />
{{ submitting ? 'Scheduling...' : 'Schedule transfer' }}
</Button>
<Button @click="modal?.hide?.()">
<XIcon aria-hidden="true" />
Cancel
</Button>
</div>
</div>
</NewModal>
@@ -195,7 +202,7 @@
<script setup lang="ts">
import { PlusIcon, SendIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Button,
Chips,
Combobox,
injectModrinthClient,
@@ -84,16 +84,16 @@
<label for="create-account-consent">I agree to receive account updates by email</label>
</div>
<ButtonStyled color="brand">
<button
class="!w-full font-bold"
:disabled="globals?.captcha_enabled ? !tokenModel : false"
@click="onCompleteSignUpClick"
>
{{ formatMessage(messages.completeSignUpButton) }}
<RightArrowIcon />
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
class="!w-full font-bold"
:disabled="globals?.captcha_enabled ? !tokenModel : false"
@click="onCompleteSignUpClick"
>
{{ formatMessage(messages.completeSignUpButton) }}
<RightArrowIcon />
</Button>
</div>
</template>
@@ -101,7 +101,7 @@
import { RightArrowIcon } from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
Button,
Checkbox,
DatePicker,
defineMessages,
@@ -55,48 +55,42 @@
</div>
</div>
<div class="flex gap-2">
<ButtonStyled circular>
<button
v-tooltip="formatMessage(commonMessages.renameButton)"
@click="
() => {
passkeyToRename = { ...passkey }
renamePasskeyModal?.show()
}
"
>
<EditIcon />
</button>
</ButtonStyled>
<ButtonStyled circular>
<button
v-tooltip="formatMessage(messages.deletePasskeyButton)"
@click="
() => {
passkeyToRemove = passkey
removePasskeyModal?.show()
}
"
>
<TrashIcon />
</button>
</ButtonStyled>
<IconButton
v-tooltip="formatMessage(commonMessages.renameButton)"
:label="formatMessage(commonMessages.renameButton)"
@click="
() => {
passkeyToRename = { ...passkey }
renamePasskeyModal?.show()
}
"
>
<EditIcon />
</IconButton>
<IconButton
v-tooltip="formatMessage(messages.deletePasskeyButton)"
:label="formatMessage(messages.deletePasskeyButton)"
@click="
() => {
passkeyToRemove = passkey
removePasskeyModal?.show()
}
"
>
<TrashIcon />
</IconButton>
</div>
</div>
</template>
<div class="input-group self-end">
<ButtonStyled>
<button @click="registerPasskey()">
<PlusIcon />
{{ formatMessage(messages.managePasskeyAddPasskeyButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="managePasskeyModal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.closeButton) }}
</button>
</ButtonStyled>
<Button @click="registerPasskey()">
<PlusIcon />
{{ formatMessage(messages.managePasskeyAddPasskeyButton) }}
</Button>
<Button @click="managePasskeyModal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.closeButton) }}
</Button>
</div>
</div>
</NewModal>
@@ -124,18 +118,19 @@
</div>
</div>
<div class="flex justify-end gap-2.5">
<ButtonStyled>
<button @click="addPasskeyModal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!pendingPasskeyName" @click="finishRegisterPasskey()">
<PlusIcon />
{{ formatMessage(messages.managePasskeyAddPasskeyButton) }}
</button>
</ButtonStyled>
<Button @click="addPasskeyModal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button
type="colored"
color="brand"
:disabled="!pendingPasskeyName"
@click="finishRegisterPasskey()"
>
<PlusIcon />
{{ formatMessage(messages.managePasskeyAddPasskeyButton) }}
</Button>
</div>
</div>
</NewModal>
@@ -163,18 +158,19 @@
</div>
</div>
<div class="flex justify-end gap-2.5">
<ButtonStyled>
<button @click="renamePasskeyModal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!passkeyToRenameName" @click="renamePasskey()">
<SaveIcon />
{{ formatMessage(commonMessages.saveButton) }}
</button>
</ButtonStyled>
<Button @click="renamePasskeyModal?.hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</Button>
<Button
type="colored"
color="brand"
:disabled="!passkeyToRenameName"
@click="renamePasskey()"
>
<SaveIcon />
{{ formatMessage(commonMessages.saveButton) }}
</Button>
</div>
</div>
</NewModal>
@@ -187,11 +183,9 @@
}}</span>
</label>
<div>
<ButtonStyled>
<button id="manage-passkeys" @click="showPasskeyModal">
<UserKeyIcon /> {{ formatMessage(messages.managePasskeyButton) }}
</button>
</ButtonStyled>
<Button id="manage-passkeys" @click="showPasskeyModal">
<UserKeyIcon /> {{ formatMessage(messages.managePasskeyButton) }}
</Button>
</div>
</div>
</div>
@@ -209,10 +203,11 @@ import {
XIcon,
} from '@modrinth/assets'
import {
ButtonStyled,
Button,
commonMessages,
ConfirmModal,
defineMessages,
IconButton,
injectModrinthClient,
injectNotificationManager,
NewModal,
+53 -55
View File
@@ -28,11 +28,9 @@
@keyup.enter="onTwoFactorSignIn()"
/>
</div>
<ButtonStyled color="brand">
<button @click="onTwoFactorSignIn()">
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
</button>
</ButtonStyled>
<Button type="colored" color="brand" @click="onTwoFactorSignIn()">
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
</Button>
</div>
</template>
<template v-else>
@@ -42,47 +40,40 @@
</div>
<section class="grid grid-cols-1 gap-2.5 sm:grid-cols-2">
<ButtonStyled v-for="provider in oauthProviders" :key="provider.id">
<a
class="relative w-full !justify-center overflow-visible !shadow-none"
:class="{
'!border !border-[var(--color-green)]': lastSignInProvider === provider.id,
}"
:href="getAuthUrl(provider.id, redirectTarget)"
:aria-label="
formatMessage(messages.continueWithProvider, { provider: provider.name })
"
@click="onOAuthProviderClick(provider.id)"
<ButtonLink
v-for="provider in oauthProviders"
:key="provider.id"
class="relative w-full !justify-center overflow-visible"
:class="{
'!border !border-[var(--color-green)]': lastSignInProvider === provider.id,
}"
:href="getAuthUrl(provider.id, redirectTarget)"
:aria-label="formatMessage(messages.continueWithProvider, { provider: provider.name })"
@click="onOAuthProviderClick(provider.id)"
>
<component :is="provider.icon" />
<span>{{ provider.name }}</span>
<span
v-if="lastSignInProvider === provider.id"
class="oauth-provider-last-sign-in-badge"
>
<component :is="provider.icon" />
<span>{{ provider.name }}</span>
<span
v-if="lastSignInProvider === provider.id"
class="oauth-provider-last-sign-in-badge"
>
{{ formatMessage(messages.lastSignInLabel) }}
</span>
</a>
</ButtonStyled>
<ButtonStyled>
<a
class="relative w-full !justify-center overflow-visible !shadow-none sm:col-span-2"
:class="{ '!border !border-[var(--color-green)]': lastSignInProvider === 'passkey' }"
role="button"
tabindex="0"
@click="onPasskeySignIn"
@keydown.enter="onPasskeySignIn"
>
<UserKeyIcon />
<span class="ml-1">{{ formatMessage(messages.continueWithPasskey) }}</span>
<span
v-if="lastSignInProvider === 'passkey'"
class="oauth-provider-last-sign-in-badge"
>
{{ formatMessage(messages.lastSignInLabel) }}
</span>
</a>
</ButtonStyled>
{{ formatMessage(messages.lastSignInLabel) }}
</span>
</ButtonLink>
<Button
class="relative !w-full !justify-center overflow-visible sm:col-span-2"
:class="{ '!border !border-[var(--color-green)]': lastSignInProvider === 'passkey' }"
role="button"
tabindex="0"
@click="onPasskeySignIn"
@keydown.enter="onPasskeySignIn"
>
<UserKeyIcon />
<span class="ml-1">{{ formatMessage(messages.continueWithPasskey) }}</span>
<span v-if="lastSignInProvider === 'passkey'" class="oauth-provider-last-sign-in-badge">
{{ formatMessage(messages.lastSignInLabel) }}
</span>
</Button>
</section>
<div class="h-px w-full bg-surface-5"></div>
@@ -117,15 +108,15 @@
v-model="tokenModel"
/>
<ButtonStyled color="brand">
<button
class="!w-full"
:disabled="globals?.captcha_enabled ? !tokenModel : false"
@click="onPasswordSignIn()"
>
{{ formatMessage(messages.continueWithEmail) }} <RightArrowIcon />
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
class="!w-full"
:disabled="globals?.captcha_enabled ? !tokenModel : false"
@click="onPasswordSignIn()"
>
{{ formatMessage(messages.continueWithEmail) }} <RightArrowIcon />
</Button>
<div class="flex flex-wrap items-center justify-center gap-2.5 !text-base">
<NuxtLink
@@ -167,7 +158,14 @@ import {
SteamColorIcon,
UserKeyIcon,
} from '@modrinth/assets'
import { ButtonStyled, commonMessages, defineMessages, StyledInput, useVIntl } from '@modrinth/ui'
import {
Button,
ButtonLink,
commonMessages,
defineMessages,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { useStorage } from '@vueuse/core'
import { computed } from 'vue'
import type { LocationQuery } from 'vue-router'
+53 -70
View File
@@ -6,67 +6,49 @@
{{ formatMessage(messages.signUpWithTitle) }}
</div>
<section class="flex flex-col gap-2.5">
<ButtonStyled>
<a
class="!shadow-none"
:href="getAuthUrl('google', redirectTarget)"
@click="onOAuthProviderClick('google')"
>
<GoogleColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Google' }) }}</span>
</a>
</ButtonStyled>
<ButtonStyled>
<a
class="!shadow-none"
:href="getAuthUrl('microsoft', redirectTarget)"
@click="onOAuthProviderClick('microsoft')"
>
<MicrosoftColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Microsoft' }) }}</span>
</a>
</ButtonStyled>
<ButtonStyled>
<a
class="!shadow-none"
:href="getAuthUrl('discord', redirectTarget)"
@click="onOAuthProviderClick('discord')"
>
<DiscordColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Discord' }) }}</span>
</a>
</ButtonStyled>
<ButtonLink
:href="getAuthUrl('google', redirectTarget)"
@click="onOAuthProviderClick('google')"
>
<GoogleColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Google' }) }}</span>
</ButtonLink>
<ButtonLink
:href="getAuthUrl('microsoft', redirectTarget)"
@click="onOAuthProviderClick('microsoft')"
>
<MicrosoftColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Microsoft' }) }}</span>
</ButtonLink>
<ButtonLink
:href="getAuthUrl('discord', redirectTarget)"
@click="onOAuthProviderClick('discord')"
>
<DiscordColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Discord' }) }}</span>
</ButtonLink>
<template v-if="showOtherOptions">
<ButtonStyled>
<a
class="!shadow-none"
:href="getAuthUrl('github', redirectTarget)"
@click="onOAuthProviderClick('github')"
>
<GitHubColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'GitHub' }) }}</span>
</a>
</ButtonStyled>
<ButtonStyled>
<a
class="!shadow-none"
:href="getAuthUrl('gitlab', redirectTarget)"
@click="onOAuthProviderClick('gitlab')"
>
<GitLabColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'GitLab' }) }}</span>
</a>
</ButtonStyled>
<ButtonStyled>
<a
class="!shadow-none"
:href="getAuthUrl('steam', redirectTarget)"
@click="onOAuthProviderClick('steam')"
>
<SteamColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Steam' }) }}</span>
</a>
</ButtonStyled>
<ButtonLink
:href="getAuthUrl('github', redirectTarget)"
@click="onOAuthProviderClick('github')"
>
<GitHubColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'GitHub' }) }}</span>
</ButtonLink>
<ButtonLink
:href="getAuthUrl('gitlab', redirectTarget)"
@click="onOAuthProviderClick('gitlab')"
>
<GitLabColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'GitLab' }) }}</span>
</ButtonLink>
<ButtonLink
:href="getAuthUrl('steam', redirectTarget)"
@click="onOAuthProviderClick('steam')"
>
<SteamColorIcon />
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Steam' }) }}</span>
</ButtonLink>
</template>
<button
class="mx-auto -mb-3 bg-transparent pt-1 text-center text-base font-semibold text-secondary transition-all hover:text-primary"
@@ -105,15 +87,15 @@
wrapper-class="w-full"
/>
<ButtonStyled color="brand">
<button
class="!w-full"
:disabled="!emailModel || !passwordModel"
@click="onContinueWithEmail()"
>
{{ formatMessage(messages.continueWithEmail) }} <RightArrowIcon />
</button>
</ButtonStyled>
<Button
type="colored"
color="brand"
class="!w-full"
:disabled="!emailModel || !passwordModel"
@click="onContinueWithEmail()"
>
{{ formatMessage(messages.continueWithEmail) }} <RightArrowIcon />
</Button>
<p v-if="!routeQuery.launcher" class="m-0 text-center">
<IntlFormatted :message-id="messages.legalDisclaimer">
@@ -159,7 +141,8 @@ import {
SteamColorIcon,
} from '@modrinth/assets'
import {
ButtonStyled,
Button,
ButtonLink,
commonMessages,
defineMessages,
IntlFormatted,
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { XCircleIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
import { Button, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
const { formatMessage } = useVIntl()
const flags = useFeatureFlags()
@@ -57,18 +57,19 @@ function alwaysIgnoreBanner() {
}}
</template>
<template #actions_right>
<ButtonStyled color="red" type="transparent" hover-color-fill="background">
<button @click="alwaysIgnoreBanner">
<XCircleIcon />
{{ formatMessage(messages.alwaysIgnore) }}
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button @click="tempIgnored = true">
<XIcon />
{{ formatMessage(messages.ignoreErrors) }}
</button>
</ButtonStyled>
<Button
type="quiet"
color="red"
class="hover:!bg-red hover:!text-[var(--color-accent-contrast)] focus-visible:!bg-red focus-visible:!text-[var(--color-accent-contrast)]"
@click="alwaysIgnoreBanner"
>
<XCircleIcon />
{{ formatMessage(messages.alwaysIgnore) }}
</Button>
<Button type="colored" color="red" @click="tempIgnored = true">
<XIcon />
{{ formatMessage(messages.ignoreErrors) }}
</Button>
</template>
</PagewideBanner>
</template>
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { XIcon } from '@modrinth/assets'
import {
ButtonStyled,
commonMessages,
defineMessages,
IconButton,
IntlFormatted,
PagewideBanner,
useVIntl,
@@ -70,11 +70,13 @@ const url = computed(() => `https://modrinth.com${route.fullPath}`)
</span>
</template>
<template #actions_top_right>
<ButtonStyled type="transparent" circular>
<button :aria-label="formatMessage(commonMessages.closeButton)" @click="hidePreviewBanner">
<XIcon aria-hidden="true" />
</button>
</ButtonStyled>
<IconButton
type="quiet"
:label="formatMessage(commonMessages.closeButton)"
@click="hidePreviewBanner"
>
<XIcon aria-hidden="true" />
</IconButton>
</template>
</PagewideBanner>
</template>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { BookTextIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, commonMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
import { ButtonLink, commonMessages, IconButton, PagewideBanner, useVIntl } from '@modrinth/ui'
const flags = useFeatureFlags()
const { formatMessage } = useVIntl()
@@ -41,29 +41,25 @@ function hideRussiaCensorshipBanner() {
</template>
<template #actions>
<div class="mt-2 flex w-fit gap-2">
<ButtonStyled color="brand">
<nuxt-link to="/news/article/standing-by-our-values-russian">
<BookTextIcon /> Прочесть наше полное заявление
<span class="text-xs font-medium">(Перевод на русский)</span>
</nuxt-link>
</ButtonStyled>
<ButtonStyled type="transparent" hover-color-fill="background">
<nuxt-link to="/news/article/standing-by-our-values">
<BookTextIcon /> Read our full statement
<span class="text-xs font-medium">(English)</span>
</nuxt-link>
</ButtonStyled>
<ButtonLink type="colored" color="brand" to="/news/article/standing-by-our-values-russian">
<BookTextIcon /> Прочесть наше полное заявление
<span class="text-xs font-medium">(Перевод на русский)</span>
</ButtonLink>
<ButtonLink type="quiet" to="/news/article/standing-by-our-values">
<BookTextIcon /> Read our full statement
<span class="text-xs font-medium">(English)</span>
</ButtonLink>
</div>
</template>
<template #actions_top_right>
<ButtonStyled circular type="transparent">
<button
v-tooltip="formatMessage(commonMessages.closeButton)"
@click="hideRussiaCensorshipBanner"
>
<XIcon :aria-label="formatMessage(commonMessages.closeButton)" />
</button>
</ButtonStyled>
<IconButton
v-tooltip="formatMessage(commonMessages.closeButton)"
type="quiet"
:label="formatMessage(commonMessages.closeButton)"
@click="hideRussiaCensorshipBanner"
>
<XIcon :aria-label="formatMessage(commonMessages.closeButton)" />
</IconButton>
</template>
</PagewideBanner>
</template>
@@ -1,12 +1,6 @@
<script setup lang="ts">
import { XIcon } from '@modrinth/assets'
import {
ButtonStyled,
commonMessages,
defineMessages,
PagewideBanner,
useVIntl,
} from '@modrinth/ui'
import { commonMessages, defineMessages, IconButton, PagewideBanner, useVIntl } from '@modrinth/ui'
const { formatMessage } = useVIntl()
const cosmetics = useCosmetics()
@@ -38,11 +32,13 @@ function hideStagingBanner() {
{{ formatMessage(messages.description) }}
</template>
<template #actions_top_right>
<ButtonStyled type="transparent" circular>
<button :aria-label="formatMessage(commonMessages.closeButton)" @click="hideStagingBanner">
<XIcon aria-hidden="true" />
</button>
</ButtonStyled>
<IconButton
type="quiet"
:label="formatMessage(commonMessages.closeButton)"
@click="hideStagingBanner"
>
<XIcon aria-hidden="true" />
</IconButton>
</template>
</PagewideBanner>
</template>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { SettingsIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
import { ButtonLink, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
const { formatMessage } = useVIntl()
@@ -30,12 +30,10 @@ const messages = defineMessages({
<span>{{ formatMessage(messages.description) }}</span>
</template>
<template #actions_right>
<ButtonStyled color="red">
<nuxt-link to="/settings/billing">
<SettingsIcon aria-hidden="true" />
{{ formatMessage(messages.action) }}
</nuxt-link>
</ButtonStyled>
<ButtonLink type="colored" color="red" to="/settings/billing">
<SettingsIcon aria-hidden="true" />
{{ formatMessage(messages.action) }}
</ButtonLink>
</template>
</PagewideBanner>
</template>
@@ -1,12 +1,6 @@
<script setup lang="ts">
import { FileTextIcon } from '@modrinth/assets'
import {
ButtonStyled,
defineMessages,
PagewideBanner,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { Button, defineMessages, PagewideBanner, useFormatMoney, useVIntl } from '@modrinth/ui'
import { computed } from 'vue'
import { getTaxThreshold } from '@/providers/creator-withdraw.ts'
@@ -56,9 +50,9 @@ function openTaxForm(e: MouseEvent) {
}}</span>
</template>
<template #actions_right>
<ButtonStyled color="orange">
<button @click="openTaxForm"><FileTextIcon /> {{ formatMessage(messages.action) }}</button>
</ButtonStyled>
<Button type="colored" color="orange" @click="openTaxForm"
><FileTextIcon /> {{ formatMessage(messages.action) }}</Button
>
</template>
</PagewideBanner>
</template>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { MessageIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
import { ButtonLink, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
const { formatMessage } = useVIntl()
@@ -31,12 +31,16 @@ const messages = defineMessages({
</template>
<template #actions_right>
<div class="flex w-fit flex-row">
<ButtonStyled color="red">
<nuxt-link to="https://support.modrinth.com" target="_blank" rel="noopener">
<MessageIcon />
{{ formatMessage(messages.action) }}
</nuxt-link>
</ButtonStyled>
<ButtonLink
type="colored"
color="red"
to="https://support.modrinth.com"
target="_blank"
rel="noopener"
>
<MessageIcon />
{{ formatMessage(messages.action) }}
</ButtonLink>
</div>
</template>
</PagewideBanner>

Some files were not shown because too many files have changed in this diff Show More