chore: clean up a bunch of legacy styles (#5973)

* remove unused experimental-styles-within

* remove unused styles

* more cleanup + prepr

* Refactor nearly all legacy buttons to use ButtonStyled

* prepr

* Update MC account selector to modern version

* prepr

---------

Co-authored-by: Calum H. <calum@modrinth.com>
This commit is contained in:
Prospector
2026-05-03 11:53:06 -07:00
committed by GitHub
parent 8a72ee9968
commit 7dbbbe590f
153 changed files with 2596 additions and 3817 deletions

View File

@@ -1177,7 +1177,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<div id="teleports"></div> <div id="teleports"></div>
<div <div
v-if="stateInitialized" v-if="stateInitialized"
class="app-grid-layout experimental-styles-within relative" class="app-grid-layout relative"
:class="{ 'disable-advanced-rendering': !themeStore.advancedRendering }" :class="{ 'disable-advanced-rendering': !themeStore.advancedRendering }"
> >
<Transition name="fade"> <Transition name="fade">
@@ -1378,7 +1378,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</div> </div>
<div <div
v-if="stateInitialized" v-if="stateInitialized"
class="app-contents experimental-styles-within" class="app-contents"
:class="{ :class="{
'sidebar-enabled': sidebarVisible, 'sidebar-enabled': sidebarVisible,
'disable-advanced-rendering': !themeStore.advancedRendering, 'disable-advanced-rendering': !themeStore.advancedRendering,
@@ -1472,7 +1472,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid"> <div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<h3 class="text-base text-primary font-medium m-0">Playing as</h3> <h3 class="text-base text-primary font-medium m-0">Playing as</h3>
<suspense> <suspense>
<AccountsCard ref="accounts" mode="small" /> <AccountsCard ref="accounts" />
</suspense> </suspense>
</div> </div>
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid"> <div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">

View File

@@ -1,81 +1,107 @@
<template> <template>
<div <div
v-if="mode !== 'isolated'" v-if="accounts.length === 0"
ref="button" class="flex flex-col gap-3 bg-button-bg border border-solid border-surface-5 rounded-xl p-3 mt-2"
class="button-base mt-2 px-3 py-2 bg-button-bg rounded-xl flex items-center gap-2"
:class="{ expanded: mode === 'expanded' }"
@click="toggleMenu"
> >
<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>
</div>
<Accordion
v-else
class="w-full mt-2 bg-button-bg border border-solid border-surface-5 rounded-xl overflow-clip"
button-class="button-base w-full bg-transparent px-3 py-2 border-0 cursor-pointer"
:open-by-default="false"
>
<template #title>
<div class="flex gap-2 w-full min-w-0">
<Avatar <Avatar
size="36px" size="36px"
:src=" :src="
selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png' selectedAccount
? avatarUrl
: 'https://launcher-files.modrinth.com/assets/steve_head.png'
" "
/> />
<div class="flex flex-col w-full"> <div class="flex flex-col items-start w-full min-w-0">
<span>{{ selectedAccount ? selectedAccount.profile.name : 'Select account' }}</span> <span class="truncate w-full text-left">{{
<span class="text-secondary text-xs">Minecraft account</span> selectedAccount ? selectedAccount.profile.name : formatMessage(messages.selectAccount)
}}</span>
<span class="text-secondary text-xs">{{ formatMessage(messages.minecraftAccount) }}</span>
</div> </div>
<DropdownIcon class="w-5 h-5 shrink-0" />
</div> </div>
<transition name="fade"> </template>
<Card <div class="bg-button-bg pt-1 pb-2 border border-solid border-surface-5">
v-if="showCard || mode === 'isolated'" <template v-if="accounts.length > 0">
ref="card" <div v-for="account in accounts" :key="account.profile.id" class="flex gap-1 items-center">
class="account-card" <button
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }" class="flex items-center flex-shrink flex-grow overflow-clip gap-2 p-2 border-0 bg-transparent cursor-pointer button-base min-w-0"
@click="setAccount(account)"
> >
<div v-if="selectedAccount" class="selected account"> <RadioButtonCheckedIcon
<Avatar size="xs" :src="avatarUrl" /> v-if="selectedAccount && selectedAccount.profile.id === account.profile.id"
<div> class="w-5 h-5 text-brand shrink-0"
<h4>{{ selectedAccount.profile.name }}</h4> />
<p>Selected</p> <RadioButtonIcon v-else class="w-5 h-5 text-secondary shrink-0" />
</div> <Avatar :src="getAccountAvatarUrl(account)" size="24px" />
<Button <p
v-tooltip="'Log out'" class="m-0 truncate min-w-0"
icon-only :class="
color="raised" selectedAccount && selectedAccount.profile.id === account.profile.id
@click="logout(selectedAccount.profile.id)" ? 'text-contrast font-semibold'
: 'text-primary'
"
>
{{ 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 /> <TrashIcon />
</Button> </button>
</ButtonStyled>
</div> </div>
<div v-else class="logged-out account"> </template>
<h4>Not signed in</h4> <div class="flex flex-col gap-2 px-2 pt-2">
<Button <ButtonStyled v-if="accounts.length > 0" class="w-full">
v-tooltip="'Log in'" <button :disabled="loginDisabled" @click="login()">
:disabled="loginDisabled"
icon-only
color="primary"
@click="login()"
>
<LogInIcon v-if="!loginDisabled" />
<SpinnerIcon v-else class="animate-spin" />
</Button>
</div>
<div v-if="displayAccounts.length > 0" class="account-group">
<div v-for="account in displayAccounts" :key="account.profile.id" class="account-row">
<Button class="option account" @click="setAccount(account)">
<Avatar :src="getAccountAvatarUrl(account)" class="icon" />
<p>{{ account.profile.name }}</p>
</Button>
<Button v-tooltip="'Log out'" icon-only @click="logout(account.profile.id)">
<TrashIcon />
</Button>
</div>
</div>
<Button v-if="accounts.length > 0" @click="login()">
<PlusIcon /> <PlusIcon />
Add account {{ formatMessage(messages.addAccount) }}
</Button> </button>
</Card> </ButtonStyled>
</transition> </div>
</div>
</Accordion>
</template> </template>
<script setup> <script setup lang="ts">
import { DropdownIcon, LogInIcon, PlusIcon, SpinnerIcon, TrashIcon } from '@modrinth/assets' import {
import { Avatar, Button, Card, injectNotificationManager } from '@modrinth/ui' LogInIcon,
import { computed, onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue' PlusIcon,
RadioButtonCheckedIcon,
RadioButtonIcon,
SpinnerIcon,
TrashIcon,
} from '@modrinth/assets'
import {
Accordion,
Avatar,
ButtonStyled,
defineMessages,
injectNotificationManager,
useVIntl,
} from '@modrinth/ui'
import type { Ref } from 'vue'
import { computed, onUnmounted, ref } from 'vue'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { import {
@@ -87,34 +113,39 @@ import {
} from '@/helpers/auth' } from '@/helpers/auth'
import { process_listener } from '@/helpers/events' import { process_listener } from '@/helpers/events'
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts' import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
import type { Skin } from '@/helpers/skins'
import { get_available_skins } from '@/helpers/skins' import { get_available_skins } from '@/helpers/skins'
import { handleSevereError } from '@/store/error.js' import { handleSevereError } from '@/store/error.js'
const { formatMessage } = useVIntl()
const { handleError } = injectNotificationManager() const { handleError } = injectNotificationManager()
defineProps({ const emit = defineEmits<{
mode: { change: []
type: String, }>()
required: true,
default: 'normal',
},
})
const emit = defineEmits(['change']) type MinecraftCredential = {
profile: {
id: string
name: string
}
}
const accounts = ref({}) const accounts: Ref<MinecraftCredential[]> = ref([])
const loginDisabled = ref(false) const loginDisabled = ref(false)
const defaultUser = ref() const defaultUser = ref<string | undefined>()
const equippedSkin = ref(null) const equippedSkin = ref<Skin | null>(null)
const headUrlCache = ref(new Map()) const headUrlCache = ref(new Map<string, string>())
async function refreshValues() { async function refreshValues() {
defaultUser.value = await get_default_user().catch(handleError) defaultUser.value = await get_default_user().catch(handleError)
accounts.value = await users().catch(handleError) const userList = await users().catch(handleError)
accounts.value = Array.isArray(userList) ? [...userList] : []
accounts.value.sort((a, b) => (a.profile?.name ?? '').localeCompare(b.profile?.name ?? ''))
try { try {
const skins = await get_available_skins() const skins = await get_available_skins()
equippedSkin.value = skins.find((skin) => skin.is_equipped) equippedSkin.value = skins.find((skin) => skin.is_equipped) ?? null
if (equippedSkin.value) { if (equippedSkin.value) {
try { try {
@@ -129,7 +160,7 @@ async function refreshValues() {
} }
} }
function setLoginDisabled(value) { function setLoginDisabled(value: boolean) {
loginDisabled.value = value loginDisabled.value = value
} }
@@ -138,10 +169,11 @@ defineExpose({
setLoginDisabled, setLoginDisabled,
loginDisabled, loginDisabled,
}) })
await refreshValues() await refreshValues()
const displayAccounts = computed(() => const selectedAccount = computed(() =>
accounts.value.filter((account) => defaultUser.value !== account.profile.id), accounts.value.find((account) => account.profile.id === defaultUser.value),
) )
const avatarUrl = computed(() => { const avatarUrl = computed(() => {
@@ -158,7 +190,7 @@ const avatarUrl = computed(() => {
return 'https://launcher-files.modrinth.com/assets/steve_head.png' return 'https://launcher-files.modrinth.com/assets/steve_head.png'
}) })
function getAccountAvatarUrl(account) { function getAccountAvatarUrl(account: MinecraftCredential) {
if ( if (
account.profile.id === selectedAccount.value?.profile?.id && account.profile.id === selectedAccount.value?.profile?.id &&
equippedSkin.value?.texture_key equippedSkin.value?.texture_key
@@ -171,13 +203,10 @@ function getAccountAvatarUrl(account) {
return `https://mc-heads.net/avatar/${account.profile.id}/128` return `https://mc-heads.net/avatar/${account.profile.id}/128`
} }
const selectedAccount = computed(() => async function setAccount(account: MinecraftCredential) {
accounts.value.find((account) => account.profile.id === defaultUser.value),
)
async function setAccount(account) {
defaultUser.value = account.profile.id defaultUser.value = account.profile.id
await set_default_user(account.profile.id).catch(handleError) await set_default_user(account.profile.id).catch(handleError)
await refreshValues()
emit('change') emit('change')
} }
@@ -187,292 +216,57 @@ async function login() {
if (loggedIn) { if (loggedIn) {
await setAccount(loggedIn) await setAccount(loggedIn)
await refreshValues()
} }
trackEvent('AccountLogIn') trackEvent('AccountLogIn')
loginDisabled.value = false loginDisabled.value = false
} }
const logout = async (id) => { async function logout(id: string) {
await remove_user(id).catch(handleError) await remove_user(id).catch(handleError)
await refreshValues() await refreshValues()
if (!selectedAccount.value && accounts.value.length > 0) { if (!selectedAccount.value && accounts.value.length > 0) {
await setAccount(accounts.value[0]) await setAccount(accounts.value[0])
await refreshValues()
} else { } else {
emit('change') emit('change')
} }
trackEvent('AccountLogOut') trackEvent('AccountLogOut')
} }
const showCard = ref(false)
const card = ref(null)
const button = ref(null)
const handleClickOutside = (event) => {
const elements = document.elementsFromPoint(event.clientX, event.clientY)
if (
card.value &&
card.value.$el !== event.target &&
!elements.includes(card.value.$el) &&
!button.value.contains(event.target)
) {
toggleMenu(false)
}
}
function toggleMenu(override = true) {
if (showCard.value || !override) {
showCard.value = false
} else {
showCard.value = true
}
}
const unlisten = await process_listener(async (e) => { const unlisten = await process_listener(async (e) => {
if (e.event === 'launched') { if (e.event === 'launched') {
await refreshValues() await refreshValues()
} }
}) })
onMounted(() => {
window.addEventListener('click', handleClickOutside)
})
onBeforeUnmount(() => {
window.removeEventListener('click', handleClickOutside)
})
onUnmounted(() => { onUnmounted(() => {
unlisten() unlisten()
}) })
const messages = defineMessages({
notSignedIn: {
id: 'minecraft-account.not-signed-in',
defaultMessage: 'Not signed in',
},
addAccount: {
id: 'minecraft-account.add-account',
defaultMessage: 'Add account',
},
removeAccount: {
id: 'minecraft-account.remove-account',
defaultMessage: 'Remove account',
},
selectAccount: {
id: 'minecraft-account.select-account',
defaultMessage: 'Select account',
},
minecraftAccount: {
id: 'minecraft-account.label',
defaultMessage: 'Minecraft account',
},
signInToMinecraft: {
id: 'minecraft-account.sign-in',
defaultMessage: 'Sign in to Minecraft',
},
})
</script> </script>
<style scoped lang="scss">
.selected {
background: var(--color-brand-highlight);
border-radius: var(--radius-lg);
color: var(--color-contrast);
gap: 1rem;
}
.logged-out {
background: var(--color-bg);
border-radius: var(--radius-lg);
gap: 1rem;
}
.account {
width: max-content;
display: flex;
align-items: center;
text-align: left;
padding: 0.5rem 1rem;
h4,
p {
margin: 0;
}
}
.account-card {
position: fixed;
display: flex;
flex-direction: column;
margin-top: 0.5rem;
right: 2rem;
z-index: 11;
gap: 0.5rem;
padding: 1rem;
border: 1px solid var(--color-divider);
width: max-content;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
max-height: calc(100vh - 300px);
overflow-y: auto;
&::-webkit-scrollbar-track {
border-top-right-radius: 1rem;
border-bottom-right-radius: 1rem;
}
&::-webkit-scrollbar {
border-top-right-radius: 1rem;
border-bottom-right-radius: 1rem;
}
&.hidden {
display: none;
}
&.expanded {
left: 13.5rem;
}
&.isolated {
position: relative;
left: 0;
top: 0;
}
}
.accounts-title {
font-size: 1.2rem;
font-weight: bolder;
}
.account-group {
width: 100%;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.option {
width: calc(100% - 2.25rem);
background: var(--color-raised-bg);
color: var(--color-base);
box-shadow: none;
img {
margin-right: 0.5rem;
}
}
.icon {
--size: 1.5rem !important;
}
.account-row {
display: flex;
flex-direction: row;
gap: 0.5rem;
vertical-align: center;
justify-content: space-between;
padding-right: 1rem;
}
.fade-enter-active,
.fade-leave-active {
transition:
opacity 0.25s ease,
translate 0.25s ease,
scale 0.25s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
translate: 0 -2rem;
scale: 0.9;
}
.avatar-button {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--color-base);
background-color: var(--color-button-bg);
border-radius: var(--radius-md);
width: 100%;
padding: 0.5rem 0.75rem;
text-align: left;
&.expanded {
border: 1px solid var(--color-divider);
padding: 1rem;
}
}
.avatar-text {
margin: auto 0 auto 0.25rem;
display: flex;
flex-direction: column;
}
.text {
width: 6rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.accounts-text {
display: flex;
align-items: center;
gap: 0.25rem;
margin: 0;
}
.qr-code {
background-color: white !important;
border-radius: var(--radius-md);
}
.modal-body {
display: flex;
flex-direction: row;
gap: var(--gap-lg);
align-items: center;
padding: var(--gap-xl);
.modal-text {
display: flex;
flex-direction: column;
gap: var(--gap-sm);
width: 100%;
h2,
p {
margin: 0;
}
.code-text {
display: flex;
flex-direction: row;
gap: var(--gap-xs);
align-items: center;
.code {
background-color: var(--color-bg);
border-radius: var(--radius-md);
border: solid 1px var(--color-button-bg);
font-family: var(--mono-font);
letter-spacing: var(--gap-md);
color: var(--color-contrast);
font-size: 2rem;
font-weight: bold;
padding: var(--gap-sm) 0 var(--gap-sm) var(--gap-md);
}
.btn {
width: 2.5rem;
height: 2.5rem;
}
}
}
}
.button-row {
display: flex;
flex-direction: row;
}
.modal {
position: absolute;
}
.code {
color: var(--color-brand);
padding: 0.05rem 0.1rem;
// row not column
display: flex;
.card {
background: var(--color-base);
color: var(--color-contrast);
padding: 0.5rem 1rem;
}
}
</style>

View File

@@ -1,7 +1,7 @@
<script setup> <script setup>
import { PlusIcon, XIcon } from '@modrinth/assets' import { PlusIcon, XIcon } from '@modrinth/assets'
import { import {
Button, ButtonStyled,
Checkbox, Checkbox,
commonMessages, commonMessages,
defineMessages, defineMessages,
@@ -179,14 +179,12 @@ const exportPack = async () => {
<div class="table-head"> <div class="table-head">
<div class="table-cell row-wise"> <div class="table-cell row-wise">
{{ formatMessage(messages.selectFilesLabel) }} {{ formatMessage(messages.selectFilesLabel) }}
<Button <ButtonStyled circular>
class="sleek-primary collapsed-button" <button @click="() => (showingFiles = !showingFiles)">
icon-only
@click="() => (showingFiles = !showingFiles)"
>
<PlusIcon v-if="!showingFiles" /> <PlusIcon v-if="!showingFiles" />
<XIcon v-else /> <XIcon v-else />
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div v-if="showingFiles" class="table-content"> <div v-if="showingFiles" class="table-content">
@@ -235,14 +233,18 @@ const exportPack = async () => {
</div> </div>
</div> </div>
<div class="button-row push-right"> <div class="button-row push-right">
<Button @click="exportModal.hide"> <ButtonStyled type="outlined">
<button @click="exportModal.hide">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</Button> </button>
<Button color="primary" @click="exportPack"> </ButtonStyled>
<ButtonStyled color="brand">
<button @click="exportPack">
<PackageIcon /> <PackageIcon />
{{ formatMessage(messages.exportButton) }} {{ formatMessage(messages.exportButton) }}
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</ModalWrapper> </ModalWrapper>

View File

@@ -15,10 +15,12 @@
<span>{{ javaInstall.path }}</span> <span>{{ javaInstall.path }}</span>
</div> </div>
<div class="table-cell table-text manage"> <div class="table-cell table-text manage">
<Button v-if="currentSelected.path === javaInstall.path" disabled <ButtonStyled v-if="currentSelected.path === javaInstall.path">
><CheckIcon /> Selected</Button <button disabled><CheckIcon /> Selected</button>
> </ButtonStyled>
<Button v-else @click="setJavaInstall(javaInstall)"><PlusIcon /> Select</Button> <ButtonStyled v-else>
<button @click="setJavaInstall(javaInstall)"><PlusIcon /> Select</button>
</ButtonStyled>
</div> </div>
</div> </div>
<div v-if="chosenInstallOptions.length === 0" class="table-row entire-row"> <div v-if="chosenInstallOptions.length === 0" class="table-row entire-row">
@@ -26,17 +28,19 @@
</div> </div>
</div> </div>
<div class="input-group push-right"> <div class="input-group push-right">
<Button @click="$refs.detectJavaModal.hide()"> <ButtonStyled type="outlined">
<button @click="$refs.detectJavaModal.hide()">
<XIcon /> <XIcon />
Cancel Cancel
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</ModalWrapper> </ModalWrapper>
</template> </template>
<script setup> <script setup>
import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets' import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets'
import { Button, injectNotificationManager } from '@modrinth/ui' import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import { ref } from 'vue' import { ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'

View File

@@ -17,35 +17,45 @@
" "
/> />
<span class="installation-buttons"> <span class="installation-buttons">
<Button <ButtonStyled v-if="props.version">
v-if="props.version" <button :disabled="props.disabled || installingJava" @click="reinstallJava">
:disabled="props.disabled || installingJava"
@click="reinstallJava"
>
<DownloadIcon /> <DownloadIcon />
{{ installingJava ? 'Installing...' : 'Install recommended' }} {{ installingJava ? 'Installing...' : 'Install recommended' }}
</Button> </button>
<Button :disabled="props.disabled" @click="autoDetect"> </ButtonStyled>
<ButtonStyled>
<button :disabled="props.disabled" @click="autoDetect">
<SearchIcon /> <SearchIcon />
Detect Detect
</Button> </button>
<Button :disabled="props.disabled" @click="handleJavaFileInput()"> </ButtonStyled>
<ButtonStyled>
<button :disabled="props.disabled" @click="handleJavaFileInput()">
<FolderSearchIcon /> <FolderSearchIcon />
Browse Browse
</Button> </button>
<Button v-if="testingJava" disabled> Testing... </Button> </ButtonStyled>
<Button v-else-if="testingJavaSuccess === true"> <ButtonStyled v-if="testingJava">
<CheckIcon class="test-success" /> <button disabled>Testing...</button>
</ButtonStyled>
<ButtonStyled v-else-if="testingJavaSuccess === true">
<button disabled>
<CheckIcon />
Success Success
</Button> </button>
<Button v-else-if="testingJavaSuccess === false"> </ButtonStyled>
<XIcon class="test-fail" /> <ButtonStyled v-else-if="testingJavaSuccess === false">
<button disabled>
<XIcon />
Failed Failed
</Button> </button>
<Button v-else :disabled="props.disabled" @click="testJava"> </ButtonStyled>
<ButtonStyled v-else>
<button :disabled="props.disabled" @click="testJava">
<PlayIcon /> <PlayIcon />
Test Test
</Button> </button>
</ButtonStyled>
</span> </span>
</div> </div>
</template> </template>
@@ -59,7 +69,7 @@ import {
SearchIcon, SearchIcon,
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Button, injectNotificationManager, StyledInput } from '@modrinth/ui' import { ButtonStyled, injectNotificationManager, StyledInput } from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog' import { open } from '@tauri-apps/plugin-dialog'
import { ref } from 'vue' import { ref } from 'vue'
@@ -204,10 +214,6 @@ async function reinstallJava() {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
margin: 0; margin: 0;
.btn {
width: max-content;
}
} }
.test-success { .test-success {

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { CheckIcon } from '@modrinth/assets' import { CheckIcon } from '@modrinth/assets'
import { Badge, Button } from '@modrinth/ui' import { Badge, ButtonStyled } from '@modrinth/ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { SwapIcon } from '@/assets/icons/index.js' import { SwapIcon } from '@/assets/icons/index.js'
@@ -74,15 +74,18 @@ const onHide = () => {
@click="$router.push(`/project/${version.project_id}/version/${version.id}`)" @click="$router.push(`/project/${version.project_id}/version/${version.id}`)"
> >
<div class="table-cell table-text"> <div class="table-cell table-text">
<Button <ButtonStyled
:color="version.id === installedVersion ? '' : 'primary'" circular
icon-only :color="version.id === installedVersion ? 'standard' : 'brand'"
>
<button
:disabled="inProgress || installing || version.id === installedVersion" :disabled="inProgress || installing || version.id === installedVersion"
@click.stop="() => switchVersion(version.id)" @click.stop="() => switchVersion(version.id)"
> >
<SwapIcon v-if="version.id !== installedVersion" /> <SwapIcon v-if="version.id !== installedVersion" />
<CheckIcon v-else /> <CheckIcon v-else />
</Button> </button>
</ButtonStyled>
</div> </div>
<div class="name-cell table-cell table-text"> <div class="name-cell table-cell table-text">
<div class="version-link"> <div class="version-link">

View File

@@ -1,5 +1,5 @@
<script setup> <script setup>
import { Button, injectNotificationManager, ProjectCard } from '@modrinth/ui' import { ButtonStyled, injectNotificationManager, ProjectCard } from '@modrinth/ui'
import { ref } from 'vue' import { ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
@@ -12,7 +12,6 @@ const { install: installVersion } = injectContentInstall()
const confirmModal = ref(null) const confirmModal = ref(null)
const project = ref(null) const project = ref(null)
const version = ref(null) const version = ref(null)
const installing = ref(false)
defineExpose({ defineExpose({
async show(event) { async show(event) {
@@ -70,7 +69,9 @@ async function install() {
</p> </p>
</div> </div>
<div class="button-group"> <div class="button-group">
<Button :loading="installing" color="primary" @click="install">Install</Button> <ButtonStyled color="brand">
<button @click="install">Install</button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -34,10 +34,14 @@
</tbody> </tbody>
</table> </table>
<div class="button-group"> <div class="button-group">
<Button @click="() => incompatibleModal.hide()"><XIcon />Cancel</Button> <ButtonStyled type="outlined">
<Button color="primary" :disabled="installing" @click="install()"> <button @click="() => incompatibleModal.hide()"><XIcon />Cancel</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="installing" @click="install()">
<DownloadIcon /> {{ installing ? 'Installing' : 'Install' }} <DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</ModalWrapper> </ModalWrapper>
@@ -45,7 +49,13 @@
<script setup> <script setup>
import { DownloadIcon, XIcon } from '@modrinth/assets' import { DownloadIcon, XIcon } from '@modrinth/assets'
import { Button, Combobox, formatLoader, injectNotificationManager, useVIntl } from '@modrinth/ui' import {
ButtonStyled,
Combobox,
formatLoader,
injectNotificationManager,
useVIntl,
} from '@modrinth/ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'

View File

@@ -1,418 +0,0 @@
<script setup>
import {
CheckIcon,
DownloadIcon,
PlusIcon,
RightArrowIcon,
SearchIcon,
UploadIcon,
XIcon,
} from '@modrinth/assets'
import { Avatar, Button, Card, injectNotificationManager, StyledInput } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/plugin-dialog'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project_v3_many } from '@/helpers/cache.js'
import {
add_project_from_version as installMod,
check_installed,
create,
get,
list,
} from '@/helpers/profile'
import {
findPreferredVersion,
installVersionDependencies,
isVersionCompatible,
} from '@/store/install.js'
const { handleError } = injectNotificationManager()
const router = useRouter()
const versions = ref()
const project = ref()
const installModal = ref()
const searchFilter = ref('')
const showCreation = ref(false)
const icon = ref(null)
const name = ref(null)
const display_icon = ref(null)
const loader = ref(null)
const gameVersion = ref(null)
const creatingInstance = ref(false)
const profiles = ref([])
const shownProfiles = computed(() =>
profiles.value.filter((profile) => {
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
}),
)
const isProfileCompatible = (profile) =>
versions.value?.some((version) => isVersionCompatible(version, project.value, profile))
const onInstall = ref(() => {})
defineExpose({
show: async (projectVal, versionsVal, callback) => {
project.value = projectVal
versions.value = versionsVal
searchFilter.value = ''
showCreation.value = false
name.value = null
icon.value = null
display_icon.value = null
gameVersion.value = null
loader.value = null
onInstall.value = callback
const profilesVal = await list().catch(handleError)
for (const profile of profilesVal) {
profile.installing = false
profile.installedMod = await check_installed(profile.path, project.value.id).catch(
handleError,
)
}
const linkedProjectIds = profilesVal
.filter((p) => p.linked_data?.project_id)
.map((p) => p.linked_data.project_id)
if (linkedProjectIds.length > 0) {
const linkedProjects = await get_project_v3_many(linkedProjectIds, 'must_revalidate').catch(
() => [],
)
const serverProjectIds = new Set(
linkedProjects.filter((p) => p?.minecraft_server != null).map((p) => p.id),
)
for (const profile of profilesVal) {
profile.isServerInstance = serverProjectIds.has(profile.linked_data?.project_id)
}
}
profiles.value = profilesVal
installModal.value.show()
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
},
})
async function install(instance) {
instance.installing = true
const version = findPreferredVersion(versions.value, project.value, instance)
if (!version) {
instance.installing = false
handleError('No compatible version found')
return
}
await installMod(instance.path, version.id, 'standalone').catch(handleError)
await installVersionDependencies(instance, version).catch(handleError)
instance.installedMod = true
instance.installing = false
trackEvent('ProjectInstall', {
loader: instance.loader,
game_version: instance.game_version,
id: project.value.id,
version_id: version.id,
project_type: project.value.project_type,
title: project.value.title,
source: 'ProjectInstallModal',
})
onInstall.value(version.id)
}
const toggleCreation = () => {
showCreation.value = !showCreation.value
name.value = null
icon.value = null
display_icon.value = null
gameVersion.value = null
loader.value = null
if (showCreation.value) {
trackEvent('InstanceCreateStart', { source: 'ProjectInstallModal' })
}
}
const upload_icon = async () => {
const res = await open({
multiple: false,
filters: [
{
name: 'Image',
extensions: ['png', 'jpeg'],
},
],
})
icon.value = res.path ?? res
if (!icon.value) return
display_icon.value = convertFileSrc(icon.value)
}
const reset_icon = () => {
icon.value = null
display_icon.value = null
}
const createInstance = async () => {
creatingInstance.value = true
const gameVersions = versions.value[0].game_versions
const gameVersion = gameVersions[0]
const loaders = versions.value[0].loaders
const loader = loaders.includes('fabric')
? 'fabric'
: loaders.includes('neoforge')
? 'neoforge'
: loaders.includes('forge')
? 'forge'
: loaders.includes('quilt')
? 'quilt'
: 'vanilla'
const id = await create(name.value, gameVersion, loader, 'latest', icon.value).catch(handleError)
await installMod(id, versions.value[0].id, 'standalone').catch(handleError)
await router.push(`/instance/${encodeURIComponent(id)}/`)
const instance = await get(id, true)
await installVersionDependencies(instance, versions.value[0]).catch(handleError)
trackEvent('InstanceCreate', {
profile_name: name.value,
game_version: versions.value[0].game_versions[0],
loader: loader,
loader_version: 'latest',
has_icon: !!icon.value,
source: 'ProjectInstallModal',
})
trackEvent('ProjectInstall', {
loader: loader,
game_version: versions.value[0].game_versions[0],
id: project.value,
version_id: versions.value[0].id,
project_type: project.value.project_type,
title: project.value.title,
source: 'ProjectInstallModal',
})
onInstall.value(versions.value[0].id)
if (installModal.value) installModal.value.hide()
creatingInstance.value = false
}
</script>
<template>
<ModalWrapper ref="installModal" header="Install project to instance" :on-hide="onInstall">
<div class="modal-body">
<StyledInput
v-model="searchFilter"
:icon="SearchIcon"
type="search"
placeholder="Search for an instance"
autocomplete="off"
/>
<div class="profiles" :class="{ 'hide-creation': !showCreation }">
<div v-for="profile in shownProfiles" :key="profile.name" class="option">
<router-link
class="btn btn-transparent profile-button"
:to="`/instance/${encodeURIComponent(profile.path)}`"
@click="installModal.hide()"
>
<Avatar
:src="profile.icon_path ? convertFileSrc(profile.icon_path) : null"
class="profile-image"
/>
{{ profile.name }}
</router-link>
<div
v-tooltip="
profile.linked_data?.locked && !profile.installedMod
? 'Unpair or unlock an instance to add mods.'
: ''
"
>
<Button
:disabled="
!isProfileCompatible(profile) || profile.installedMod || profile.installing
"
@click="install(profile)"
>
<DownloadIcon
v-if="isProfileCompatible(profile) && !profile.installedMod && !profile.installing"
/>
<CheckIcon v-else-if="profile.installedMod" />
{{
profile.installing
? 'Installing...'
: profile.installedMod
? 'Installed'
: !isProfileCompatible(profile)
? 'Incompatible'
: 'Install'
}}
</Button>
</div>
</div>
</div>
<Card v-if="showCreation" class="creation-card">
<div class="creation-container">
<div class="creation-icon">
<Avatar size="md" class="icon" :src="display_icon" />
<div class="creation-icon__description">
<Button @click="upload_icon()">
<UploadIcon />
<span class="no-wrap"> Select icon </span>
</Button>
<Button :disabled="!display_icon" @click="reset_icon()">
<XIcon />
<span class="no-wrap"> Remove icon </span>
</Button>
</div>
</div>
<div class="creation-settings">
<StyledInput
v-model="name"
autocomplete="off"
type="text"
placeholder="Name"
class="creation-input"
/>
<Button :disabled="creatingInstance === true || !name" @click="createInstance()">
<RightArrowIcon />
{{ creatingInstance ? 'Creating...' : 'Create' }}
</Button>
</div>
</div>
</Card>
<div class="input-group push-right">
<Button :color="showCreation ? '' : 'primary'" @click="toggleCreation()">
<PlusIcon />
{{ showCreation ? 'Hide New Instance' : 'Create new instance' }}
</Button>
<Button @click="installModal.hide()">Cancel</Button>
</div>
</div>
</ModalWrapper>
</template>
<style scoped lang="scss">
.creation-card {
display: flex;
flex-direction: column;
gap: 1rem;
margin: 0;
background-color: var(--color-bg);
}
.creation-container {
display: flex;
flex-direction: row;
gap: 1rem;
}
.creation-icon {
display: flex;
flex-direction: row;
gap: 1rem;
align-items: center;
flex-grow: 1;
.creation-icon__description {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}
.creation-input {
width: 100%;
}
.no-wrap {
white-space: nowrap;
}
.creation-dropdown {
width: min-content !important;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.creation-settings {
width: 100%;
margin-left: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
justify-content: center;
}
.modal-body {
display: flex;
flex-direction: column;
gap: 1rem;
min-width: 350px;
}
.profiles {
max-height: 12rem;
overflow-y: auto;
&.hide-creation {
max-height: 21rem;
}
}
.option {
width: calc(100%);
background: var(--color-raised-bg);
color: var(--color-base);
box-shadow: none;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
img {
margin-right: 0.5rem;
}
.name {
display: flex;
flex-direction: column;
justify-content: center;
}
.profile-button {
align-content: start;
padding: 0.5rem;
text-align: left;
}
}
.profile-image {
--size: 2rem !important;
}
</style>

View File

@@ -7,7 +7,7 @@
<template #actions> <template #actions>
<div class="flex gap-2 justify-end"> <div class="flex gap-2 justify-end">
<ButtonStyled type="outlined"> <ButtonStyled type="outlined">
<button class="!border !border-surface-4" @click="modal?.hide()"> <button @click="modal?.hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>

View File

@@ -11,7 +11,7 @@
<template #actions> <template #actions>
<div class="flex gap-2 justify-end"> <div class="flex gap-2 justify-end">
<ButtonStyled type="outlined"> <ButtonStyled type="outlined">
<button class="!border !border-surface-4" @click="handleCancel"> <button @click="handleCancel">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets' import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
import { Button, injectNotificationManager, Slider, StyledInput } from '@modrinth/ui' import { ButtonStyled, injectNotificationManager, Slider, StyledInput } from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog' import { open } from '@tauri-apps/plugin-dialog'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
@@ -73,9 +73,11 @@ async function findLauncherDir() {
wrapper-class="w-full" wrapper-class="w-full"
> >
<template #right> <template #right>
<Button class="ml-1.5" @click="findLauncherDir"> <ButtonStyled circular>
<button class="ml-1.5" @click="findLauncherDir">
<FolderSearchIcon /> <FolderSearchIcon />
</Button> </button>
</ButtonStyled>
</template> </template>
</StyledInput> </StyledInput>
<p class="m-0 leading-tight text-secondary"> <p class="m-0 leading-tight text-secondary">

View File

@@ -25,7 +25,9 @@
<div class="flex flex-col gap-4 w-full min-h-[20rem]"> <div class="flex flex-col gap-4 w-full min-h-[20rem]">
<section> <section>
<h2 class="text-base font-semibold mb-2">Texture</h2> <h2 class="text-base font-semibold mb-2">Texture</h2>
<Button @click="openUploadSkinModal"> <UploadIcon /> Replace texture </Button> <ButtonStyled>
<button @click="openUploadSkinModal"><UploadIcon /> Replace texture</button>
</ButtonStyled>
</section> </section>
<section> <section>
@@ -79,7 +81,7 @@
</div> </div>
<div class="flex gap-2 mt-12"> <div class="flex gap-2 mt-12">
<ButtonStyled color="brand" :disabled="disableSave || isSaving"> <ButtonStyled color="brand">
<button v-tooltip="saveTooltip" :disabled="disableSave || isSaving" @click="save"> <button v-tooltip="saveTooltip" :disabled="disableSave || isSaving" @click="save">
<SpinnerIcon v-if="isSaving" class="animate-spin" /> <SpinnerIcon v-if="isSaving" class="animate-spin" />
<CheckIcon v-else-if="mode === 'new'" /> <CheckIcon v-else-if="mode === 'new'" />
@@ -87,7 +89,9 @@
{{ mode === 'new' ? 'Add skin' : 'Save skin' }} {{ mode === 'new' ? 'Add skin' : 'Save skin' }}
</button> </button>
</ButtonStyled> </ButtonStyled>
<Button :disabled="isSaving" @click="hide"><XIcon />Cancel</Button> <ButtonStyled type="outlined">
<button :disabled="isSaving" @click="hide"><XIcon />Cancel</button>
</ButtonStyled>
</div> </div>
</ModalWrapper> </ModalWrapper>
@@ -109,7 +113,6 @@ import {
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
Button,
ButtonStyled, ButtonStyled,
CapeButton, CapeButton,
CapeLikeTextButton, CapeLikeTextButton,

View File

@@ -93,7 +93,7 @@ defineExpose({ show, hide })
<template #actions> <template #actions>
<div class="flex gap-2 justify-end"> <div class="flex gap-2 justify-end">
<ButtonStyled type="outlined"> <ButtonStyled type="outlined">
<button class="!border !border-surface-4" @click="hide()"> <button @click="hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>

View File

@@ -106,7 +106,7 @@ const titleMessage = defineMessage({
<template #actions> <template #actions>
<div class="flex gap-2 justify-end"> <div class="flex gap-2 justify-end">
<ButtonStyled type="outlined"> <ButtonStyled type="outlined">
<button class="!border !border-surface-4" @click="hide()"> <button @click="hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>

View File

@@ -692,6 +692,24 @@
"instance.worlds.world_in_use": { "instance.worlds.world_in_use": {
"message": "World is in use" "message": "World is in use"
}, },
"minecraft-account.add-account": {
"message": "Add account"
},
"minecraft-account.label": {
"message": "Minecraft account"
},
"minecraft-account.not-signed-in": {
"message": "Not signed in"
},
"minecraft-account.remove-account": {
"message": "Remove account"
},
"minecraft-account.select-account": {
"message": "Select account"
},
"minecraft-account.sign-in": {
"message": "Sign in to Minecraft"
},
"search.filter.locked.instance": { "search.filter.locked.instance": {
"message": "Provided by the instance" "message": "Provided by the instance"
}, },

View File

@@ -9,7 +9,6 @@ import {
UpdatedIcon, UpdatedIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
Button,
ButtonStyled, ButtonStyled,
ConfirmModal, ConfirmModal,
injectNotificationManager, injectNotificationManager,
@@ -383,25 +382,25 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
@select="changeSkin(skin)" @select="changeSkin(skin)"
> >
<template #overlay-buttons> <template #overlay-buttons>
<Button <ButtonStyled color="brand">
color="green" <button
aria-label="Edit skin" aria-label="Edit skin"
class="pointer-events-auto" class="pointer-events-auto"
@click.stop="(e: MouseEvent) => editSkinModal?.show(e, skin)" @click.stop="(e: MouseEvent) => editSkinModal?.show(e, skin)"
> >
<EditIcon /> Edit <EditIcon /> Edit
</Button> </button>
<Button </ButtonStyled>
v-show="!skin.is_equipped" <ButtonStyled v-show="!skin.is_equipped" circular color="red">
<button
v-tooltip="'Delete skin'" v-tooltip="'Delete skin'"
aria-label="Delete skin" aria-label="Delete skin"
color="red"
class="!rounded-[100%] pointer-events-auto" class="!rounded-[100%] pointer-events-auto"
icon-only
@click.stop="() => confirmDeleteSkin(skin)" @click.stop="() => confirmDeleteSkin(skin)"
> >
<TrashIcon /> <TrashIcon />
</Button> </button>
</ButtonStyled>
</template> </template>
</SkinButton> </SkinButton>
</div> </div>

View File

@@ -55,7 +55,7 @@
/> />
<div class="flex gap-2"> <div class="flex gap-2">
<ButtonStyled type="outlined"> <ButtonStyled type="outlined">
<button class="!h-10 !border-button-bg !border-[1px]" @click="addServerModal?.show()"> <button class="!h-10" @click="addServerModal?.show()">
<PlusIcon class="size-5" /> <PlusIcon class="size-5" />
{{ formatMessage(messages.addServer) }} {{ formatMessage(messages.addServer) }}
</button> </button>
@@ -141,7 +141,7 @@
> >
<template #actions> <template #actions>
<ButtonStyled type="outlined"> <ButtonStyled type="outlined">
<button class="!h-10 !border-button-bg !border-[1px]" @click="addServerModal?.show()"> <button class="!h-10" @click="addServerModal?.show()">
<PlusIcon class="size-5" /> <PlusIcon class="size-5" />
{{ formatMessage(messages.addServer) }} {{ formatMessage(messages.addServer) }}
</button> </button>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { PlusIcon } from '@modrinth/assets' import { PlusIcon } from '@modrinth/assets'
import { Button, injectNotificationManager, NavTabs } from '@modrinth/ui' import { ButtonStyled, injectNotificationManager, NavTabs } from '@modrinth/ui'
import { inject, onUnmounted, ref, shallowRef } from 'vue' import { inject, onUnmounted, ref, shallowRef } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
@@ -55,10 +55,12 @@ onUnmounted(() => {
<NewInstanceImage /> <NewInstanceImage />
</div> </div>
<h3>No instances found</h3> <h3>No instances found</h3>
<Button color="primary" :disabled="offline" @click="showCreationModal?.()"> <ButtonStyled color="brand">
<button :disabled="offline" @click="showCreationModal?.()">
<PlusIcon /> <PlusIcon />
Create new instance Create new instance
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -39,9 +39,12 @@
</div> </div>
<div class="controls"> <div class="controls">
<div class="buttons"> <div class="buttons">
<Button class="close" icon-only @click="hideImage"> <ButtonStyled circular>
<button class="close" @click="hideImage">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
</Button> </button>
</ButtonStyled>
<ButtonStyled circular>
<a <a
class="open btn icon-only" class="open btn icon-only"
target="_blank" target="_blank"
@@ -53,21 +56,23 @@
> >
<ExternalIcon aria-hidden="true" /> <ExternalIcon aria-hidden="true" />
</a> </a>
<Button icon-only @click="zoomedIn = !zoomedIn"> </ButtonStyled>
<ButtonStyled circular>
<button @click="zoomedIn = !zoomedIn">
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" /> <ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" /> <ContractIcon v-else aria-hidden="true" />
</Button> </button>
<Button </ButtonStyled>
v-if="filteredGallery.length > 1" <ButtonStyled v-if="filteredGallery.length > 1" circular>
class="previous" <button class="previous" @click="previousImage()">
icon-only
@click="previousImage()"
>
<LeftArrowIcon aria-hidden="true" /> <LeftArrowIcon aria-hidden="true" />
</Button> </button>
<Button v-if="filteredGallery.length > 1" class="next" icon-only @click="nextImage()"> </ButtonStyled>
<ButtonStyled v-if="filteredGallery.length > 1" circular>
<button class="next" @click="nextImage()">
<RightArrowIcon aria-hidden="true" /> <RightArrowIcon aria-hidden="true" />
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -85,7 +90,7 @@ import {
RightArrowIcon, RightArrowIcon,
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Button, Card, useFormatDateTime } from '@modrinth/ui' import { ButtonStyled, Card, useFormatDateTime } from '@modrinth/ui'
import { computed, onMounted, onUnmounted, ref } from 'vue' import { computed, onMounted, onUnmounted, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js' import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'

View File

@@ -14,10 +14,10 @@
<h2>{{ version.name }}</h2> <h2>{{ version.name }}</h2>
</div> </div>
<div class="button-group"> <div class="button-group">
<Button <ButtonStyled color="brand">
color="primary" <button
:action="() => install(version.id)"
:disabled="installing || (installed && installedVersion === version.id)" :disabled="installing || (installed && installedVersion === version.id)"
@click="() => install(version.id)"
> >
<DownloadIcon v-if="!installed" /> <DownloadIcon v-if="!installed" />
<SwapIcon v-else-if="installedVersion !== version.id" /> <SwapIcon v-else-if="installedVersion !== version.id" />
@@ -29,19 +29,23 @@
? 'Installed' ? 'Installed'
: 'Install' : 'Install'
}} }}
</Button> </button>
<Button> </ButtonStyled>
<ButtonStyled>
<button>
<ReportIcon /> <ReportIcon />
Report Report
</Button> </button>
</ButtonStyled>
<ButtonStyled>
<a <a
:href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`" :href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`"
rel="external" rel="external"
class="btn"
> >
<ExternalIcon />
Modrinth website Modrinth website
<ExternalIcon />
</a> </a>
</ButtonStyled>
</div> </div>
</Card> </Card>
<div class="version-container"> <div class="version-container">
@@ -68,16 +72,13 @@
<span v-if="file.primary" class="primary-label"> Primary </span> <span v-if="file.primary" class="primary-label"> Primary </span>
</span> </span>
</span> </span>
<Button <ButtonStyled v-if="project.project_type !== 'modpack' || file.primary" color="brand">
v-if="project.project_type !== 'modpack' || file.primary" <button class="download" :disabled="installed" @click="() => install(version.id)">
class="download"
:action="() => install(version.id)"
:disabled="installed"
>
<DownloadIcon v-if="!installed" /> <DownloadIcon v-if="!installed" />
<CheckIcon v-else /> <CheckIcon v-else />
{{ installed ? 'Installed' : 'Install' }} {{ installed ? 'Installed' : 'Install' }}
</Button> </button>
</ButtonStyled>
</Card> </Card>
</Card> </Card>
<Card v-if="displayDependencies.length > 0"> <Card v-if="displayDependencies.length > 0">
@@ -168,7 +169,15 @@
<script setup> <script setup>
import { CheckIcon, DownloadIcon, ExternalIcon, FileIcon, ReportIcon } from '@modrinth/assets' import { CheckIcon, DownloadIcon, ExternalIcon, FileIcon, ReportIcon } from '@modrinth/assets'
import { Avatar, Badge, Breadcrumbs, Button, Card, CopyCode, useFormatDateTime } from '@modrinth/ui' import {
Avatar,
Badge,
Breadcrumbs,
ButtonStyled,
Card,
CopyCode,
useFormatDateTime,
} from '@modrinth/ui'
import { formatBytes, renderString } from '@modrinth/utils' import { formatBytes, renderString } from '@modrinth/utils'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'

View File

@@ -134,10 +134,6 @@ const [loaders, gameVersions] = await Promise.all([
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
flex-grow: 1; flex-grow: 1;
.multiselect {
flex-grow: 1;
}
} }
.card-row { .card-row {

File diff suppressed because one or more lines are too long

View File

@@ -1,64 +1,8 @@
/*
Base components
*/
.known-error .multiselect__tags {
border-color: var(--color-red) !important;
background-color: var(--color-warning-bg) !important;
&::placeholder {
color: var(--color-warning-text);
}
}
.grid-display {
display: grid;
grid-gap: var(--spacing-card-md);
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
.grid-display__item {
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: flex-start;
background-color: var(--color-bg);
border-radius: var(--size-rounded-card);
padding: var(--spacing-card-lg);
gap: var(--spacing-card-md);
outline: 1px solid transparent;
.label {
color: var(--color-heading);
font-weight: bold;
font-size: 1rem;
}
.value {
color: var(--color-text-primary);
font-weight: bold;
font-size: 2rem;
}
.goto-link {
margin-top: auto;
}
}
&.width-12 {
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}
&.width-16 {
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}
}
/* /*
Cards and body styling Cards and body styling
*/ */
.base-card { .base-card {
@extend .padding-lg; padding: var(--spacing-card-lg);
position: relative; position: relative;
background-color: var(--color-raised-bg); background-color: var(--color-raised-bg);
@@ -129,73 +73,9 @@
} }
} }
.padding-lg {
padding: var(--spacing-card-lg);
}
.padding-bg {
padding: var(--spacing-card-bg);
}
.padding-md {
padding: var(--spacing-card-md);
}
.padding-sm {
padding: var(--spacing-card-sm);
}
.padding-0 {
padding: 0;
}
.padding-block-lg {
padding-block: var(--spacing-card-lg);
}
.padding-block-bg {
padding-block: var(--spacing-card-bg);
}
.padding-block-md {
padding-block: var(--spacing-card-md);
}
.padding-block-sm {
padding-block: var(--spacing-card-sm);
}
.padding-block-0 {
padding-block: 0;
}
.padding-inline-lg {
padding-inline: var(--spacing-card-lg);
}
.padding-inline-bg {
padding-inline: var(--spacing-card-bg);
}
.padding-inline-md {
padding-inline: var(--spacing-card-md);
}
.padding-inline-sm {
padding-inline: var(--spacing-card-sm);
}
.padding-inline-0 {
padding-inline: 0;
}
.universal-body { .universal-body {
@extend .universal-labels; @extend .universal-labels;
.multiselect {
width: 15rem;
}
> :where(input + *, .input-group + *, .chips + *, .input-div + *) { > :where(input + *, .input-group + *, .chips + *, .input-div + *) {
&:not(:empty) { &:not(:empty) {
margin-block-start: var(--spacing-card-md); margin-block-start: var(--spacing-card-md);
@@ -340,19 +220,6 @@
} }
} }
.navigation-card {
@extend .base-card;
@extend .padding-inline-lg;
@extend .padding-block-md;
align-items: center;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 0.5rem;
min-height: 3.75rem;
}
/* /*
Other Other
*/ */
@@ -372,19 +239,6 @@
} }
} }
.title-link {
text-decoration: underline;
&:focus-visible,
&:hover {
color: var(--color-heading);
}
&:active {
color: var(--color-text-primary);
}
}
.button-base { .button-base {
@extend .button-animation; @extend .button-animation;
font-weight: 500; font-weight: 500;
@@ -455,36 +309,6 @@ tr.button-transparent {
} }
} }
.button-within {
transition:
opacity 0.5s ease-in-out,
filter 0.2s ease-in-out,
transform 0.05s ease-in-out,
outline 0.2s ease-in-out;
&:focus-visible:not(&.disabled),
&:hover:not(&.disabled) {
filter: brightness(0.85);
}
&:active:not(&.disabled) {
filter: brightness(0.8);
}
&.disabled {
cursor: not-allowed;
filter: grayscale(50%);
opacity: 0.5;
box-shadow: none;
&disabled,
&[disabled='true'] {
cursor: not-allowed;
box-shadow: none;
}
}
}
.button-color-base { .button-color-base {
box-sizing: border-box; box-sizing: border-box;
--text-color: var(--color-button-text); --text-color: var(--color-button-text);
@@ -536,10 +360,6 @@ tr.button-transparent {
background: none; background: none;
box-shadow: none; box-shadow: none;
} }
&.bold-button {
font-weight: bold;
}
} }
.square-button { .square-button {
@@ -570,24 +390,6 @@ tr.button-transparent {
flex-shrink: 0; flex-shrink: 0;
} }
.header-button {
@extend .iconified-button;
box-sizing: content-box;
min-height: unset;
border-radius: var(--size-rounded-max);
white-space: nowrap;
padding: 0.5rem 0.75rem;
max-height: 1.75rem;
svg {
vertical-align: middle;
margin-right: 0.5rem;
height: 1.25rem;
width: 1.25rem;
}
}
.raised-button { .raised-button {
--background-color: var(--color-raised-bg); --background-color: var(--color-raised-bg);
box-shadow: var(--shadow-inset-sm), var(--shadow-raised); box-shadow: var(--shadow-inset-sm), var(--shadow-raised);
@@ -608,11 +410,6 @@ tr.button-transparent {
--text-color: var(--color-brand-inverted); --text-color: var(--color-brand-inverted);
} }
.alt-brand-button {
--background-color: var(--color-brand-highlight);
--text-color: var(--color-text);
}
.button-group { .button-group {
display: flex; display: flex;
grid-gap: var(--spacing-card-sm); grid-gap: var(--spacing-card-sm);
@@ -621,21 +418,6 @@ tr.button-transparent {
justify-content: right; justify-content: right;
} }
.multiselect--above .multiselect__content-wrapper {
border-top: none !important;
border-top-left-radius: var(--size-rounded-card) !important;
border-top-right-radius: var(--size-rounded-card) !important;
}
.known-error .multiselect__tags {
border-color: var(--color-red) !important;
background-color: var(--color-warning-bg) !important;
&::placeholder {
color: var(--color-warning-text);
}
}
.error { .error {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -707,36 +489,6 @@ textarea.known-error {
color: var(--color-link-active); color: var(--color-link-active);
} }
h1 {
.beta-badge {
font-size: 0.4em;
}
}
.beta-badge {
font-size: 0.7em;
padding: 0.2rem 0.4rem;
background-color: transparent;
box-sizing: border-box;
border: 2px solid var(--color-text);
color: var(--color-text);
border-radius: var(--size-rounded-max);
margin-left: 0.5rem;
font-weight: bold;
}
.router-link-exact-active,
h1,
h2,
h3 {
.beta-badge {
background-color: var(--color-button-text-active);
box-sizing: border-box;
border-color: transparent;
color: var(--color-raised-bg);
}
}
@media (prefers-reduced-motion) { @media (prefers-reduced-motion) {
.button-animation, .button-animation,
button { button {
@@ -751,7 +503,6 @@ h3 {
} }
.full-width-inputs { .full-width-inputs {
.multiselect,
input, input,
.iconified-input { .iconified-input {
width: 100%; width: 100%;
@@ -774,10 +525,6 @@ button {
max-width: 100%; max-width: 100%;
align-items: center; align-items: center;
.multiselect {
width: 15rem;
}
input { input {
flex-shrink: 2; flex-shrink: 2;
} }
@@ -799,20 +546,6 @@ button {
} }
} }
.input-stack {
display: flex;
flex-direction: column;
> *:not(:last-child) {
margin-bottom: var(--spacing-card-sm);
}
> .multiselect {
width: unset;
height: inherit;
}
}
.text-input-wrapper { .text-input-wrapper {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -844,22 +577,6 @@ button {
user-select: none; user-select: none;
} }
.text-input-wrapper__after {
display: flex;
color: var(--color-text);
padding: 0.5rem 1rem 0.5rem 0;
font-weight: var(--font-weight-medium);
min-height: 36px;
box-sizing: border-box;
width: fit-content;
align-items: center;
filter: grayscale(50%);
opacity: 0.5;
box-shadow: none;
flex-shrink: 0;
user-select: none;
}
input, input,
textarea { textarea {
border-radius: 0; border-radius: 0;
@@ -880,30 +597,6 @@ button {
} }
} }
.primary-stat {
align-items: center;
display: flex;
margin-bottom: 0.6rem;
.primary-stat__icon {
height: 1rem;
width: 1rem;
}
.primary-stat__text {
margin-left: 0.4rem;
}
.primary-stat__counter {
font-size: var(--font-size-lg);
font-weight: bold;
}
&.no-margin {
margin: 0;
}
}
.project-list { .project-list {
width: 100%; width: 100%;
gap: var(--spacing-card-md); gap: var(--spacing-card-md);
@@ -1033,99 +726,18 @@ a.iconified-link {
} }
} }
a.subtle-link {
&:focus-visible,
&:hover {
text-decoration: underline;
filter: var(--hover-filter);
}
&:active {
filter: var(--active-filter);
}
}
.inline-svg svg, .inline-svg svg,
svg.inline-svg { svg.inline-svg {
vertical-align: middle; vertical-align: middle;
} }
// START STUFF FOR OMORPHIA // START STUFF FOR OMORPHIA
.experimental-styles-within {
.tag-list { .tag-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--gap-4); gap: var(--gap-4);
} }
.tag-list__item {
background-color: var(--_bg-color, var(--color-button-bg));
padding: var(--gap-4) var(--gap-8);
border-radius: var(--radius-max);
font-weight: var(--weight-bold);
font-size: var(--text-14);
display: flex;
gap: var(--gap-4);
align-items: center;
vertical-align: middle;
color: var(--_color, var(--color-secondary));
svg {
width: var(--icon-14);
height: var(--icon-14);
display: flex;
}
}
.status-list {
display: flex;
flex-direction: column;
gap: var(--gap-8);
padding-left: var(--gap-6);
color: var(--color-base);
font-weight: var(--weight-bold);
}
.status-list__item {
display: flex;
align-items: center;
gap: var(--gap-4);
svg {
width: var(--icon-16);
height: var(--icon-16);
margin-right: var(--gap-4);
}
span {
color: var(--color-secondary);
font-style: italic;
font-weight: var(--weight-normal);
}
}
.status-list__item--color-green svg {
color: var(--color-green);
}
.status-list__item--color-orange svg {
color: var(--color-orange);
}
.status-list__item--color-red svg {
color: var(--color-red);
}
.status-list__item--color-blue svg {
color: var(--color-blue);
}
.status-list__item--color-purple svg {
color: var(--color-purple);
}
&.flex-card,
.flex-card { .flex-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1184,18 +796,6 @@ svg.inline-svg {
} }
} }
.links-list {
@extend .list-style;
> a {
@extend .iconified-list-item;
&:hover {
text-decoration: underline;
}
}
}
.details-list { .details-list {
@extend .list-style; @extend .list-style;
} }
@@ -1209,7 +809,6 @@ svg.inline-svg {
font-size: var(--text-14); font-size: var(--text-14);
} }
} }
}
.card-shadow { .card-shadow {
box-shadow: var(--shadow-card); box-shadow: var(--shadow-card);

View File

@@ -12,9 +12,11 @@
<div class="modal-body"> <div class="modal-body">
<div v-if="header" class="header"> <div v-if="header" class="header">
<strong>{{ header }}</strong> <strong>{{ header }}</strong>
<button class="iconified-button icon-only transparent" @click="hide"> <ButtonStyled circular type="transparent">
<button @click="hide">
<XIcon /> <XIcon />
</button> </button>
</ButtonStyled>
</div> </div>
<div class="content"> <div class="content">
<slot /> <slot />
@@ -27,9 +29,11 @@
<script> <script>
import { XIcon } from '@modrinth/assets' import { XIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
export default { export default {
components: { components: {
ButtonStyled,
XIcon, XIcon,
}, },
props: { props: {

View File

@@ -223,9 +223,7 @@ function developerModeIncrement() {
</script> </script>
<template> <template>
<footer <footer class="footer-brand-background border-0 border-t-[1px] border-solid">
class="footer-brand-background experimental-styles-within border-0 border-t-[1px] border-solid"
>
<div class="mx-auto flex max-w-screen-xl flex-col gap-6 p-6 pb-20 sm:px-12 md:py-12"> <div class="mx-auto flex max-w-screen-xl flex-col gap-6 p-6 pb-20 sm:px-12 md:py-12">
<div <div
class="grid grid-cols-1 gap-4 text-primary md:grid-cols-[1fr_2fr] lg:grid-cols-[auto_auto_auto_auto_auto]" class="grid grid-cols-1 gap-4 text-primary md:grid-cols-[1fr_2fr] lg:grid-cols-[auto_auto_auto_auto_auto]"

View File

@@ -199,9 +199,9 @@
</span> </span>
<div v-if="compact" class="notification__actions"> <div v-if="compact" class="notification__actions">
<template v-if="type === 'team_invite' || type === 'organization_invite'"> <template v-if="type === 'team_invite' || type === 'organization_invite'">
<ButtonStyled circular color="brand" type="transparent">
<button <button
v-tooltip="`Accept`" v-tooltip="`Accept`"
class="iconified-button square-button brand-button button-transparent"
@click=" @click="
() => { () => {
acceptTeamInvite(notification.body.team_id) acceptTeamInvite(notification.body.team_id)
@@ -211,9 +211,10 @@
> >
<CheckIcon /> <CheckIcon />
</button> </button>
</ButtonStyled>
<ButtonStyled circular color="red" type="transparent">
<button <button
v-tooltip="`Decline`" v-tooltip="`Decline`"
class="iconified-button square-button danger-button button-transparent"
@click=" @click="
() => { () => {
removeSelfFromTeam(notification.body.team_id) removeSelfFromTeam(notification.body.team_id)
@@ -223,23 +224,21 @@
> >
<XIcon /> <XIcon />
</button> </button>
</ButtonStyled>
</template> </template>
<button <ButtonStyled v-else-if="!notification.read" circular type="transparent">
v-else-if="!notification.read" <button v-tooltip="`Mark as read`" @click="read()">
v-tooltip="`Mark as read`"
class="iconified-button square-button button-transparent"
@click="read()"
>
<XIcon /> <XIcon />
</button> </button>
</ButtonStyled>
</div> </div>
<div v-else class="notification__actions"> <div v-else class="notification__actions">
<div v-if="type !== null" class="input-group"> <div v-if="type !== null" class="input-group">
<template <template
v-if="(type === 'team_invite' || type === 'organization_invite') && !notification.read" v-if="(type === 'team_invite' || type === 'organization_invite') && !notification.read"
> >
<ButtonStyled color="brand">
<button <button
class="iconified-button brand-button"
@click=" @click="
() => { () => {
acceptTeamInvite(notification.body.team_id) acceptTeamInvite(notification.body.team_id)
@@ -250,8 +249,9 @@
<CheckIcon /> <CheckIcon />
Accept Accept
</button> </button>
</ButtonStyled>
<ButtonStyled color="red">
<button <button
class="iconified-button danger-button"
@click=" @click="
() => { () => {
removeSelfFromTeam(notification.body.team_id) removeSelfFromTeam(notification.body.team_id)
@@ -262,49 +262,36 @@
<XIcon /> <XIcon />
Decline Decline
</button> </button>
</ButtonStyled>
</template> </template>
<button <ButtonStyled v-else-if="!notification.read">
v-else-if="!notification.read" <button @click="read()">
class="iconified-button"
:class="{ 'raised-button': raised }"
@click="read()"
>
<CheckIcon /> <CheckIcon />
Mark as read Mark as read
</button> </button>
</ButtonStyled>
<CopyCode v-if="flags.developerMode" :text="notification.id" /> <CopyCode v-if="flags.developerMode" :text="notification.id" />
</div> </div>
<div v-else class="input-group"> <div v-else class="input-group">
<nuxt-link <ButtonStyled v-if="notification.link && notification.link !== '#'">
v-if="notification.link && notification.link !== '#'" <nuxt-link :to="notification.link" target="_blank">
class="iconified-button"
:class="{ 'raised-button': raised }"
:to="notification.link"
target="_blank"
>
<ExternalIcon /> <ExternalIcon />
Open link Open link
</nuxt-link> </nuxt-link>
<button </ButtonStyled>
v-for="(action, actionIndex) in notification.actions" <ButtonStyled v-for="(action, actionIndex) in notification.actions" :key="actionIndex">
:key="actionIndex" <button @click="performAction(notification, actionIndex)">
class="iconified-button"
:class="{ 'raised-button': raised }"
@click="performAction(notification, actionIndex)"
>
<CheckIcon v-if="action.title === 'Accept'" /> <CheckIcon v-if="action.title === 'Accept'" />
<XIcon v-else-if="action.title === 'Deny'" /> <XIcon v-else-if="action.title === 'Deny'" />
{{ action.title }} {{ action.title }}
</button> </button>
<button </ButtonStyled>
v-if="notification.actions.length === 0 && !notification.read" <ButtonStyled v-if="notification.actions.length === 0 && !notification.read">
class="iconified-button" <button @click="performAction(notification, null)">
:class="{ 'raised-button': raised }"
@click="performAction(notification, null)"
>
<CheckIcon /> <CheckIcon />
Mark as read Mark as read
</button> </button>
</ButtonStyled>
<CopyCode v-if="flags.developerMode" :text="notification.id" /> <CopyCode v-if="flags.developerMode" :text="notification.id" />
</div> </div>
</div> </div>
@@ -325,6 +312,7 @@ import {
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
Avatar, Avatar,
ButtonStyled,
Categories, Categories,
CopyCode, CopyCode,
DoubleIcon, DoubleIcon,
@@ -594,10 +582,6 @@ function getLoaderCategories(ver) {
gap: var(--spacing-card-sm); gap: var(--spacing-card-sm);
} }
.notification__actions .iconified-button.square-button svg {
margin-right: 0;
}
.unknown-type { .unknown-type {
color: var(--color-red); color: var(--color-red);
} }
@@ -618,4 +602,8 @@ function getLoaderCategories(ver) {
color: var(--color-blue); color: var(--color-blue);
} }
} }
.title-link {
@apply underline hover:brightness-[--hover-brightness];
}
</style> </style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<nav <nav
ref="scrollContainer" ref="scrollContainer"
class="card-shadow experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold" class="card-shadow relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
> >
<button <button
v-for="(option, index) in options" v-for="(option, index) in options"

View File

@@ -67,21 +67,23 @@
</div> </div>
<div class="table-cell"> <div class="table-cell">
<nuxt-link <ButtonStyled circular>
class="btn icon-only" <nuxt-link :to="`/project/${project.slug ? project.slug : project.id}/settings`">
:to="`/project/${project.slug ? project.slug : project.id}/settings`"
>
<SettingsIcon /> <SettingsIcon />
</nuxt-link> </nuxt-link>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
<div class="push-right input-group"> <div class="push-right input-group">
<Button @click="$refs.modalOpen?.hide()"> <ButtonStyled type="outlined">
<button @click="$refs.modalOpen?.hide()">
<XIcon /> <XIcon />
Cancel Cancel
</Button> </button>
<Button :disabled="!selectedProjects?.length" color="primary" @click="onSubmitHandler()"> </ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!selectedProjects?.length" @click="onSubmitHandler()">
<TransferIcon /> <TransferIcon />
<span> <span>
Transfer Transfer
@@ -97,20 +99,23 @@
{{ selectedProjects.length === 1 ? 'project' : 'projects' }} {{ selectedProjects.length === 1 ? 'project' : 'projects' }}
</span> </span>
</span> </span>
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
<Button @click="$refs.modalOpen?.show()"> <ButtonStyled>
<button @click="$refs.modalOpen?.show()">
<TransferIcon /> <TransferIcon />
<span>Transfer projects</span> <span>Transfer projects</span>
</Button> </button>
</ButtonStyled>
</div> </div>
</template> </template>
<script setup> <script setup>
import { BoxIcon, SettingsIcon, TransferIcon, XIcon } from '@modrinth/assets' import { BoxIcon, SettingsIcon, TransferIcon, XIcon } from '@modrinth/assets'
import { Avatar, Button, Checkbox, CopyCode, Modal } from '@modrinth/ui' import { Avatar, ButtonStyled, Checkbox, CopyCode, Modal } from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils' import { formatProjectType } from '@modrinth/utils'
const EDIT_DETAILS = 1 << 2 const EDIT_DETAILS = 1 << 2

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { SettingsIcon } from '@modrinth/assets' import { SettingsIcon } from '@modrinth/assets'
import { defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui' import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
@@ -30,10 +30,12 @@ const messages = defineMessages({
<span>{{ formatMessage(messages.description) }}</span> <span>{{ formatMessage(messages.description) }}</span>
</template> </template>
<template #actions> <template #actions>
<nuxt-link class="btn" to="/settings/billing"> <ButtonStyled>
<nuxt-link to="/settings/billing">
<SettingsIcon aria-hidden="true" /> <SettingsIcon aria-hidden="true" />
{{ formatMessage(messages.action) }} {{ formatMessage(messages.action) }}
</nuxt-link> </nuxt-link>
</ButtonStyled>
</template> </template>
</PagewideBanner> </PagewideBanner>
</template> </template>

View File

@@ -1,6 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { SettingsIcon } from '@modrinth/assets' import { SettingsIcon } from '@modrinth/assets'
import { defineMessages, injectNotificationManager, PagewideBanner, useVIntl } from '@modrinth/ui' import {
ButtonStyled,
defineMessages,
injectNotificationManager,
PagewideBanner,
useVIntl,
} from '@modrinth/ui'
import { FetchError } from 'ofetch' import { FetchError } from 'ofetch'
const { addNotification } = injectNotificationManager() const { addNotification } = injectNotificationManager()
@@ -91,13 +97,17 @@ async function handleResendEmailVerification() {
</span> </span>
</template> </template>
<template #actions> <template #actions>
<button v-if="hasEmail" class="btn" @click="handleResendEmailVerification"> <ButtonStyled v-if="hasEmail">
<button @click="handleResendEmailVerification">
{{ formatMessage(verifyEmailBannerMessages.action) }} {{ formatMessage(verifyEmailBannerMessages.action) }}
</button> </button>
<nuxt-link v-else class="btn" to="/settings/account"> </ButtonStyled>
<ButtonStyled v-else>
<nuxt-link to="/settings/account">
<SettingsIcon aria-hidden="true" /> <SettingsIcon aria-hidden="true" />
{{ formatMessage(addEmailBannerMessages.action) }} {{ formatMessage(addEmailBannerMessages.action) }}
</nuxt-link> </nuxt-link>
</ButtonStyled>
</template> </template>
</PagewideBanner> </PagewideBanner>
</template> </template>

View File

@@ -81,15 +81,21 @@
</span> </span>
</h2> </h2>
<div class="chart-controls__buttons"> <div class="chart-controls__buttons">
<Button v-tooltip="'Toggle project colors'" icon-only @click="onToggleColors"> <ButtonStyled circular type="outlined">
<button v-tooltip="'Toggle project colors'" @click="onToggleColors">
<PaletteIcon /> <PaletteIcon />
</Button> </button>
<Button v-tooltip="'Download this data as CSV'" icon-only @click="onDownloadSetAsCSV"> </ButtonStyled>
<ButtonStyled circular type="outlined">
<button v-tooltip="'Download this data as CSV'" @click="onDownloadSetAsCSV">
<DownloadIcon /> <DownloadIcon />
</Button> </button>
<Button v-tooltip="'Refresh the chart'" icon-only @click="resetCharts"> </ButtonStyled>
<ButtonStyled circular type="outlined">
<button v-tooltip="'Refresh the chart'" @click="resetCharts">
<UpdatedIcon /> <UpdatedIcon />
</Button> </button>
</ButtonStyled>
<DropdownSelect <DropdownSelect
v-model="selectedRange" v-model="selectedRange"
class="range-dropdown" class="range-dropdown"
@@ -156,8 +162,8 @@
<template v-for="project in selectedDataSetProjects" :key="project"> <template v-for="project in selectedDataSetProjects" :key="project">
<button <button
v-tooltip="project.title" v-tooltip="project.title"
:class="`legend__item button-base btn-transparent ${ :class="`legend__item button-base legend__item-transparent ${
!projectIsOnDisplay(project.id) ? 'btn-dimmed' : '' !projectIsOnDisplay(project.id) ? 'legend__item-dimmed' : ''
}`" }`"
@click=" @click="
() => () =>
@@ -311,7 +317,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { DownloadIcon, PaletteIcon, UpdatedIcon } from '@modrinth/assets' import { DownloadIcon, PaletteIcon, UpdatedIcon } from '@modrinth/assets'
import { import {
Button, ButtonStyled,
Card, Card,
DropdownSelect, DropdownSelect,
useCompactNumber, useCompactNumber,
@@ -842,7 +848,7 @@ const defaultRanges: RangeObject[] = [
} }
} }
.btn-transparent { .legend__item-transparent {
background-color: transparent; background-color: transparent;
border: none; border: none;
cursor: pointer; cursor: pointer;
@@ -851,7 +857,7 @@ const defaultRanges: RangeObject[] = [
font-weight: var(--font-weight-regular); font-weight: var(--font-weight-regular);
} }
.btn-dimmed { .legend__item-dimmed {
opacity: 0.5; opacity: 0.5;
} }

View File

@@ -38,13 +38,13 @@
{{ formatMessage(messages.collectionInfo, { count: projectIds.length }) }} {{ formatMessage(messages.collectionInfo, { count: projectIds.length }) }}
</p> </p>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<ButtonStyled class="w-24"> <ButtonStyled type="outlined">
<button @click="modal.hide()"> <button @click="modal.hide()">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
</ButtonStyled> </ButtonStyled>
<ButtonStyled color="brand" class="w-36"> <ButtonStyled color="brand">
<button :disabled="hasHitLimit" @click="create"> <button :disabled="hasHitLimit" @click="create">
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createCollection) }} {{ formatMessage(messages.createCollection) }}

View File

@@ -59,13 +59,13 @@
{{ formatMessage(messages.ownershipInfo) }} {{ formatMessage(messages.ownershipInfo) }}
</p> </p>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<ButtonStyled class="w-24"> <ButtonStyled type="outlined">
<button @click="hide"> <button @click="hide">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
</ButtonStyled> </ButtonStyled>
<ButtonStyled color="brand" class="w-40"> <ButtonStyled color="brand">
<button :disabled="hasHitLimit" @click="createOrganization"> <button :disabled="hasHitLimit" @click="createOrganization">
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createOrganization) }} {{ formatMessage(messages.createOrganization) }}

View File

@@ -110,13 +110,13 @@
<span>{{ formatMessage(messages.summaryDescription) }}</span> <span>{{ formatMessage(messages.summaryDescription) }}</span>
</div> </div>
<div class="flex justify-end gap-2.5"> <div class="flex justify-end gap-2.5">
<ButtonStyled class="w-24"> <ButtonStyled type="outlined">
<button @click="cancel"> <button @click="cancel">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
</ButtonStyled> </ButtonStyled>
<ButtonStyled color="brand" class="w-32"> <ButtonStyled color="brand">
<button v-tooltip="missingFieldsTooltip" :disabled="disableCreate" @click="createProject"> <button v-tooltip="missingFieldsTooltip" :disabled="disableCreate" @click="createProject">
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createProject) }} {{ formatMessage(messages.createProject) }}

View File

@@ -58,24 +58,20 @@
</div> </div>
<template #actions> <template #actions>
<div v-if="currentStage === 'completion'" class="mt-4 flex w-full gap-3"> <div v-if="currentStage === 'completion'" class="mt-4 flex w-full gap-3">
<ButtonStyled class="flex-1"> <ButtonStyled>
<button class="w-full text-contrast" @click="handleClose"> <button class="w-full flex-1 text-contrast" @click="handleClose">
{{ formatMessage(commonMessages.closeButton) }} {{ formatMessage(commonMessages.closeButton) }}
</button> </button>
</ButtonStyled> </ButtonStyled>
<ButtonStyled class="flex-1"> <ButtonStyled>
<button class="w-full text-contrast" @click="handleViewTransactions"> <button class="w-full flex-1 text-contrast" @click="handleViewTransactions">
{{ formatMessage(messages.transactionsButton) }} {{ formatMessage(messages.transactionsButton) }}
</button> </button>
</ButtonStyled> </ButtonStyled>
</div> </div>
<div v-else class="mt-4 flex flex-col justify-end gap-2 sm:flex-row"> <div v-else class="mt-4 flex flex-col justify-end gap-2 sm:flex-row">
<ButtonStyled type="outlined"> <ButtonStyled type="outlined">
<button <button :disabled="leftButtonConfig.disabled" @click="leftButtonConfig.handler">
class="!border-surface-5"
:disabled="leftButtonConfig.disabled"
@click="leftButtonConfig.handler"
>
<component :is="leftButtonConfig.icon" /> <component :is="leftButtonConfig.icon" />
{{ leftButtonConfig.label }} {{ leftButtonConfig.label }}
</button> </button>

View File

@@ -30,9 +30,13 @@
</ButtonStyled> </ButtonStyled>
</div> </div>
</div> </div>
<div v-if="!collapsed" class="grid-display width-16 mt-4"> <div v-if="!collapsed" class="mt-4 grid grid-cols-[repeat(auto-fit,minmax(15rem,1fr))] gap-2">
<div v-for="nag in visibleNags" :key="nag.id" class="grid-display__item"> <div
<span class="flex items-center gap-2 font-semibold"> v-for="nag in visibleNags"
:key="nag.id"
class="flex flex-col gap-3 rounded-2xl border border-solid border-surface-5 bg-surface-2 p-4"
>
<span class="flex items-center gap-2 font-medium text-contrast">
<component <component
:is="nag.icon || getDefaultIcon(nag.status)" :is="nag.icon || getDefaultIcon(nag.status)"
v-tooltip="getStatusTooltip(nag.status)" v-tooltip="getStatusTooltip(nag.status)"
@@ -52,7 +56,7 @@
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/${ :to="`/${project.project_type}/${project.slug ? project.slug : project.id}/${
nag.link.path nag.link.path
}`" }`"
class="goto-link" class="goto-link mt-auto"
> >
{{ getFormattedMessage(nag.link.title) }} {{ getFormattedMessage(nag.link.title) }}
<ChevronRightIcon aria-hidden="true" class="featured-header-chevron" /> <ChevronRightIcon aria-hidden="true" class="featured-header-chevron" />

View File

@@ -154,8 +154,8 @@
@update-thread="updateThread" @update-thread="updateThread"
> >
<template #closedActions> <template #closedActions>
<ButtonStyled v-if="isStaff(auth.user)" color="green" class="mt-2"> <ButtonStyled v-if="isStaff(auth.user)" color="green">
<button class="w-full gap-2 sm:w-auto" @click="reopenReport()"> <button class="mt-2 w-full gap-2 sm:w-auto" @click="reopenReport()">
<CheckCircleIcon class="size-4" /> <CheckCircleIcon class="size-4" />
Reopen Thread Reopen Thread
</button> </button>

View File

@@ -1201,7 +1201,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
:href="file.download_url" :href="file.download_url"
:title="`Download ${file.file_name}`" :title="`Download ${file.file_name}`"
:download="file.file_name" :download="file.file_name"
class="!border-px !border-surface-4"
tabindex="0" tabindex="0"
> >
<DownloadIcon /> Download <DownloadIcon /> Download
@@ -1362,7 +1361,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
" "
> >
<button <button
class="!border-[1px]"
:disabled="updatingDetails.has(flag.detail.id)" :disabled="updatingDetails.has(flag.detail.id)"
@click="updateDetailStatus(flag.detail.id, 'safe')" @click="updateDetailStatus(flag.detail.id, 'safe')"
> >
@@ -1379,7 +1377,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
" "
> >
<button <button
class="!border-[1px]"
:disabled="updatingDetails.has(flag.detail.id)" :disabled="updatingDetails.has(flag.detail.id)"
@click="updateDetailStatus(flag.detail.id, 'unsafe')" @click="updateDetailStatus(flag.detail.id, 'unsafe')"
> >

View File

@@ -40,11 +40,7 @@
</div> </div>
</div> </div>
<ButtonStyled v-if="content" type="outlined"> <ButtonStyled v-if="content" type="outlined">
<button <button :disabled="!hasPermission" @click="handleSwitchCompatibility">
class="!border-[1px]"
:disabled="!hasPermission"
@click="handleSwitchCompatibility"
>
<ArrowLeftRightIcon /> <ArrowLeftRightIcon />
Switch type Switch type
</button> </button>

View File

@@ -21,10 +21,10 @@
</span> </span>
</div> </div>
</div> </div>
<ButtonStyled color="medal-promo" type="outlined" size="large" class="z-10 my-auto mt-2"> <ButtonStyled color="medal-promo" type="outlined" size="large">
<nuxt-link <nuxt-link
to="https://medal.tv/modrinth" to="https://medal.tv/modrinth"
class="z-10 flex w-full items-center justify-center gap-1 md:mt-0 md:w-auto" class="z-10 my-auto mt-2 flex w-full items-center justify-center gap-1 md:mt-0 md:w-auto"
>{{ formatMessage(messages.learnMoreButton) }} <ExternalIcon >{{ formatMessage(messages.learnMoreButton) }} <ExternalIcon
/></nuxt-link> /></nuxt-link>
</ButtonStyled> </ButtonStyled>

View File

@@ -23,14 +23,12 @@
I confirm that I have properly addressed the moderators' comments. I confirm that I have properly addressed the moderators' comments.
</Checkbox> </Checkbox>
<div class="input-group push-right"> <div class="input-group push-right">
<button <ButtonStyled color="orange">
class="iconified-button moderation-button" <button :disabled="!submissionConfirmation" @click="resubmit()">
:disabled="!submissionConfirmation"
@click="resubmit()"
>
<ScaleIcon aria-hidden="true" /> <ScaleIcon aria-hidden="true" />
Resubmit for review Resubmit for review
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -55,14 +53,12 @@
I acknowledge that the moderators do not actively monitor the thread. I acknowledge that the moderators do not actively monitor the thread.
</Checkbox> </Checkbox>
<div class="input-group push-right"> <div class="input-group push-right">
<button <ButtonStyled color="brand">
class="btn btn-primary" <button :disabled="!replyConfirmation" @click="sendReplyFromModal()">
:disabled="!replyConfirmation"
@click="sendReplyFromModal()"
>
<ReplyIcon aria-hidden="true" /> <ReplyIcon aria-hidden="true" />
Reply to thread Reply to thread
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -85,10 +81,12 @@
</div> </div>
<template v-if="report && report.closed"> <template v-if="report && report.closed">
<p>This thread is closed and new messages cannot be sent to it.</p> <p>This thread is closed and new messages cannot be sent to it.</p>
<button v-if="isStaff(auth.user)" class="iconified-button" @click="reopenReport()"> <ButtonStyled v-if="isStaff(auth.user)">
<button @click="reopenReport()">
<CheckCircleIcon aria-hidden="true" /> <CheckCircleIcon aria-hidden="true" />
Reopen thread Reopen thread
</button> </button>
</ButtonStyled>
</template> </template>
<template v-else-if="!report || !report.closed"> <template v-else-if="!report || !report.closed">
<div class="markdown-editor-spacing"> <div class="markdown-editor-spacing">
@@ -99,9 +97,9 @@
/> />
</div> </div>
<div class="input-group"> <div class="input-group">
<ButtonStyled color="brand">
<button <button
v-if="sortedMessages.length > 0" v-if="sortedMessages.length > 0"
class="btn btn-primary"
:disabled="!replyBody" :disabled="!replyBody"
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()" @click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
> >
@@ -110,101 +108,79 @@
</button> </button>
<button <button
v-else v-else
class="btn btn-primary"
:disabled="!replyBody" :disabled="!replyBody"
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()" @click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
> >
<SendIcon aria-hidden="true" /> <SendIcon aria-hidden="true" />
Send Send
</button> </button>
<button </ButtonStyled>
v-if="isStaff(auth.user)" <ButtonStyled v-if="isStaff(auth.user)">
class="btn" <button :disabled="!replyBody" @click="sendReply(null, true)">
:disabled="!replyBody"
@click="sendReply(null, true)"
>
<ScaleIcon aria-hidden="true" /> <ScaleIcon aria-hidden="true" />
Add private note Add private note
</button> </button>
</ButtonStyled>
<template v-if="currentMember && !isStaff(auth.user)"> <template v-if="currentMember && !isStaff(auth.user)">
<template v-if="isRejected(project)"> <template v-if="isRejected(project)">
<button <ButtonStyled color="orange">
v-if="replyBody" <button v-if="replyBody" @click="openResubmitModal(true)">
class="iconified-button moderation-button"
@click="openResubmitModal(true)"
>
<ScaleIcon aria-hidden="true" /> <ScaleIcon aria-hidden="true" />
Resubmit for review with reply Resubmit for review with reply
</button> </button>
<button <button v-else @click="openResubmitModal(false)">
v-else
class="iconified-button moderation-button"
@click="openResubmitModal(false)"
>
<ScaleIcon aria-hidden="true" /> <ScaleIcon aria-hidden="true" />
Resubmit for review Resubmit for review
</button> </button>
</ButtonStyled>
</template> </template>
</template> </template>
<div class="spacer"></div> <div class="spacer"></div>
<div class="input-group extra-options"> <div class="input-group extra-options">
<template v-if="report"> <template v-if="report">
<template v-if="isStaff(auth.user)"> <template v-if="isStaff(auth.user)">
<button <ButtonStyled color="red">
v-if="replyBody" <button v-if="replyBody" @click="closeReport(true)">
class="iconified-button danger-button"
@click="closeReport(true)"
>
<CheckCircleIcon aria-hidden="true" /> <CheckCircleIcon aria-hidden="true" />
Close with reply Close with reply
</button> </button>
<button v-else class="iconified-button danger-button" @click="closeReport()"> <button v-else @click="closeReport()">
<CheckCircleIcon aria-hidden="true" /> <CheckCircleIcon aria-hidden="true" />
Close thread Close thread
</button> </button>
</ButtonStyled>
</template> </template>
</template> </template>
<template v-if="project"> <template v-if="project">
<template v-if="isStaff(auth.user)"> <template v-if="isStaff(auth.user)">
<button <ButtonStyled v-if="replyBody" color="green">
v-if="replyBody" <button :disabled="isApproved(project)" @click="sendReply(requestedStatus)">
class="btn btn-green"
:disabled="isApproved(project)"
@click="sendReply(requestedStatus)"
>
<CheckIcon aria-hidden="true" /> <CheckIcon aria-hidden="true" />
Approve with reply Approve with reply
</button> </button>
<button </ButtonStyled>
v-else <ButtonStyled v-else color="green">
class="btn btn-green" <button :disabled="isApproved(project)" @click="setStatus(requestedStatus)">
:disabled="isApproved(project)"
@click="setStatus(requestedStatus)"
>
<CheckIcon aria-hidden="true" /> <CheckIcon aria-hidden="true" />
Approve Approve
</button> </button>
</ButtonStyled>
<div class="joined-buttons"> <div class="joined-buttons">
<button <ButtonStyled v-if="replyBody" color="red">
v-if="replyBody" <button :disabled="project.status === 'rejected'" @click="sendReply('rejected')">
class="btn btn-danger"
:disabled="project.status === 'rejected'"
@click="sendReply('rejected')"
>
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
Reject with reply Reject with reply
</button> </button>
<button </ButtonStyled>
v-else <ButtonStyled v-else color="red">
class="btn btn-danger" <button :disabled="project.status === 'rejected'" @click="setStatus('rejected')">
:disabled="project.status === 'rejected'"
@click="setStatus('rejected')"
>
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
Reject Reject
</button> </button>
</ButtonStyled>
<ButtonStyled color="red">
<OverflowMenu <OverflowMenu
class="btn btn-danger btn-dropdown-animation icon-only" class="btn-dropdown-animation"
:options=" :options="
replyBody replyBody
? [ ? [
@@ -263,7 +239,7 @@
] ]
" "
> >
<DropdownIcon style="rotate: 180deg" aria-hidden="true" /> <DropdownIcon aria-hidden="true" />
<template #withhold-reply> <template #withhold-reply>
<EyeOffIcon aria-hidden="true" /> <EyeOffIcon aria-hidden="true" />
Withhold with reply Withhold with reply
@@ -289,6 +265,7 @@
Send to review Send to review
</template> </template>
</OverflowMenu> </OverflowMenu>
</ButtonStyled>
</div> </div>
</template> </template>
</template> </template>
@@ -311,6 +288,7 @@ import {
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
ButtonStyled,
Checkbox, Checkbox,
CopyCode, CopyCode,
injectNotificationManager, injectNotificationManager,

View File

@@ -104,8 +104,9 @@
</span> </span>
</span> </span>
<div v-if="isStaff(auth.user) && message.author_id === auth.user.id" class="message__actions"> <div v-if="isStaff(auth.user) && message.author_id === auth.user.id" class="message__actions">
<ButtonStyled circular type="transparent">
<OverflowMenu <OverflowMenu
class="btn btn-transparent icon-only" class="btn-dropdown-animation"
:options="[ :options="[
{ {
id: 'delete', id: 'delete',
@@ -118,6 +119,7 @@
<MoreHorizontalIcon /> <MoreHorizontalIcon />
<template #delete> <TrashIcon /> Delete </template> <template #delete> <TrashIcon /> Delete </template>
</OverflowMenu> </OverflowMenu>
</ButtonStyled>
</div> </div>
</div> </div>
</template> </template>
@@ -135,6 +137,7 @@ import {
AutoLink, AutoLink,
Avatar, Avatar,
Badge, Badge,
ButtonStyled,
OverflowMenu, OverflowMenu,
useFormatDateTime, useFormatDateTime,
useRelativeTime, useRelativeTime,

View File

@@ -2,7 +2,7 @@
<NuxtLayout> <NuxtLayout>
<LoadingBar /> <LoadingBar />
<NotificationPanel /> <NotificationPanel />
<div class="main experimental-styles-within"> <div class="main">
<div v-if="is404" class="error-graphic"> <div v-if="is404" class="error-graphic">
<Logo404 /> <Logo404 />
</div> </div>

View File

@@ -54,7 +54,7 @@
:api-url="config.public.apiBaseUrl" :api-url="config.public.apiBaseUrl"
/> />
<header <header
class="experimental-styles-within desktop-only relative z-[5] mx-auto grid max-w-[1280px] grid-cols-[1fr_auto] items-center gap-2 px-6 py-4 lg:grid-cols-[auto_1fr_auto]" class="desktop-only relative z-[5] mx-auto grid max-w-[1280px] grid-cols-[1fr_auto] items-center gap-2 px-6 py-4 lg:grid-cols-[auto_1fr_auto]"
> >
<div> <div>
<NuxtLink <NuxtLink
@@ -1298,7 +1298,8 @@ const { cycle: changeTheme } = useTheme()
justify-content: center; justify-content: center;
padding: 1rem; padding: 1rem;
.iconified-button { > button,
> a {
width: 100%; width: 100%;
max-width: 500px; max-width: 500px;
padding: 0.75rem; padding: 0.75rem;

View File

@@ -45,15 +45,7 @@
</div> </div>
</div> </div>
<div v-else class="experimental-styles-within"> <div v-else>
<NewModal ref="settingsModal">
<template #title>
<Avatar :src="project.icon_url" :alt="project.title" class="icon" size="32px" />
<span class="text-lg font-extrabold text-contrast">
{{ formatMessage(messages.settingsTitle) }}
</span>
</template>
</NewModal>
<NewModal <NewModal
ref="modalLicense" ref="modalLicense"
:header="project.license.name ? project.license.name : formatMessage(messages.licenseTitle)" :header="project.license.name ? project.license.name : formatMessage(messages.licenseTitle)"
@@ -464,30 +456,19 @@
:member="!!currentMember" :member="!!currentMember"
> >
<template #actions> <template #actions>
<ButtonStyled <ButtonStyled v-if="auth.user && currentMember" size="large" color="brand" circular>
v-if="auth.user && currentMember"
size="large"
color="brand"
class="lg:!hidden"
circular
>
<nuxt-link <nuxt-link
v-tooltip="'Edit project'" v-tooltip="'Edit project'"
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`" :to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
class="!font-bold" class="!font-bold lg:!hidden"
> >
<SettingsIcon aria-hidden="true" /> <SettingsIcon aria-hidden="true" />
</nuxt-link> </nuxt-link>
</ButtonStyled> </ButtonStyled>
<ButtonStyled <ButtonStyled v-if="auth.user && currentMember" size="large" color="brand">
v-if="auth.user && currentMember"
size="large"
color="brand"
class="max-lg:!hidden"
>
<nuxt-link <nuxt-link
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`" :to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
class="!font-bold" class="!font-bold max-lg:!hidden"
> >
<SettingsIcon aria-hidden="true" /> <SettingsIcon aria-hidden="true" />
Edit project Edit project
@@ -594,7 +575,7 @@
</nuxt-link> </nuxt-link>
</ButtonStyled> </ButtonStyled>
<template #popper> <template #popper>
<div class="experimental-styles-within grid grid-cols-[min-content] gap-1"> <div class="grid grid-cols-[min-content] gap-1">
<div class="flex min-w-60 items-center justify-between gap-4"> <div class="flex min-w-60 items-center justify-between gap-4">
<h3 <h3
class="m-0 flex items-center gap-2 whitespace-nowrap text-base font-bold text-contrast" class="m-0 flex items-center gap-2 whitespace-nowrap text-base font-bold text-contrast"
@@ -724,13 +705,15 @@
<div v-else class="menu-text"> <div v-else class="menu-text">
<p class="popout-text">{{ formatMessage(messages.noCollectionsFound) }}</p> <p class="popout-text">{{ formatMessage(messages.noCollectionsFound) }}</p>
</div> </div>
<ButtonStyled>
<button <button
class="btn collection-button" class="mx-3 mb-3"
@click="(event) => $refs.modal_collection.show(event)" @click="(event) => $refs.modal_collection.show(event)"
> >
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createNewCollection) }} {{ formatMessage(messages.createNewCollection) }}
</button> </button>
</ButtonStyled>
</template> </template>
</PopoutMenu> </PopoutMenu>
<nuxt-link v-else v-tooltip="'Save'" :to="signInRouteObj" aria-label="Save"> <nuxt-link v-else v-tooltip="'Save'" :to="signInRouteObj" aria-label="Save">
@@ -890,32 +873,29 @@
:supported-versions="serverSupportedVersions" :supported-versions="serverSupportedVersions"
:loaders="serverModpackLoaders" :loaders="serverModpackLoaders"
:status-online="projectV3?.minecraft_java_server?.ping?.data != null" :status-online="projectV3?.minecraft_java_server?.ping?.data != null"
class="card flex-card experimental-styles-within" class="card flex-card"
/> />
<ProjectSidebarCompatibility <ProjectSidebarCompatibility
v-if="projectV3Loaded && !isServerProject" v-if="projectV3Loaded && !isServerProject"
:project="project" :project="project"
:tags="tags" :tags="tags"
:project-v3="projectV3" :project-v3="projectV3"
class="card flex-card experimental-styles-within" class="card flex-card"
/> />
<AdPlaceholder v-if="!auth.user && tags.approvedStatuses.includes(project.status)" /> <AdPlaceholder v-if="!auth.user && tags.approvedStatuses.includes(project.status)" />
<ProjectSidebarLinks <ProjectSidebarLinks
:project="project" :project="project"
:project-v3="projectV3" :project-v3="projectV3"
:link-target="$external()" :link-target="$external()"
class="card flex-card experimental-styles-within" class="card flex-card"
/>
<ProjectSidebarTags
:project="project"
class="card flex-card experimental-styles-within"
/> />
<ProjectSidebarTags :project="project" class="card flex-card" />
<ProjectSidebarCreators <ProjectSidebarCreators
:organization="organization" :organization="organization"
:members="members" :members="members"
:org-link="(slug) => `/organization/${slug}`" :org-link="(slug) => `/organization/${slug}`"
:user-link="(username) => `/user/${username}`" :user-link="(username) => `/user/${username}`"
class="card flex-card experimental-styles-within" class="card flex-card"
/> />
<!-- TODO: Finish license modal and enable --> <!-- TODO: Finish license modal and enable -->
<ProjectSidebarDetails <ProjectSidebarDetails
@@ -924,9 +904,9 @@
:has-versions="versions.length > 0" :has-versions="versions.length > 0"
:link-target="$external()" :link-target="$external()"
:show-followers="isServerProject" :show-followers="isServerProject"
class="card flex-card experimental-styles-within" class="card flex-card"
/> />
<div class="card flex-card experimental-styles-within"> <div class="card flex-card">
<h2>{{ formatMessage(detailsMessages.title) }}</h2> <h2>{{ formatMessage(detailsMessages.title) }}</h2>
<div class="details-list"> <div class="details-list">
@@ -1177,7 +1157,6 @@ const formatDateTime = useFormatDateTime({
const debug = useDebugLogger('DownloadModal') const debug = useDebugLogger('DownloadModal')
const settingsModal = ref()
const downloadModal = ref() const downloadModal = ref()
const openInAppModal = ref() const openInAppModal = ref()
const overTheTopDownloadAnimation = ref() const overTheTopDownloadAnimation = ref()
@@ -2748,11 +2727,6 @@ provideProjectPageContext({
color: var(--color-secondary); color: var(--color-secondary);
} }
.collection-button {
margin: var(--gap-sm) var(--gap-md);
white-space: nowrap;
}
.menu-text { .menu-text {
padding: 0 var(--gap-md); padding: 0 var(--gap-md);
font-size: var(--font-size-nm); font-size: var(--font-size-nm);

View File

@@ -53,14 +53,16 @@
{{ formatDate(version.date_published) }}</span {{ formatDate(version.date_published) }}</span
> >
</div> </div>
<ButtonStyled color="brand" type="transparent">
<a <a
class="ml-auto"
:href="version.primaryFile?.url" :href="version.primaryFile?.url"
class="iconified-button download"
:title="`Download ${version.name}`" :title="`Download ${version.name}`"
> >
<DownloadIcon aria-hidden="true" /> <DownloadIcon aria-hidden="true" />
Download Download
</a> </a>
</ButtonStyled>
</div> </div>
<div <div
v-if="version.changelog && !version.duplicate" v-if="version.changelog && !version.duplicate"
@@ -87,6 +89,7 @@
<script setup> <script setup>
import { DownloadIcon, SpinnerIcon } from '@modrinth/assets' import { DownloadIcon, SpinnerIcon } from '@modrinth/assets'
import { import {
ButtonStyled,
injectModrinthClient, injectModrinthClient,
injectProjectPageContext, injectProjectPageContext,
Pagination, Pagination,

View File

@@ -10,9 +10,9 @@
<div class="file-header"> <div class="file-header">
<ImageIcon aria-hidden="true" /> <ImageIcon aria-hidden="true" />
<strong>{{ editFile ? editFile.name : 'Current image' }}</strong> <strong>{{ editFile ? editFile.name : 'Current image' }}</strong>
<ButtonStyled v-if="editIndex === -1" type="outlined">
<FileInput <FileInput
v-if="editIndex === -1" class="button-like"
class="iconified-button raised-button"
prompt="Replace" prompt="Replace"
:accept="acceptFileTypes" :accept="acceptFileTypes"
:max-size="5242880" :max-size="5242880"
@@ -27,6 +27,7 @@
> >
<TransferIcon aria-hidden="true" /> <TransferIcon aria-hidden="true" />
</FileInput> </FileInput>
</ButtonStyled>
</div> </div>
<img <img
:src=" :src="
@@ -68,53 +69,42 @@
placeholder="Enter order index..." placeholder="Enter order index..."
/> />
<label for="gallery-image-featured"> <label for="gallery-image-featured">
<span class="label__title">Featured</span> <span class="label__title">Banner image</span>
<span class="label__description"> <span class="label__description">
A featured gallery image shows up in search and your project card. Only one gallery You can feature one image on your project to be used as a banner image.
image can be featured.
</span> </span>
</label> </label>
<button <ButtonStyled v-if="!editFeatured">
v-if="!editFeatured" <button id="gallery-image-featured" class="w-fit" @click="editFeatured = true">
id="gallery-image-featured"
class="iconified-button"
@click="editFeatured = true"
>
<StarIcon aria-hidden="true" /> <StarIcon aria-hidden="true" />
Feature image Set as banner
</button> </button>
<button </ButtonStyled>
v-else <ButtonStyled v-else>
id="gallery-image-featured" <button id="gallery-image-featured" class="w-fit" @click="editFeatured = false">
class="iconified-button"
@click="editFeatured = false"
>
<StarIcon fill="currentColor" aria-hidden="true" /> <StarIcon fill="currentColor" aria-hidden="true" />
Unfeature image Unset as banner
</button> </button>
</ButtonStyled>
<div class="button-group"> <div class="button-group">
<button class="iconified-button" @click="modalEditItem?.hide()"> <ButtonStyled type="outlined">
<button @click="modalEditItem?.hide()">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
Cancel Cancel
</button> </button>
<button </ButtonStyled>
v-if="editIndex === -1" <ButtonStyled v-if="editIndex === -1" color="brand">
class="iconified-button brand-button" <button :disabled="shouldPreventActions" @click="createGalleryItem">
:disabled="shouldPreventActions"
@click="createGalleryItem"
>
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
Add gallery image Add gallery image
</button> </button>
<button </ButtonStyled>
v-else <ButtonStyled v-else color="brand">
class="iconified-button brand-button" <button :disabled="shouldPreventActions" @click="editGalleryItem">
:disabled="shouldPreventActions"
@click="editGalleryItem"
>
<SaveIcon aria-hidden="true" /> <SaveIcon aria-hidden="true" />
Save changes Save changes
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -155,12 +145,15 @@
</p> </p>
</div> </div>
<div class="controls"> <div class="controls">
<div class="buttons"> <div class="flex gap-2">
<button class="close circle-button" @click="expandedGalleryItem = null"> <ButtonStyled circular>
<button class="close" @click="expandedGalleryItem = null">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
</button> </button>
</ButtonStyled>
<ButtonStyled circular>
<a <a
class="open circle-button" class="open"
target="_blank" target="_blank"
:href=" :href="
expandedGalleryItem?.raw_url expandedGalleryItem?.raw_url
@@ -170,24 +163,23 @@
> >
<ExternalIcon aria-hidden="true" /> <ExternalIcon aria-hidden="true" />
</a> </a>
<button class="circle-button" @click="zoomedIn = !zoomedIn"> </ButtonStyled>
<ButtonStyled circular>
<button @click="zoomedIn = !zoomedIn">
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" /> <ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" /> <ContractIcon v-else aria-hidden="true" />
</button> </button>
<button </ButtonStyled>
v-if="filteredGallery.length > 1" <ButtonStyled v-if="filteredGallery.length > 1" circular>
class="previous circle-button" <button class="previous" @click="previousImage()">
@click="previousImage()"
>
<LeftArrowIcon aria-hidden="true" /> <LeftArrowIcon aria-hidden="true" />
</button> </button>
<button </ButtonStyled>
v-if="filteredGallery.length > 1" <ButtonStyled v-if="filteredGallery.length > 1" circular>
class="next circle-button" <button class="next" @click="nextImage()">
@click="nextImage()"
>
<RightArrowIcon aria-hidden="true" /> <RightArrowIcon aria-hidden="true" />
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -195,17 +187,19 @@
</div> </div>
<div v-if="currentMember && filteredGallery.length" class="card header-buttons"> <div v-if="currentMember && filteredGallery.length" class="card header-buttons">
<ButtonStyled color="brand">
<FileInput <FileInput
:max-size="5242880" :max-size="5242880"
:accept="acceptFileTypes" :accept="acceptFileTypes"
prompt="Upload an image" prompt="Upload an image"
aria-label="Upload an image" aria-label="Upload an image"
class="iconified-button brand-button" class="button-like"
:disabled="!isPermission(currentMember?.permissions, 1 << 2)" :disabled="!isPermission(currentMember?.permissions, 1 << 2)"
@change="handleFiles" @change="handleFiles"
> >
<UploadIcon aria-hidden="true" /> <UploadIcon aria-hidden="true" />
</FileInput> </FileInput>
</ButtonStyled>
<span class="indicator"> <span class="indicator">
<InfoIcon aria-hidden="true" /> Click to choose an image or drag one onto this page <InfoIcon aria-hidden="true" /> Click to choose an image or drag one onto this page
</span> </span>
@@ -239,8 +233,8 @@
{{ formatDate(item.created) }} {{ formatDate(item.created) }}
</div> </div>
<div v-if="currentMember" class="gallery-buttons input-group"> <div v-if="currentMember" class="gallery-buttons input-group">
<ButtonStyled>
<button <button
class="iconified-button"
@click=" @click="
() => { () => {
resetEdit() resetEdit()
@@ -256,8 +250,9 @@
<EditIcon aria-hidden="true" /> <EditIcon aria-hidden="true" />
Edit Edit
</button> </button>
</ButtonStyled>
<ButtonStyled>
<button <button
class="iconified-button"
@click=" @click="
() => { () => {
deleteIndex = index deleteIndex = index
@@ -268,6 +263,7 @@
<TrashIcon aria-hidden="true" /> <TrashIcon aria-hidden="true" />
Remove Remove
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -304,6 +300,7 @@ import {
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
ButtonStyled,
ConfirmModal, ConfirmModal,
DropArea, DropArea,
FileInput, FileInput,
@@ -541,43 +538,6 @@ async function deleteGalleryImage() {
width: calc(100vw - 2 * var(--spacing-card-lg)); width: calc(100vw - 2 * var(--spacing-card-lg));
height: calc(100vh - 2 * var(--spacing-card-lg)); height: calc(100vh - 2 * var(--spacing-card-lg));
.circle-button {
padding: 0.5rem;
line-height: 1;
display: flex;
max-width: 2rem;
color: var(--color-button-text);
background-color: var(--color-button-bg);
border-radius: var(--size-rounded-max);
margin: 0;
box-shadow: inset 0px -1px 1px rgb(17 24 39 / 10%);
&:not(:last-child) {
margin-right: 0.5rem;
}
&:hover {
background-color: var(--color-button-bg-hover) !important;
svg {
color: var(--color-button-text-hover) !important;
}
}
&:active {
background-color: var(--color-button-bg-active) !important;
svg {
color: var(--color-button-text-active) !important;
}
}
svg {
height: 1rem;
width: 1rem;
}
}
.image { .image {
position: absolute; position: absolute;
left: 50%; left: 50%;
@@ -653,14 +613,6 @@ async function deleteGalleryImage() {
} }
} }
.buttons {
display: flex;
button {
margin-right: 0.5rem;
}
}
.items { .items {
display: grid; display: grid;
grid-template-rows: 1fr; grid-template-rows: 1fr;
@@ -762,10 +714,6 @@ async function deleteGalleryImage() {
strong { strong {
word-wrap: anywhere; word-wrap: anywhere;
} }
.iconified-button {
margin-left: auto;
}
} }
img { img {
@@ -778,8 +726,4 @@ async function deleteGalleryImage() {
} }
} }
} }
.brand-button {
color: var(--color-accent-contrast);
}
</style> </style>

View File

@@ -152,7 +152,7 @@ watch(route, () => {
@toggle-collapsed="() => (collapsedChecklist = !collapsedChecklist)" @toggle-collapsed="() => (collapsedChecklist = !collapsedChecklist)"
@set-processing="setProcessing" @set-processing="setProcessing"
/> />
<div class="experimental-styles-within grid gap-4 lg:grid-cols-[1fr_3fr]"> <div class="grid gap-4 lg:grid-cols-[1fr_3fr]">
<div> <div>
<NavStack :items="navItems" /> <NavStack :items="navItems" />
</div> </div>

View File

@@ -1,6 +1,6 @@
<template> <template>
<LoadingIndicator v-if="!projectV3" class="py-6" /> <LoadingIndicator v-if="!projectV3" class="py-6" />
<div v-else-if="showEnvironmentMigration" class="card experimental-styles-within"> <div v-else-if="showEnvironmentMigration" class="card">
<h2 class="m-0 mb-2 block text-lg font-extrabold text-contrast">Project environment</h2> <h2 class="m-0 mb-2 block text-lg font-extrabold text-contrast">Project environment</h2>
<EnvironmentMigration /> <EnvironmentMigration />
</div> </div>

View File

@@ -10,9 +10,9 @@
<div class="file-header"> <div class="file-header">
<ImageIcon aria-hidden="true" /> <ImageIcon aria-hidden="true" />
<strong>{{ editFile ? editFile.name : 'Current image' }}</strong> <strong>{{ editFile ? editFile.name : 'Current image' }}</strong>
<ButtonStyled v-if="editIndex === -1" type="outlined">
<FileInput <FileInput
v-if="editIndex === -1" class="button-like"
class="iconified-button raised-button"
prompt="Replace" prompt="Replace"
:accept="acceptFileTypes" :accept="acceptFileTypes"
:max-size="5242880" :max-size="5242880"
@@ -27,6 +27,7 @@
> >
<TransferIcon aria-hidden="true" /> <TransferIcon aria-hidden="true" />
</FileInput> </FileInput>
</ButtonStyled>
</div> </div>
<img <img
:src=" :src="
@@ -68,53 +69,42 @@
placeholder="Enter order index..." placeholder="Enter order index..."
/> />
<label for="gallery-image-featured"> <label for="gallery-image-featured">
<span class="label__title">Featured</span> <span class="label__title">Banner image</span>
<span class="label__description"> <span class="label__description">
A featured gallery image shows up in search and your project card. Only one gallery You can feature one image on your project to be used as a banner image.
image can be featured.
</span> </span>
</label> </label>
<button <ButtonStyled v-if="!editFeatured">
v-if="!editFeatured" <button id="gallery-image-featured" class="w-fit" @click="editFeatured = true">
id="gallery-image-featured"
class="iconified-button"
@click="editFeatured = true"
>
<StarIcon aria-hidden="true" /> <StarIcon aria-hidden="true" />
Feature image Set as banner
</button> </button>
<button </ButtonStyled>
v-else <ButtonStyled v-else>
id="gallery-image-featured" <button id="gallery-image-featured" class="w-fit" @click="editFeatured = false">
class="iconified-button"
@click="editFeatured = false"
>
<StarIcon fill="currentColor" aria-hidden="true" /> <StarIcon fill="currentColor" aria-hidden="true" />
Unfeature image Unset as banner
</button> </button>
</ButtonStyled>
<div class="button-group"> <div class="button-group">
<button class="iconified-button" @click="modal_edit_item.hide()"> <ButtonStyled type="outlined">
<button @click="modal_edit_item.hide()">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
Cancel Cancel
</button> </button>
<button </ButtonStyled>
v-if="editIndex === -1" <ButtonStyled v-if="editIndex === -1" color="brand">
class="iconified-button brand-button" <button :disabled="shouldPreventActions" @click="createGalleryItem">
:disabled="shouldPreventActions"
@click="createGalleryItem"
>
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
Add gallery image Add gallery image
</button> </button>
<button </ButtonStyled>
v-else <ButtonStyled v-else color="brand">
class="iconified-button brand-button" <button :disabled="shouldPreventActions" @click="editGalleryItem">
:disabled="shouldPreventActions"
@click="editGalleryItem"
>
<SaveIcon aria-hidden="true" /> <SaveIcon aria-hidden="true" />
Save changes Save changes
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -155,12 +145,15 @@
</p> </p>
</div> </div>
<div class="controls"> <div class="controls">
<div class="buttons"> <div class="flex gap-2">
<button class="close circle-button" @click="expandedGalleryItem = null"> <ButtonStyled circular>
<button class="close" @click="expandedGalleryItem = null">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
</button> </button>
</ButtonStyled>
<ButtonStyled circular>
<a <a
class="open circle-button" class="open"
target="_blank" target="_blank"
:href=" :href="
expandedGalleryItem.raw_url expandedGalleryItem.raw_url
@@ -170,41 +163,42 @@
> >
<ExternalIcon aria-hidden="true" /> <ExternalIcon aria-hidden="true" />
</a> </a>
<button class="circle-button" @click="zoomedIn = !zoomedIn"> </ButtonStyled>
<ButtonStyled circular>
<button @click="zoomedIn = !zoomedIn">
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" /> <ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" /> <ContractIcon v-else aria-hidden="true" />
</button> </button>
<button </ButtonStyled>
v-if="filteredGallery.length > 1" <ButtonStyled v-if="filteredGallery.length > 1" circular>
class="previous circle-button" <button class="previous" @click="previousImage()">
@click="previousImage()"
>
<LeftArrowIcon aria-hidden="true" /> <LeftArrowIcon aria-hidden="true" />
</button> </button>
<button </ButtonStyled>
v-if="filteredGallery.length > 1" <ButtonStyled v-if="filteredGallery.length > 1" circular>
class="next circle-button" <button class="next" @click="nextImage()">
@click="nextImage()"
>
<RightArrowIcon aria-hidden="true" /> <RightArrowIcon aria-hidden="true" />
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-if="currentMember" class="card header-buttons"> <div v-if="currentMember" class="card header-buttons">
<ButtonStyled color="brand">
<FileInput <FileInput
:max-size="5242880" :max-size="5242880"
:accept="acceptFileTypes" :accept="acceptFileTypes"
prompt="Upload an image" prompt="Upload an image"
aria-label="Upload an image" aria-label="Upload an image"
class="iconified-button brand-button" class="button-like"
:disabled="!isPermission(currentMember?.permissions, 1 << 2)" :disabled="!isPermission(currentMember?.permissions, 1 << 2)"
@change="handleFiles" @change="handleFiles"
> >
<UploadIcon aria-hidden="true" /> <UploadIcon aria-hidden="true" />
</FileInput> </FileInput>
</ButtonStyled>
<span class="indicator"> <span class="indicator">
<InfoIcon aria-hidden="true" /> Click to choose an image or drag one onto this page <InfoIcon aria-hidden="true" /> Click to choose an image or drag one onto this page
</span> </span>
@@ -238,8 +232,8 @@
{{ formatDate(item.created) }} {{ formatDate(item.created) }}
</div> </div>
<div v-if="currentMember" class="gallery-buttons input-group"> <div v-if="currentMember" class="gallery-buttons input-group">
<ButtonStyled>
<button <button
class="iconified-button"
@click=" @click="
() => { () => {
resetEdit() resetEdit()
@@ -255,8 +249,9 @@
<EditIcon aria-hidden="true" /> <EditIcon aria-hidden="true" />
Edit Edit
</button> </button>
</ButtonStyled>
<ButtonStyled>
<button <button
class="iconified-button"
@click=" @click="
() => { () => {
deleteIndex = index deleteIndex = index
@@ -267,6 +262,7 @@
<TrashIcon aria-hidden="true" /> <TrashIcon aria-hidden="true" />
Remove Remove
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -294,6 +290,7 @@ import {
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
ButtonStyled,
ConfirmModal, ConfirmModal,
DropArea, DropArea,
FileInput, FileInput,
@@ -501,43 +498,6 @@ onUnmounted(() => {
width: calc(100vw - 2 * var(--spacing-card-lg)); width: calc(100vw - 2 * var(--spacing-card-lg));
height: calc(100vh - 2 * var(--spacing-card-lg)); height: calc(100vh - 2 * var(--spacing-card-lg));
.circle-button {
padding: 0.5rem;
line-height: 1;
display: flex;
max-width: 2rem;
color: var(--color-button-text);
background-color: var(--color-button-bg);
border-radius: var(--size-rounded-max);
margin: 0;
box-shadow: inset 0px -1px 1px rgb(17 24 39 / 10%);
&:not(:last-child) {
margin-right: 0.5rem;
}
&:hover {
background-color: var(--color-button-bg-hover) !important;
svg {
color: var(--color-button-text-hover) !important;
}
}
&:active {
background-color: var(--color-button-bg-active) !important;
svg {
color: var(--color-button-text-active) !important;
}
}
svg {
height: 1rem;
width: 1rem;
}
}
.image { .image {
position: absolute; position: absolute;
left: 50%; left: 50%;
@@ -613,14 +573,6 @@ onUnmounted(() => {
} }
} }
.buttons {
display: flex;
button {
margin-right: 0.5rem;
}
}
.items { .items {
display: grid; display: grid;
grid-template-rows: 1fr; grid-template-rows: 1fr;
@@ -723,7 +675,7 @@ onUnmounted(() => {
word-wrap: anywhere; word-wrap: anywhere;
} }
.iconified-button { label.button-like {
margin-left: auto; margin-left: auto;
} }
} }
@@ -738,8 +690,4 @@ onUnmounted(() => {
} }
} }
} }
.brand-button {
color: var(--color-accent-contrast);
}
</style> </style>

View File

@@ -81,13 +81,14 @@
size="md" size="md"
class="project__icon" class="project__icon"
/> />
<div class="input-stack"> <div class="flex flex-col gap-2">
<ButtonStyled>
<FileInput <FileInput
id="project-icon" id="project-icon"
:max-size="262144000" :max-size="262144000"
:show-icon="true" :show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp" accept="image/png,image/jpeg,image/gif,image/webp"
class="choose-image iconified-button" class="button-like choose-image"
prompt="Upload icon" prompt="Upload icon"
aria-label="Upload icon" aria-label="Upload icon"
:disabled="!hasPermission" :disabled="!hasPermission"
@@ -95,15 +96,13 @@
> >
<UploadIcon aria-hidden="true" /> <UploadIcon aria-hidden="true" />
</FileInput> </FileInput>
<button </ButtonStyled>
v-if="!deletedIcon && (previewImage || project.icon_url)" <ButtonStyled v-if="!deletedIcon && (previewImage || project.icon_url)">
class="iconified-button" <button :disabled="!hasPermission" @click="markIconForDeletion">
:disabled="!hasPermission"
@click="markIconForDeletion"
>
<TrashIcon aria-hidden="true" /> <TrashIcon aria-hidden="true" />
Remove icon Remove icon
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -157,26 +156,25 @@
</label> </label>
</div> </div>
<div class="mt-2 flex items-center gap-2"> <div class="mt-2 flex items-center gap-2">
<ButtonStyled>
<FileInput <FileInput
:max-size="524288" :max-size="524288"
:show-icon="true" :show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp" accept="image/png,image/jpeg,image/gif,image/webp"
class="iconified-button" class="button-like"
prompt="Upload banner" prompt="Upload banner"
:disabled="!hasPermission" :disabled="!hasPermission"
@change="showBannerPreview" @change="showBannerPreview"
> >
<UploadIcon aria-hidden="true" /> <UploadIcon aria-hidden="true" />
</FileInput> </FileInput>
<button </ButtonStyled>
v-if="!deletedBanner && (bannerPreview || bannerGalleryImage?.url)" <ButtonStyled v-if="!deletedBanner && (bannerPreview || bannerGalleryImage?.url)">
class="iconified-button" <button :disabled="!hasPermission" @click="markBannerForDeletion">
:disabled="!hasPermission"
@click="markBannerForDeletion"
>
<TrashIcon aria-hidden="true" /> <TrashIcon aria-hidden="true" />
Remove banner Remove banner
</button> </button>
</ButtonStyled>
</div> </div>
<div class="mt-2 text-secondary">Gif, 468×60px recommended.</div> <div class="mt-2 text-secondary">Gif, 468×60px recommended.</div>
</div> </div>
@@ -285,15 +283,12 @@
Removes your project from Modrinth's servers and search. Clicking on this will delete your Removes your project from Modrinth's servers and search. Clicking on this will delete your
project, so be extra careful! project, so be extra careful!
</p> </p>
<button <ButtonStyled color="red">
type="button" <button :disabled="!hasDeletePermission" @click="$refs.modal_confirm.show()">
class="iconified-button danger-button"
:disabled="!hasDeletePermission"
@click="$refs.modal_confirm.show()"
>
<TrashIcon aria-hidden="true" /> <TrashIcon aria-hidden="true" />
Delete project Delete project
</button> </button>
</ButtonStyled>
</section> </section>
<UnsavedChangesPopup <UnsavedChangesPopup
:original="original" :original="original"
@@ -319,6 +314,7 @@ import {
import { MIN_SUMMARY_CHARS } from '@modrinth/moderation' import { MIN_SUMMARY_CHARS } from '@modrinth/moderation'
import { import {
Avatar, Avatar,
ButtonStyled,
Combobox, Combobox,
ConfirmLeaveModal, ConfirmLeaveModal,
ConfirmModal, ConfirmModal,

View File

@@ -104,7 +104,7 @@
> >
</label> </label>
<div class="input-stack w-1/2"> <div class="flex w-1/2 flex-col gap-2">
<StyledInput <StyledInput
v-if="!current.nonSpdxLicense" v-if="!current.nonSpdxLicense"
id="license-spdx" id="license-spdx"

View File

@@ -105,15 +105,12 @@
/> />
</div> </div>
<div class="button-group"> <div class="button-group">
<button <ButtonStyled color="brand">
type="button" <button :disabled="!hasServerChanges" @click="saveServerChanges()">
class="iconified-button brand-button"
:disabled="!hasServerChanges"
@click="saveServerChanges()"
>
<SaveIcon /> <SaveIcon />
Save changes Save changes
</button> </button>
</ButtonStyled>
</div> </div>
</section> </section>
@@ -275,15 +272,12 @@
/> />
</div> </div>
<div class="button-group"> <div class="button-group">
<button <ButtonStyled color="brand">
type="button" <button :disabled="!hasChanges" @click="saveChanges()">
class="iconified-button brand-button"
:disabled="!hasChanges"
@click="saveChanges()"
>
<SaveIcon /> <SaveIcon />
Save changes Save changes
</button> </button>
</ButtonStyled>
</div> </div>
</section> </section>
</div> </div>
@@ -293,6 +287,7 @@
import { SaveIcon, TriangleAlertIcon } from '@modrinth/assets' import { SaveIcon, TriangleAlertIcon } from '@modrinth/assets'
import { commonLinkDomains, isCommonUrl, isDiscordUrl, isLinkShortener } from '@modrinth/moderation' import { commonLinkDomains, isCommonUrl, isDiscordUrl, isLinkShortener } from '@modrinth/moderation'
import { import {
ButtonStyled,
DropdownSelect, DropdownSelect,
injectModrinthClient, injectModrinthClient,
injectNotificationManager, injectNotificationManager,

View File

@@ -41,22 +41,23 @@
@keypress.enter="inviteTeamMember()" @keypress.enter="inviteTeamMember()"
/> />
<label for="username" class="hidden">Username</label> <label for="username" class="hidden">Username</label>
<ButtonStyled color="brand">
<button <button
class="iconified-button brand-button"
:disabled="(currentMember?.permissions & MANAGE_INVITES) !== MANAGE_INVITES" :disabled="(currentMember?.permissions & MANAGE_INVITES) !== MANAGE_INVITES"
@click="inviteTeamMember()" @click="inviteTeamMember()"
> >
<UserPlusIcon /> <UserPlusIcon />
Invite Invite
</button> </button>
</ButtonStyled>
</div> </div>
<div class="adjacent-input"> <div class="adjacent-input">
<span class="label"> <span class="label">
<span class="label__title">Leave project</span> <span class="label__title">Leave project</span>
<span class="label__description"> Remove yourself as a member of this project. </span> <span class="label__description"> Remove yourself as a member of this project. </span>
</span> </span>
<ButtonStyled color="red">
<button <button
class="iconified-button danger-button"
:disabled="currentMember?.is_owner" :disabled="currentMember?.is_owner"
:title=" :title="
currentMember?.is_owner ? 'You cannot leave the project if you are the owner!' : '' currentMember?.is_owner ? 'You cannot leave the project if you are the owner!' : ''
@@ -66,6 +67,7 @@
<UserXIcon /> <UserXIcon />
Leave project Leave project
</button> </button>
</ButtonStyled>
</div> </div>
</Card> </Card>
<div <div
@@ -88,8 +90,9 @@
<div class="side-buttons"> <div class="side-buttons">
<Badge v-if="member.accepted" type="accepted" /> <Badge v-if="member.accepted" type="accepted" />
<Badge v-else type="pending" /> <Badge v-else type="pending" />
<ButtonStyled circular>
<button <button
class="square-button dropdown-icon" class="dropdown-icon"
@click=" @click="
openTeamMembers.indexOf(member.user.id) === -1 openTeamMembers.indexOf(member.user.id) === -1
? openTeamMembers.push(member.user.id) ? openTeamMembers.push(member.user.id)
@@ -98,6 +101,7 @@
> >
<DropdownIcon /> <DropdownIcon />
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
@@ -225,31 +229,30 @@
</div> </div>
</template> </template>
<div class="input-group"> <div class="input-group">
<ButtonStyled color="brand">
<button <button
class="iconified-button brand-button"
:disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER" :disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER"
@click="updateTeamMember(index)" @click="updateTeamMember(index)"
> >
<SaveIcon /> <SaveIcon />
Save changes Save changes
</button> </button>
</ButtonStyled>
<ButtonStyled v-if="!member.is_owner" color="red">
<button <button
v-if="!member.is_owner"
class="iconified-button danger-button"
:disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER" :disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER"
@click="removeTeamMember(index)" @click="removeTeamMember(index)"
> >
<UserXIcon /> <UserXIcon />
Remove member Remove member
</button> </button>
<button </ButtonStyled>
v-if="!member.is_owner && currentMember?.is_owner && member.accepted" <ButtonStyled v-if="!member.is_owner && currentMember?.is_owner && member.accepted">
class="iconified-button" <button @click="openTransferModal(index, $event)">
@click="openTransferModal(index, $event)"
>
<TransferIcon /> <TransferIcon />
Transfer ownership Transfer ownership
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -305,19 +308,19 @@
force-direction="up" force-direction="up"
:disabled="!currentMember?.is_owner || organizationOptions.length === 0" :disabled="!currentMember?.is_owner || organizationOptions.length === 0"
/> />
<button <ButtonStyled color="brand">
class="btn btn-primary" <button :disabled="!selectedOrganization" @click="openTransferToOrgModal($event)">
:disabled="!selectedOrganization"
@click="openTransferToOrgModal($event)"
>
<CheckIcon /> <CheckIcon />
<span class="w-max"> Transfer management </span> <span class="w-max"> Transfer management </span>
</button> </button>
</ButtonStyled>
</div> </div>
<button v-if="organization" class="btn" @click="$refs.modal_remove.show()"> <ButtonStyled v-if="organization">
<button @click="$refs.modal_remove.show()">
<OrganizationIcon /> <OrganizationIcon />
Remove from organization Remove from organization
</button> </button>
</ButtonStyled>
</section> </section>
<div <div
v-for="(member, index) in allOrgMembers" v-for="(member, index) in allOrgMembers"
@@ -339,8 +342,9 @@
<div class="side-buttons"> <div class="side-buttons">
<Badge v-if="member.accepted" type="accepted" /> <Badge v-if="member.accepted" type="accepted" />
<Badge v-else type="pending" /> <Badge v-else type="pending" />
<ButtonStyled circular>
<button <button
class="square-button dropdown-icon" class="dropdown-icon"
@click=" @click="
openTeamMembers.indexOf(member.user.id) === -1 openTeamMembers.indexOf(member.user.id) === -1
? openTeamMembers.push(member.user.id) ? openTeamMembers.push(member.user.id)
@@ -349,6 +353,7 @@
> >
<DropdownIcon /> <DropdownIcon />
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
@@ -522,8 +527,8 @@
we don't allow clicking the button in that last case. we don't allow clicking the button in that last case.
--> -->
<ButtonStyled color="brand">
<button <button
class="iconified-button brand-button"
:disabled=" :disabled="
(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER || (currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
(!allOrgMembers[index].oldOverride && !allOrgMembers[index].override) (!allOrgMembers[index].oldOverride && !allOrgMembers[index].override)
@@ -533,6 +538,7 @@
<SaveIcon /> <SaveIcon />
Save changes Save changes
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -554,6 +560,7 @@ import {
import { import {
Avatar, Avatar,
Badge, Badge,
ButtonStyled,
Card, Card,
Checkbox, Checkbox,
Combobox, Combobox,

View File

@@ -1,5 +1,5 @@
<template> <template>
<section class="experimental-styles-within overflow-visible"> <section class="overflow-visible">
<!-- Loading state --> <!-- Loading state -->
<div <div
v-if="versionsLoading && !versions?.length" v-if="versionsLoading && !versions?.length"

View File

@@ -132,7 +132,7 @@
</div> </div>
</div> </div>
</NewModal> </NewModal>
<div class="page experimental-styles-within"> <div class="page">
<div <div
class="mb-4 flex items-center justify-between border-0 border-b border-solid border-divider pb-4" class="mb-4 flex items-center justify-between border-0 border-b border-solid border-divider pb-4"
> >

View File

@@ -96,8 +96,8 @@ onMounted(() => {
</div> </div>
<div class="mt-auto flex gap-2"> <div class="mt-auto flex gap-2">
<ButtonStyled color="brand" class="flex-1"> <ButtonStyled color="brand">
<button class="w-full justify-center" @click="openPreview(id)"> <button class="w-full flex-1 justify-center" @click="openPreview(id)">
<PlayIcon class="h-4 w-4" aria-hidden="true" /> <PlayIcon class="h-4 w-4" aria-hidden="true" />
Preview Preview
</button> </button>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { CopyIcon, LibraryIcon, PlayIcon, SearchIcon } from '@modrinth/assets' import { CopyIcon, LibraryIcon, PlayIcon, SearchIcon } from '@modrinth/assets'
import { ButtonStyled, Card, NewModal, StyledInput } from '@modrinth/ui' import { ButtonStyled, NewModal, StyledInput } from '@modrinth/ui'
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import emails from '~/templates/emails' import emails from '~/templates/emails'
@@ -178,15 +178,14 @@ onMounted(() => {
</div> </div>
<div class="input-group mt-4"> <div class="input-group mt-4">
<button class="iconified-button transparent" type="button" @click="closePreview"> <ButtonStyled type="transparent">
Close <button @click="closePreview">Close</button>
</button> </ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
</NewModal> </NewModal>
<div class="normal-page__content"> <div class="normal-page__content">
<Card class="mb-6 flex flex-col gap-4">
<div class="flex flex-wrap items-center gap-3"> <div class="flex flex-wrap items-center gap-3">
<StyledInput <StyledInput
id="email-search" id="email-search"
@@ -212,16 +211,16 @@ onMounted(() => {
<div <div
v-if="filtered.length === 0" v-if="filtered.length === 0"
class="rounded-lg border border-dashed border-divider px-6 py-10 text-center text-sm text-secondary" class="mt-4 border-0 border-b border-solid border-surface-4 pb-4"
> >
No templates match your search. No templates match your search.
</div> </div>
<ul v-else class="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> <ul v-else class="m-0 mt-4 grid gap-3 p-0 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<li <li
v-for="id in filtered" v-for="id in filtered"
:key="id" :key="id"
class="hover:border-green/70 group flex flex-col justify-between rounded-lg border border-divider bg-button-bg p-4 shadow-sm transition hover:shadow" class="group flex flex-col justify-between rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4 shadow-sm transition hover:shadow"
> >
<div class="mb-3"> <div class="mb-3">
<div class="font-mono text-sm font-semibold tracking-tight text-contrast"> <div class="font-mono text-sm font-semibold tracking-tight text-contrast">
@@ -233,29 +232,28 @@ onMounted(() => {
</div> </div>
<div class="mt-auto flex gap-2"> <div class="mt-auto flex gap-2">
<ButtonStyled color="brand" class="flex-1"> <ButtonStyled color="brand">
<button class="w-full justify-center" @click="openPreview(id, $event)"> <button @click="openPreview(id, $event)">
<PlayIcon class="h-4 w-4" aria-hidden="true" /> <PlayIcon aria-hidden="true" />
Preview Preview
</button> </button>
</ButtonStyled> </ButtonStyled>
<ButtonStyled> <ButtonStyled circular type="outlined">
<button class="justify-center" title="Copy preview URL" @click="copy(id)"> <button title="Copy preview URL" @click="copy(id)">
<CopyIcon class="h-4 w-4" aria-hidden="true" /> <CopyIcon aria-hidden="true" />
</button> </button>
</ButtonStyled> </ButtonStyled>
</div> </div>
</li> </li>
</ul> </ul>
</Card>
<p class="mt-2 text-xs text-secondary"> <p class="mt-4">
All templates come from All templates come from
<code class="rounded bg-code-bg px-1 py-0.5 text-[11px] text-code-text" <code class="rounded bg-code-bg px-1 py-0.5 text-sm text-code-text"
>src/emails/index.ts</code >src/emails/index.ts</code
>. Popouts render via >. Popouts render via
<code class="rounded bg-code-bg px-1 py-0.5 text-[11px] text-code-text" <code class="rounded bg-code-bg px-1 py-0.5 text-sm text-code-text"
>/_internal/templates/email/[template]</code >/_internal/templates/email/[template]</code
>. >.
</p> </p>

View File

@@ -120,7 +120,7 @@
</div> </div>
</NewModal> </NewModal>
<AssignNoticeModal ref="assignNoticeModal" @close="refreshNotices" /> <AssignNoticeModal ref="assignNoticeModal" @close="refreshNotices" />
<div class="page experimental-styles-within"> <div class="page">
<div <div
class="mb-6 flex items-end justify-between border-0 border-b border-solid border-divider pb-4" class="mb-6 flex items-end justify-between border-0 border-b border-solid border-divider pb-4"
> >

View File

@@ -8,7 +8,7 @@
proceed-label="Cancel transfer" proceed-label="Cancel transfer"
@proceed="confirmCancel" @proceed="confirmCancel"
/> />
<div class="experimental-styles-within mx-auto max-w-[78.5rem] p-4"> <div class="mx-auto max-w-[78.5rem] p-4">
<div <div
class="mb-6 flex items-end justify-between border-0 border-b border-solid border-divider pb-4" class="mb-6 flex items-end justify-between border-0 border-b border-solid border-divider pb-4"
> >

View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
ArrowDownIcon,
BoxIcon, BoxIcon,
DownloadIcon, DownloadIcon,
EditIcon, EditIcon,
@@ -13,6 +14,7 @@ import {
Accordion, Accordion,
Avatar, Avatar,
Badge, Badge,
ButtonStyled,
Checkbox, Checkbox,
commonMessages, commonMessages,
defineMessages, defineMessages,
@@ -541,12 +543,8 @@ useSeoMeta({
{{ formatMessage(messages.description) }} {{ formatMessage(messages.description) }}
</h2> </h2>
<div class="button-group"> <div class="button-group">
<button <ButtonStyled v-if="os" color="brand" size="large">
v-if="os" <button rel="noopener nofollow" @click="handleDownload">
class="iconified-button brand-button btn btn-large"
rel="noopener nofollow"
@click="handleDownload"
>
<svg <svg
v-if="os === 'Linux'" v-if="os === 'Linux'"
class="light-icon" class="light-icon"
@@ -599,9 +597,13 @@ useSeoMeta({
</svg> </svg>
{{ formatMessage(messages.downloadModrinthAppButton) }} {{ formatMessage(messages.downloadModrinthAppButton) }}
</button> </button>
<button class="iconified-button outline-button btn btn-large" @click="scrollToSection"> </ButtonStyled>
<ButtonStyled type="outlined" size="large">
<button @click="scrollToSection">
<ArrowDownIcon />
{{ formatMessage(messages.moreDownloadOptions) }} {{ formatMessage(messages.moreDownloadOptions) }}
</button> </button>
</ButtonStyled>
</div> </div>
<img src="https://cdn-raw.modrinth.com/app-landing/app-screenshot.webp" alt="" /> <img src="https://cdn-raw.modrinth.com/app-landing/app-screenshot.webp" alt="" />
<div class="bottom-transition" /> <div class="bottom-transition" />
@@ -644,9 +646,11 @@ useSeoMeta({
<div class="cell">{{ mod.version }}</div> <div class="cell">{{ mod.version }}</div>
<div class="cell check"> <div class="cell check">
<Checkbox :model-value="true" tabindex="-1" /> <Checkbox :model-value="true" tabindex="-1" />
<button class="btn icon-only transparent" tabindex="-1"> <ButtonStyled circular type="transparent">
<button tabindex="-1">
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -1278,12 +1282,6 @@ useSeoMeta({
margin: 0 auto; margin: 0 auto;
justify-content: center; justify-content: center;
mask-image: none; mask-image: none;
.outline-button {
color: var(--landing-color-heading);
background: none;
border: 1px var(--landing-color-heading) solid;
}
} }
img { img {
@@ -2052,7 +2050,6 @@ useSeoMeta({
background: rgba(59, 63, 85, 0.15); background: rgba(59, 63, 85, 0.15);
box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.16); box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.16);
.btn,
button { button {
&:hover { &:hover {
cursor: default !important; cursor: default !important;

View File

@@ -31,17 +31,19 @@ useSeoMeta({
margin: 0; margin: 0;
} }
.auth-container .btn { .auth-container .btn-wrapper :is(a, button) {
font-weight: 700; font-weight: 700;
min-height: 2.5rem; min-height: 2.5rem;
text-decoration: none; text-decoration: none;
} }
.centered-btn { .centered-btn :is(a, button),
.centered-btn:is(a, button) {
margin-inline: auto; margin-inline: auto;
} }
.btn.continue-btn svg { .continue-btn :is(a, button) svg,
.continue-btn:is(a, button) svg {
margin: 0 0 0 0.5rem; margin: 0 0 0 0.5rem;
} }
@@ -52,19 +54,19 @@ useSeoMeta({
width: 100%; width: 100%;
} }
.third-party .btn { .third-party a {
width: 100%; width: 100%;
vertical-align: middle; vertical-align: middle;
} }
.third-party .btn svg { .third-party a svg {
margin-right: var(--gap-sm); margin-right: var(--gap-sm);
width: 1.25rem; width: 1.25rem;
height: 1.25rem; height: 1.25rem;
} }
@media screen and (max-width: 25.5rem) { @media screen and (max-width: 25.5rem) {
.third-party .btn { .third-party a {
grid-column: 1 / 3; grid-column: 1 / 3;
} }
} }

View File

@@ -55,14 +55,18 @@
</div> </div>
</div> </div>
<div class="button-row"> <div class="button-row">
<Button class="wide-button" large :action="onReject" :disabled="pending"> <ButtonStyled size="large">
<button class="wide-button" :disabled="pending" @click="onReject">
<XIcon /> <XIcon />
{{ formatMessage(messages.decline) }} {{ formatMessage(messages.decline) }}
</Button> </button>
<Button class="wide-button" color="primary" large :action="onAuthorize" :disabled="pending"> </ButtonStyled>
<ButtonStyled color="brand" size="large">
<button class="wide-button" :disabled="pending" @click="onAuthorize">
<CheckIcon /> <CheckIcon />
{{ formatMessage(messages.authorize) }} {{ formatMessage(messages.authorize) }}
</Button> </button>
</ButtonStyled>
</div> </div>
<div class="redirection-notice"> <div class="redirection-notice">
<p class="redirect-instructions"> <p class="redirect-instructions">
@@ -83,7 +87,7 @@
import { CheckIcon, XIcon } from '@modrinth/assets' import { CheckIcon, XIcon } from '@modrinth/assets'
import { import {
Avatar, Avatar,
Button, ButtonStyled,
commonMessages, commonMessages,
defineMessages, defineMessages,
injectModrinthClient, injectModrinthClient,

View File

@@ -22,13 +22,15 @@
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" /> <HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
<ButtonStyled color="brand">
<button <button
class="btn btn-primary centered-btn" class="mx-auto"
:disabled="globals?.captcha_enabled ? !token : false" :disabled="globals?.captcha_enabled ? !token : false"
@click="recovery" @click="recovery"
> >
<SendIcon /> {{ formatMessage(methodChoiceMessages.action) }} <SendIcon /> {{ formatMessage(methodChoiceMessages.action) }}
</button> </button>
</ButtonStyled>
</template> </template>
<template v-else-if="step === 'passed_challenge'"> <template v-else-if="step === 'passed_challenge'">
<p>{{ formatMessage(postChallengeMessages.description) }}</p> <p>{{ formatMessage(postChallengeMessages.description) }}</p>
@@ -57,9 +59,11 @@
wrapper-class="w-full" wrapper-class="w-full"
/> />
<button class="auth-form__input btn btn-primary continue-btn" @click="changePassword"> <ButtonStyled color="brand">
<button class="auth-form__input continue-btn" @click="changePassword">
{{ formatMessage(postChallengeMessages.action) }} {{ formatMessage(postChallengeMessages.action) }}
</button> </button>
</ButtonStyled>
</template> </template>
</section> </section>
</div> </div>
@@ -67,6 +71,7 @@
<script setup> <script setup>
import { KeyIcon, MailIcon, SendIcon } from '@modrinth/assets' import { KeyIcon, MailIcon, SendIcon } from '@modrinth/assets'
import { import {
ButtonStyled,
commonMessages, commonMessages,
defineMessages, defineMessages,
injectModrinthClient, injectModrinthClient,

View File

@@ -23,38 +23,52 @@
@keyup.enter="begin2FASignIn" @keyup.enter="begin2FASignIn"
/> />
<button class="btn btn-primary continue-btn" @click="begin2FASignIn"> <ButtonStyled color="brand">
<button class="continue-btn" @click="begin2FASignIn">
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon /> {{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
</button> </button>
</ButtonStyled>
</template> </template>
<template v-else> <template v-else>
<h1>{{ formatMessage(messages.signInWithLabel) }}</h1> <h1>{{ formatMessage(messages.signInWithLabel) }}</h1>
<section class="third-party"> <section class="third-party">
<a class="btn" :href="getAuthUrl('discord', redirectTarget)"> <ButtonStyled>
<a :href="getAuthUrl('discord', redirectTarget)">
<DiscordColorIcon /> <DiscordColorIcon />
<span>Discord</span> <span>Discord</span>
</a> </a>
<a class="btn" :href="getAuthUrl('github', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('github', redirectTarget)">
<GitHubColorIcon /> <GitHubColorIcon />
<span>GitHub</span> <span>GitHub</span>
</a> </a>
<a class="btn" :href="getAuthUrl('microsoft', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('microsoft', redirectTarget)">
<MicrosoftColorIcon /> <MicrosoftColorIcon />
<span>Microsoft</span> <span>Microsoft</span>
</a> </a>
<a class="btn" :href="getAuthUrl('google', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('google', redirectTarget)">
<GoogleColorIcon /> <GoogleColorIcon />
<span>Google</span> <span>Google</span>
</a> </a>
<a class="btn" :href="getAuthUrl('steam', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('steam', redirectTarget)">
<SteamColorIcon /> <SteamColorIcon />
<span>Steam</span> <span>Steam</span>
</a> </a>
<a class="btn" :href="getAuthUrl('gitlab', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('gitlab', redirectTarget)">
<GitLabColorIcon /> <GitLabColorIcon />
<span>GitLab</span> <span>GitLab</span>
</a> </a>
</ButtonStyled>
</section> </section>
<h1>{{ formatMessage(messages.usePasswordLabel) }}</h1> <h1>{{ formatMessage(messages.usePasswordLabel) }}</h1>
@@ -85,13 +99,15 @@
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" /> <HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
<ButtonStyled color="brand">
<button <button
class="btn btn-primary continue-btn centered-btn" class="continue-btn centered-btn"
:disabled="globals?.captcha_enabled ? !token : false" :disabled="globals?.captcha_enabled ? !token : false"
@click="beginPasswordSignIn()" @click="beginPasswordSignIn()"
> >
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon /> {{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
</button> </button>
</ButtonStyled>
<div class="auth-form__additional-options"> <div class="auth-form__additional-options">
<IntlFormatted :message-id="messages.additionalOptionsLabel"> <IntlFormatted :message-id="messages.additionalOptionsLabel">
@@ -137,6 +153,7 @@ import {
SteamColorIcon, SteamColorIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
ButtonStyled,
commonMessages, commonMessages,
defineMessages, defineMessages,
injectModrinthClient, injectModrinthClient,

View File

@@ -3,30 +3,42 @@
<h1>{{ formatMessage(messages.signUpWithTitle) }}</h1> <h1>{{ formatMessage(messages.signUpWithTitle) }}</h1>
<section class="third-party"> <section class="third-party">
<a class="btn discord-btn" :href="getAuthUrl('discord', redirectTarget)"> <ButtonStyled>
<a class="discord-btn" :href="getAuthUrl('discord', redirectTarget)">
<DiscordColorIcon /> <DiscordColorIcon />
<span>Discord</span> <span>Discord</span>
</a> </a>
<a class="btn" :href="getAuthUrl('github', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('github', redirectTarget)">
<GitHubColorIcon /> <GitHubColorIcon />
<span>GitHub</span> <span>GitHub</span>
</a> </a>
<a class="btn" :href="getAuthUrl('microsoft', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('microsoft', redirectTarget)">
<MicrosoftColorIcon /> <MicrosoftColorIcon />
<span>Microsoft</span> <span>Microsoft</span>
</a> </a>
<a class="btn" :href="getAuthUrl('google', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('google', redirectTarget)">
<GoogleColorIcon /> <GoogleColorIcon />
<span>Google</span> <span>Google</span>
</a> </a>
<a class="btn" :href="getAuthUrl('steam', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('steam', redirectTarget)">
<SteamColorIcon /> <SteamColorIcon />
<span>Steam</span> <span>Steam</span>
</a> </a>
<a class="btn" :href="getAuthUrl('gitlab', redirectTarget)"> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('gitlab', redirectTarget)">
<GitLabColorIcon /> <GitLabColorIcon />
<span>GitLab</span> <span>GitLab</span>
</a> </a>
</ButtonStyled>
</section> </section>
<h1>{{ formatMessage(messages.createAccountTitle) }}</h1> <h1>{{ formatMessage(messages.createAccountTitle) }}</h1>
@@ -100,13 +112,15 @@
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" /> <HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
<ButtonStyled color="brand">
<button <button
class="btn btn-primary continue-btn centered-btn" class="continue-btn centered-btn"
:disabled="globals?.captcha_enabled ? !token : false" :disabled="globals?.captcha_enabled ? !token : false"
@click="createAccount" @click="createAccount"
> >
{{ formatMessage(messages.createAccountButton) }} <RightArrowIcon /> {{ formatMessage(messages.createAccountButton) }} <RightArrowIcon />
</button> </button>
</ButtonStyled>
<div class="auth-form__additional-options"> <div class="auth-form__additional-options">
{{ formatMessage(messages.alreadyHaveAccountLabel) }} {{ formatMessage(messages.alreadyHaveAccountLabel) }}
@@ -138,6 +152,7 @@ import {
UserIcon, UserIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
ButtonStyled,
Checkbox, Checkbox,
commonMessages, commonMessages,
defineMessages, defineMessages,

View File

@@ -6,9 +6,11 @@
<section class="auth-form"> <section class="auth-form">
<p>{{ formatMessage(alreadyVerifiedMessages.description) }}</p> <p>{{ formatMessage(alreadyVerifiedMessages.description) }}</p>
<NuxtLink class="btn" to="/settings/account"> <ButtonStyled>
<NuxtLink to="/settings/account">
<SettingsIcon /> {{ formatMessage(messages.accountSettings) }} <SettingsIcon /> {{ formatMessage(messages.accountSettings) }}
</NuxtLink> </NuxtLink>
</ButtonStyled>
</section> </section>
</template> </template>

View File

@@ -24,10 +24,12 @@
:description="formatMessage(messages.subscribeCheckbox)" :description="formatMessage(messages.subscribeCheckbox)"
/> />
<button class="btn btn-primary centered-btn" @click="continueSignUp"> <ButtonStyled color="brand">
<button class="centered-btn" @click="continueSignUp">
{{ formatMessage(commonMessages.continueButton) }} {{ formatMessage(commonMessages.continueButton) }}
<RightArrowIcon /> <RightArrowIcon />
</button> </button>
</ButtonStyled>
<p class="tos-text"> <p class="tos-text">
<IntlFormatted :message-id="messages.tosLabel"> <IntlFormatted :message-id="messages.tosLabel">
@@ -50,6 +52,7 @@
<script setup> <script setup>
import { RightArrowIcon, WavingRinthbot } from '@modrinth/assets' import { RightArrowIcon, WavingRinthbot } from '@modrinth/assets'
import { import {
ButtonStyled,
Checkbox, Checkbox,
commonMessages, commonMessages,
defineMessages, defineMessages,

View File

@@ -116,14 +116,14 @@
</RadioButtons> </RadioButtons>
</div> </div>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<ButtonStyled class="w-24"> <ButtonStyled>
<button @click="() => editModal?.hide()"> <button class="w-24" @click="() => editModal?.hide()">
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
</ButtonStyled> </ButtonStyled>
<ButtonStyled color="brand" class="w-36"> <ButtonStyled color="brand">
<button :disabled="saving" @click="save()"> <button class="w-36" :disabled="saving" @click="save()">
<SpinnerIcon v-if="saving" class="animate-spin" aria-hidden="true" /> <SpinnerIcon v-if="saving" class="animate-spin" aria-hidden="true" />
<SaveIcon v-else aria-hidden="true" /> <SaveIcon v-else aria-hidden="true" />
{{ {{
@@ -193,7 +193,7 @@
}" }"
> >
<template #stat="{ children }"> <template #stat="{ children }">
<span class="primary-stat__counter"> <span>
<component :is="() => normalizeChildren(children)" /> <component :is="() => normalizeChildren(children)" />
</span> </span>
</template> </template>
@@ -333,24 +333,19 @@
" "
> >
<template v-if="canEdit || collection.id === 'following'" #actions> <template v-if="canEdit || collection.id === 'following'" #actions>
<button <ButtonStyled v-if="canEdit">
v-if="canEdit" <button class="remove-btn" :disabled="removing" @click="() => removeProject(project)">
class="iconified-button remove-btn"
:disabled="removing"
@click="() => removeProject(project)"
>
<SpinnerIcon v-if="removing" class="animate-spin" aria-hidden="true" /> <SpinnerIcon v-if="removing" class="animate-spin" aria-hidden="true" />
<XIcon v-else aria-hidden="true" /> <XIcon v-else aria-hidden="true" />
{{ formatMessage(messages.removeProjectButton) }} {{ formatMessage(messages.removeProjectButton) }}
</button> </button>
<button </ButtonStyled>
v-if="collection.id === 'following'" <ButtonStyled v-if="collection.id === 'following'">
class="iconified-button" <button @click="unfollowProject(project)">
@click="unfollowProject(project)"
>
<HeartMinusIcon aria-hidden="true" /> <HeartMinusIcon aria-hidden="true" />
{{ formatMessage(messages.unfollowProjectButton) }} {{ formatMessage(messages.unfollowProjectButton) }}
</button> </button>
</ButtonStyled>
</template> </template>
</ProjectCard> </ProjectCard>
</ProjectCardList> </ProjectCardList>

View File

@@ -30,14 +30,12 @@
<span class="font-semibold text-secondary">{{ selected }}</span> <span class="font-semibold text-secondary">{{ selected }}</span>
</DropdownSelect> </DropdownSelect>
<Button <ButtonStyled color="brand">
color="primary" <button class="ml-auto" @click="(event) => $refs.modal_creation.show(event)">
class="ml-auto"
@click="(event) => $refs.modal_creation.show(event)"
>
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createNewButton) }} {{ formatMessage(messages.createNewButton) }}
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="collections-grid"> <div class="collections-grid">
@@ -146,7 +144,7 @@ import {
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
Avatar, Avatar,
Button, ButtonStyled,
commonMessages, commonMessages,
defineMessages, defineMessages,
DropdownSelect, DropdownSelect,

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="dashboard-overview"> <div>
<section class="universal-card dashboard-header"> <section class="universal-card dashboard-header">
<Avatar :src="auth.user.avatar_url" size="md" circle :alt="auth.user.username" /> <Avatar :src="auth.user.avatar_url" size="md" circle :alt="auth.user.username" />
<div class="username"> <div class="username">
@@ -12,7 +12,7 @@
</NuxtLink> </NuxtLink>
</div> </div>
</section> </section>
<div class="dashboard-notifications"> <div>
<section class="universal-card"> <section class="universal-card">
<div class="header__row"> <div class="header__row">
<h2 class="header__title text-2xl"> <h2 class="header__title text-2xl">
@@ -50,44 +50,12 @@
</template> </template>
<div v-else class="universal-body"> <div v-else class="universal-body">
<p>{{ formatMessage(messages.noUnreadNotifications) }}</p> <p>{{ formatMessage(messages.noUnreadNotifications) }}</p>
<nuxt-link class="iconified-button !mt-4" to="/dashboard/notifications/history"> <ButtonStyled>
<nuxt-link to="/dashboard/notifications/history" class="!mt-4 w-fit">
<HistoryIcon /> <HistoryIcon />
{{ formatMessage(messages.viewNotificationHistory) }} {{ formatMessage(messages.viewNotificationHistory) }}
</nuxt-link> </nuxt-link>
</div> </ButtonStyled>
</section>
</div>
<div class="dashboard-analytics">
<section class="universal-card">
<h2>{{ formatMessage(commonMessages.analyticsButton) }}</h2>
<div class="grid-display">
<div class="grid-display__item">
<div class="label">{{ formatMessage(messages.totalDownloads) }}</div>
<div class="value">
{{ $formatNumber(projects.reduce((agg, x) => agg + x.downloads, 0)) }}
</div>
<span>{{
formatMessage(messages.fromProjects, { count: downloadsProjectCount })
}}</span>
<!-- <NuxtLink class="goto-link" to="/dashboard/analytics"-->
<!-- >View breakdown-->
<!-- <ChevronRightIcon-->
<!-- class="featured-header-chevron"-->
<!-- aria-hidden="true"-->
<!-- /></NuxtLink>-->
</div>
<div class="grid-display__item">
<div class="label">{{ formatMessage(messages.totalFollowers) }}</div>
<div class="value">
{{ $formatNumber(projects.reduce((agg, x) => agg + x.followers, 0)) }}
</div>
<span>
<span>{{
formatMessage(messages.fromProjects, { count: followersProjectCount })
}}</span>
</span>
</div>
</div> </div>
</section> </section>
</div> </div>
@@ -97,6 +65,7 @@
import { ChevronRightIcon, HistoryIcon } from '@modrinth/assets' import { ChevronRightIcon, HistoryIcon } from '@modrinth/assets'
import { import {
Avatar, Avatar,
ButtonStyled,
commonMessages, commonMessages,
defineMessages, defineMessages,
injectModrinthClient, injectModrinthClient,
@@ -152,19 +121,6 @@ useHead({
const auth = await useAuth() const auth = await useAuth()
const client = injectModrinthClient() const client = injectModrinthClient()
const { data: projects } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'projects']),
queryFn: () => client.labrinth.users_v2.getProjects(auth.value?.user?.id),
placeholderData: [],
})
const downloadsProjectCount = computed(
() => projects.value.filter((project) => project.downloads > 0).length,
)
const followersProjectCount = computed(
() => projects.value.filter((project) => project.followers > 0).length,
)
const { data, refetch } = useQuery({ const { data, refetch } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'notifications']), queryKey: computed(() => ['user', auth.value?.user?.id, 'notifications']),
queryFn: async () => { queryFn: async () => {
@@ -190,40 +146,6 @@ const notifications = computed(() => {
const extraNotifs = computed(() => (data.value ? data.value.extraNotifs : 0)) const extraNotifs = computed(() => (data.value ? data.value.extraNotifs : 0))
</script> </script>
<style lang="scss"> <style lang="scss">
.dashboard-overview {
display: grid;
grid-template:
'header header'
'notifications analytics' / 1fr auto;
gap: var(--spacing-card-md);
> .universal-card {
margin: 0;
}
@media screen and (max-width: 750px) {
display: flex;
flex-direction: column;
}
}
.dashboard-notifications {
grid-area: notifications;
//display: flex;
//flex-direction: column;
//gap: var(--spacing-card-md);
a.view-more-notifs {
display: flex;
width: fit-content;
margin-left: auto;
}
}
.dashboard-analytics {
grid-area: analytics;
}
.dashboard-header { .dashboard-header {
display: flex; display: flex;
gap: var(--spacing-card-bg); gap: var(--spacing-card-bg);

View File

@@ -21,14 +21,18 @@
</h2> </h2>
</div> </div>
<template v-if="!history"> <template v-if="!history">
<Button v-if="data.hasRead" @click="updateRoute()"> <ButtonStyled v-if="data.hasRead">
<button @click="updateRoute()">
<HistoryIcon /> <HistoryIcon />
{{ formatMessage(messages.viewHistory) }} {{ formatMessage(messages.viewHistory) }}
</Button> </button>
<Button v-if="notifications.length > 0" color="danger" @click="readAll()"> </ButtonStyled>
<ButtonStyled v-if="notifications.length > 0" color="red">
<button @click="readAll()">
<CheckCheckIcon /> <CheckCheckIcon />
{{ formatMessage(messages.markAllAsRead) }} {{ formatMessage(messages.markAllAsRead) }}
</Button> </button>
</ButtonStyled>
</template> </template>
</div> </div>
<Chips <Chips
@@ -67,7 +71,7 @@
<script setup> <script setup>
import { CheckCheckIcon, HistoryIcon } from '@modrinth/assets' import { CheckCheckIcon, HistoryIcon } from '@modrinth/assets'
import { import {
Button, ButtonStyled,
Chips, Chips,
commonMessages, commonMessages,
defineMessages, defineMessages,

View File

@@ -5,10 +5,12 @@
<div class="header__row"> <div class="header__row">
<h2 class="header__title text-2xl">{{ formatMessage(messages.organizationsTitle) }}</h2> <h2 class="header__title text-2xl">{{ formatMessage(messages.organizationsTitle) }}</h2>
<div class="input-group"> <div class="input-group">
<button class="iconified-button brand-button" @click="openCreateOrgModal"> <ButtonStyled color="brand">
<button @click="openCreateOrgModal">
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createOrganization) }} {{ formatMessage(messages.createOrganization) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<template v-if="orgs?.length > 0"> <template v-if="orgs?.length > 0">
@@ -51,7 +53,7 @@
<script setup> <script setup>
import { PlusIcon, UsersIcon } from '@modrinth/assets' import { PlusIcon, UsersIcon } from '@modrinth/assets'
import { Avatar, defineMessages, injectModrinthClient, useVIntl } from '@modrinth/ui' import { Avatar, ButtonStyled, defineMessages, injectModrinthClient, useVIntl } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query' import { useQuery } from '@tanstack/vue-query'
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue' import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'

View File

@@ -7,68 +7,77 @@
<label for="issue-tracker-input" :title="formatMessage(messages.issueTrackerDescription)"> <label for="issue-tracker-input" :title="formatMessage(messages.issueTrackerDescription)">
<span class="label__title">{{ formatMessage(messages.issueTrackerLabel) }}</span> <span class="label__title">{{ formatMessage(messages.issueTrackerLabel) }}</span>
</label> </label>
<div class="input-group shrink-first"> <div class="flex gap-2">
<StyledInput <StyledInput
id="issue-tracker-input" id="issue-tracker-input"
v-model="editLinks.issues.val" v-model="editLinks.issues.val"
:disabled="editLinks.issues.clear" :disabled="editLinks.issues.clear"
type="url" type="url"
class="w-full"
:placeholder="getLinkInputPlaceholder(editLinks.issues.clear)" :placeholder="getLinkInputPlaceholder(editLinks.issues.clear)"
:maxlength="2048" :maxlength="2048"
/> />
<ButtonStyled circular>
<button <button
v-tooltip="formatMessage(messages.clearLinkLabel)" v-tooltip="formatMessage(messages.clearLinkLabel)"
class="label-button"
:aria-label="formatMessage(messages.clearLinkLabel)" :aria-label="formatMessage(messages.clearLinkLabel)"
class="square-button label-button"
:data-active="editLinks.issues.clear" :data-active="editLinks.issues.clear"
@click="editLinks.issues.clear = !editLinks.issues.clear" @click="editLinks.issues.clear = !editLinks.issues.clear"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
<label for="source-code-input" :title="formatMessage(messages.sourceCodeDescription)"> <label for="source-code-input" :title="formatMessage(messages.sourceCodeDescription)">
<span class="label__title">{{ formatMessage(messages.sourceCodeLabel) }}</span> <span class="label__title">{{ formatMessage(messages.sourceCodeLabel) }}</span>
</label> </label>
<div class="input-group shrink-first"> <div class="flex gap-2">
<StyledInput <StyledInput
id="source-code-input" id="source-code-input"
v-model="editLinks.source.val" v-model="editLinks.source.val"
:disabled="editLinks.source.clear" :disabled="editLinks.source.clear"
type="url" type="url"
class="w-full"
:maxlength="2048" :maxlength="2048"
:placeholder="getLinkInputPlaceholder(editLinks.source.clear)" :placeholder="getLinkInputPlaceholder(editLinks.source.clear)"
/> />
<ButtonStyled circular>
<button <button
v-tooltip="formatMessage(messages.clearLinkLabel)" v-tooltip="formatMessage(messages.clearLinkLabel)"
class="label-button"
:aria-label="formatMessage(messages.clearLinkLabel)" :aria-label="formatMessage(messages.clearLinkLabel)"
class="square-button label-button"
:data-active="editLinks.source.clear" :data-active="editLinks.source.clear"
@click="editLinks.source.clear = !editLinks.source.clear" @click="editLinks.source.clear = !editLinks.source.clear"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
<label for="wiki-page-input" :title="formatMessage(messages.wikiPageDescription)"> <label for="wiki-page-input" :title="formatMessage(messages.wikiPageDescription)">
<span class="label__title">{{ formatMessage(messages.wikiPageLabel) }}</span> <span class="label__title">{{ formatMessage(messages.wikiPageLabel) }}</span>
</label> </label>
<div class="input-group shrink-first"> <div class="flex gap-2">
<StyledInput <StyledInput
id="wiki-page-input" id="wiki-page-input"
v-model="editLinks.wiki.val" v-model="editLinks.wiki.val"
:disabled="editLinks.wiki.clear" :disabled="editLinks.wiki.clear"
type="url" type="url"
class="w-full"
:maxlength="2048" :maxlength="2048"
:placeholder="getLinkInputPlaceholder(editLinks.wiki.clear)" :placeholder="getLinkInputPlaceholder(editLinks.wiki.clear)"
/> />
<ButtonStyled circular>
<button <button
v-tooltip="formatMessage(messages.clearLinkLabel)" v-tooltip="formatMessage(messages.clearLinkLabel)"
class="label-button"
:aria-label="formatMessage(messages.clearLinkLabel)" :aria-label="formatMessage(messages.clearLinkLabel)"
class="square-button label-button"
:data-active="editLinks.wiki.clear" :data-active="editLinks.wiki.clear"
@click="editLinks.wiki.clear = !editLinks.wiki.clear" @click="editLinks.wiki.clear = !editLinks.wiki.clear"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
<label <label
for="discord-invite-input" for="discord-invite-input"
@@ -76,24 +85,27 @@
> >
<span class="label__title">{{ formatMessage(messages.discordInviteLabel) }}</span> <span class="label__title">{{ formatMessage(messages.discordInviteLabel) }}</span>
</label> </label>
<div class="input-group shrink-first"> <div class="flex gap-2">
<StyledInput <StyledInput
id="discord-invite-input" id="discord-invite-input"
v-model="editLinks.discord.val" v-model="editLinks.discord.val"
:disabled="editLinks.discord.clear" :disabled="editLinks.discord.clear"
class="w-full"
type="url" type="url"
:maxlength="2048" :maxlength="2048"
:placeholder="getLinkInputPlaceholder(editLinks.discord.clear, true)" :placeholder="getLinkInputPlaceholder(editLinks.discord.clear, true)"
/> />
<ButtonStyled circular>
<button <button
v-tooltip="formatMessage(messages.clearLinkLabel)" v-tooltip="formatMessage(messages.clearLinkLabel)"
class="label-button"
:aria-label="formatMessage(messages.clearLinkLabel)" :aria-label="formatMessage(messages.clearLinkLabel)"
class="square-button label-button"
:data-active="editLinks.discord.clear" :data-active="editLinks.discord.clear"
@click="editLinks.discord.clear = !editLinks.discord.clear" @click="editLinks.discord.clear = !editLinks.discord.clear"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
</section> </section>
<p> <p>
@@ -128,15 +140,19 @@
:label="formatMessage(messages.showAllProjects)" :label="formatMessage(messages.showAllProjects)"
:description="formatMessage(messages.showAllProjects)" :description="formatMessage(messages.showAllProjects)"
/> />
<div class="push-right input-group"> <div class="input-group ml-auto mt-4">
<button class="iconified-button" @click="$refs.editLinksModal.hide()"> <ButtonStyled type="outlined">
<button @click="$refs.editLinksModal.hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
<button class="iconified-button brand-button" @click="bulkEditLinks()"> </ButtonStyled>
<ButtonStyled color="brand">
<button @click="bulkEditLinks()">
<SaveIcon /> <SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }} {{ formatMessage(commonMessages.saveChangesButton) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</NewModal> </NewModal>
@@ -145,10 +161,12 @@
<div class="header__row"> <div class="header__row">
<h2 class="header__title text-2xl">{{ formatMessage(messages.headTitle) }}</h2> <h2 class="header__title text-2xl">{{ formatMessage(messages.headTitle) }}</h2>
<div class="input-group"> <div class="input-group">
<button class="iconified-button brand-button" @click="$refs.modal_creation.show($event)"> <ButtonStyled color="brand">
<button @click="$refs.modal_creation.show($event)">
<PlusIcon /> <PlusIcon />
{{ formatMessage(commonMessages.createAProjectButton) }} {{ formatMessage(commonMessages.createAProjectButton) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<p v-if="projects.length < 1"> <p v-if="projects.length < 1">
@@ -157,14 +175,12 @@
<template v-else> <template v-else>
<p>{{ formatMessage(messages.bulkEditHint) }}</p> <p>{{ formatMessage(messages.bulkEditHint) }}</p>
<div class="input-group"> <div class="input-group">
<button <ButtonStyled>
class="iconified-button" <button :disabled="selectedProjects.length === 0" @click="$refs.editLinksModal.show()">
:disabled="selectedProjects.length === 0"
@click="$refs.editLinksModal.show()"
>
<EditIcon /> <EditIcon />
{{ formatMessage(messages.editLinksButton) }} {{ formatMessage(messages.editLinksButton) }}
</button> </button>
</ButtonStyled>
<div class="push-right"> <div class="push-right">
<div class="labeled-control-row"> <div class="labeled-control-row">
{{ formatMessage(commonMessages.sortByLabel) }} {{ formatMessage(commonMessages.sortByLabel) }}
@@ -175,14 +191,15 @@
:options="sortOptions" :options="sortOptions"
@update:model-value="projects = updateSort(projects, sortBy, descending)" @update:model-value="projects = updateSort(projects, sortBy, descending)"
/> />
<ButtonStyled circular>
<button <button
v-tooltip="formatMessage(descending ? messages.descending : messages.ascending)" v-tooltip="formatMessage(descending ? messages.descending : messages.ascending)"
class="square-button"
@click="updateDescending()" @click="updateDescending()"
> >
<SortDescIcon v-if="descending" /> <SortDescIcon v-if="descending" />
<SortAscIcon v-else /> <SortAscIcon v-else />
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -603,9 +603,7 @@
</h2> </h2>
</div> </div>
<div <div class="flex w-full flex-col-reverse gap-2 md:w-auto md:flex-col md:items-center">
class="experimental-styles-within flex w-full flex-col-reverse gap-2 md:w-auto md:flex-col md:items-center"
>
<ButtonStyled color="standard" size="large"> <ButtonStyled color="standard" size="large">
<button class="w-full md:w-fit" @click="selectProduct('custom')"> <button class="w-full md:w-fit" @click="selectProduct('custom')">
{{ formatMessage(messages.getStartedButton) }} {{ formatMessage(messages.getStartedButton) }}

View File

@@ -208,7 +208,7 @@
<p> <p>
<IntlFormatted :message-id="messages.playWithLauncherDescription"> <IntlFormatted :message-id="messages.playWithLauncherDescription">
<template #link="{ children }"> <template #link="{ children }">
<nuxt-link class="title-link" to="/app"> <nuxt-link class="underline hover:brightness-[--hover-brightness]" to="/app">
<component :is="() => children" /> <component :is="() => children" />
</nuxt-link> </nuxt-link>
</template> </template>

View File

@@ -1,7 +1,5 @@
<template> <template>
<div <div class="relative mx-auto mb-6 flex min-h-screen w-full max-w-[1280px] flex-col px-6">
class="experimental-styles-within relative mx-auto mb-6 flex min-h-screen w-full max-w-[1280px] flex-col px-6"
>
<h1>Moderation</h1> <h1>Moderation</h1>
<NavTabs :links="moderationLinks" class="mb-4 hidden sm:flex" /> <NavTabs :links="moderationLinks" class="mb-4 hidden sm:flex" />
<div class="mb-4 sm:hidden"> <div class="mb-4 sm:hidden">

View File

@@ -57,7 +57,7 @@
</Combobox> </Combobox>
</div> </div>
<ButtonStyled color="orange" class="w-full sm:w-auto"> <ButtonStyled color="orange">
<button <button
class="flex !h-[40px] w-full items-center justify-center gap-2 sm:w-auto" class="flex !h-[40px] w-full items-center justify-center gap-2 sm:w-auto"
:disabled="paginatedProjects?.length === 0" :disabled="paginatedProjects?.length === 0"

View File

@@ -108,7 +108,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="page experimental-styles-within py-6"> <div class="page py-6">
<div <div
class="flex flex-wrap items-center justify-between gap-4 border-0 border-b-[1px] border-solid border-divider px-6 pb-6" class="flex flex-wrap items-center justify-between gap-4 border-0 border-b-[1px] border-solid border-divider px-6 pb-6"
> >
@@ -122,7 +122,7 @@ onMounted(() => {
<RssIcon /> <RssIcon />
</a> </a>
</ButtonStyled> </ButtonStyled>
<ButtonStyled circular icon-only> <ButtonStyled circular>
<a v-tooltip="`Changelog`" href="/news/changelog" aria-label="Changelog"> <a v-tooltip="`Changelog`" href="/news/changelog" aria-label="Changelog">
<GitGraphIcon /> <GitGraphIcon />
</a> </a>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="page experimental-styles-within"> <div class="page">
<h1 class="m-0 text-3xl font-extrabold">Changelog</h1> <h1 class="m-0 text-3xl font-extrabold">Changelog</h1>
<p class="my-3">Keep up-to-date on what's new with Modrinth.</p> <p class="my-3">Keep up-to-date on what's new with Modrinth.</p>
<NuxtPage /> <NuxtPage />

View File

@@ -41,7 +41,7 @@ useSeoMeta({
</script> </script>
<template> <template>
<div class="page experimental-styles-within py-6"> <div class="page py-6">
<div class="flex flex-wrap items-center justify-between gap-4 px-6"> <div class="flex flex-wrap items-center justify-between gap-4 px-6">
<div> <div>
<h1 class="m-0 text-3xl font-extrabold">News</h1> <h1 class="m-0 text-3xl font-extrabold">News</h1>
@@ -53,7 +53,7 @@ useSeoMeta({
<RssIcon /> <RssIcon />
</a> </a>
</ButtonStyled> </ButtonStyled>
<ButtonStyled circular icon-only> <ButtonStyled circular>
<a v-tooltip="`Changelog`" href="/news/changelog" aria-label="Changelog"> <a v-tooltip="`Changelog`" href="/news/changelog" aria-label="Changelog">
<GitGraphIcon /> <GitGraphIcon />
</a> </a>

View File

@@ -4,7 +4,7 @@
</div> </div>
<div <div
v-else-if="organization" v-else-if="organization"
class="experimental-styles-within new-page sidebar" class="new-page sidebar"
:class="{ 'alt-layout': cosmetics.leftContentLayout || routeHasSettings }" :class="{ 'alt-layout': cosmetics.leftContentLayout || routeHasSettings }"
> >
<ModalCreation ref="modal_creation" :organization-id="organization.id" /> <ModalCreation ref="modal_creation" :organization-id="organization.id" />
@@ -176,14 +176,18 @@
<h2>Invitation to join {{ organization.name }}</h2> <h2>Invitation to join {{ organization.name }}</h2>
<p>You have been invited to join {{ organization.name }}.</p> <p>You have been invited to join {{ organization.name }}.</p>
<div class="input-group"> <div class="input-group">
<button class="iconified-button brand-button" @click="onAcceptInvite"> <ButtonStyled color="brand">
<button @click="onAcceptInvite">
<CheckIcon /> <CheckIcon />
Accept Accept
</button> </button>
<button class="iconified-button danger-button" @click="onDeclineInvite"> </ButtonStyled>
<ButtonStyled color="red">
<button @click="onDeclineInvite">
<XIcon /> <XIcon />
Decline Decline
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto"> <div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">

View File

@@ -2,7 +2,7 @@
import { TrashIcon, UploadIcon } from '@modrinth/assets' import { TrashIcon, UploadIcon } from '@modrinth/assets'
import { import {
Avatar, Avatar,
Button, ButtonStyled,
ConfirmModal, ConfirmModal,
FileInput, FileInput,
injectNotificationManager, injectNotificationManager,
@@ -161,27 +161,27 @@ const onDeleteOrganization = useClientTry(async () => {
size="md" size="md"
class="project__icon" class="project__icon"
/> />
<div class="input-stack"> <div class="flex flex-col gap-2">
<ButtonStyled>
<FileInput <FileInput
id="project-icon" id="project-icon"
:max-size="262144" :max-size="262144"
:show-icon="true" :show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp" accept="image/png,image/jpeg,image/gif,image/webp"
class="btn" class="button-like"
prompt="Upload icon" prompt="Upload icon"
:disabled="!hasPermission" :disabled="!hasPermission"
@change="showPreviewImage" @change="showPreviewImage"
> >
<UploadIcon /> <UploadIcon />
</FileInput> </FileInput>
<Button </ButtonStyled>
v-if="!deletedIcon && (previewImage || organization.icon_url)" <ButtonStyled v-if="!deletedIcon && (previewImage || organization.icon_url)">
:disabled="!hasPermission" <button :disabled="!hasPermission" @click="markIconForDeletion">
@click="markIconForDeletion"
>
<TrashIcon /> <TrashIcon />
Remove icon Remove icon
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
@@ -231,10 +231,12 @@ const onDeleteOrganization = useClientTry(async () => {
Deleting your organization will transfer all of its projects to the organization owner. This Deleting your organization will transfer all of its projects to the organization owner. This
action cannot be undone. action cannot be undone.
</p> </p>
<Button color="danger" @click="() => $refs.modal_deletion.show()"> <ButtonStyled color="red">
<button @click="() => $refs.modal_deletion.show()">
<TrashIcon /> <TrashIcon />
Delete organization Delete organization
</Button> </button>
</ButtonStyled>
</div> </div>
<UnsavedChangesPopup <UnsavedChangesPopup
:original="originalState" :original="originalState"

View File

@@ -43,8 +43,8 @@
@keypress.enter="() => onInviteTeamMember(organization.team, currentUsername)" @keypress.enter="() => onInviteTeamMember(organization.team, currentUsername)"
/> />
<label for="username" class="hidden">Username</label> <label for="username" class="hidden">Username</label>
<Button <ButtonStyled color="brand">
color="primary" <button
:disabled=" :disabled="
!isPermission( !isPermission(
currentMember.organization_permissions, currentMember.organization_permissions,
@@ -55,7 +55,8 @@
> >
<UserPlusIcon /> <UserPlusIcon />
Invite Invite
</Button> </button>
</ButtonStyled>
</div> </div>
<div class="adjacent-input"> <div class="adjacent-input">
<span class="label"> <span class="label">
@@ -64,14 +65,15 @@
Remove yourself as a member of this organization. Remove yourself as a member of this organization.
</span> </span>
</span> </span>
<Button <ButtonStyled color="red">
color="danger" <button
:disabled="currentMember.is_owner" :disabled="currentMember.is_owner"
@click="() => onLeaveProject(organization.team_id, auth.user.id)" @click="() => onLeaveProject(organization.team_id, auth.user.id)"
> >
<UserRemoveIcon /> <UserRemoveIcon />
Leave organization Leave organization
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div <div
@@ -94,9 +96,8 @@
<div class="side-buttons"> <div class="side-buttons">
<Badge v-if="member.accepted" type="accepted" /> <Badge v-if="member.accepted" type="accepted" />
<Badge v-else type="pending" /> <Badge v-else type="pending" />
<Button <ButtonStyled circular type="transparent">
icon-only <button
transparent
class="dropdown-icon" class="dropdown-icon"
@click=" @click="
openTeamMembers.indexOf(member.user.id) === -1 openTeamMembers.indexOf(member.user.id) === -1
@@ -105,7 +106,8 @@
" "
> >
<DropdownIcon /> <DropdownIcon />
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
@@ -188,8 +190,8 @@
</div> </div>
</template> </template>
<div class="input-group"> <div class="input-group">
<Button <ButtonStyled color="brand">
color="primary" <button
:disabled=" :disabled="
!isPermission( !isPermission(
currentMember.organization_permissions, currentMember.organization_permissions,
@@ -200,10 +202,10 @@
> >
<SaveIcon /> <SaveIcon />
Save changes Save changes
</Button> </button>
<Button </ButtonStyled>
v-if="!member.is_owner" <ButtonStyled v-if="!member.is_owner" color="red">
color="danger" <button
:disabled=" :disabled="
!isPermission( !isPermission(
currentMember.organization_permissions, currentMember.organization_permissions,
@@ -218,14 +220,14 @@
> >
<UserRemoveIcon /> <UserRemoveIcon />
Remove member Remove member
</Button> </button>
<Button </ButtonStyled>
v-if="!member.is_owner && currentMember.is_owner && member.accepted" <ButtonStyled v-if="!member.is_owner && currentMember.is_owner && member.accepted">
@click="(e) => openTransferModal(member, e)" <button @click="(e) => openTransferModal(member, e)">
>
<TransferIcon /> <TransferIcon />
Transfer ownership Transfer ownership
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -244,7 +246,7 @@ import {
import { import {
Avatar, Avatar,
Badge, Badge,
Button, ButtonStyled,
Checkbox, Checkbox,
injectNotificationManager, injectNotificationManager,
StyledInput, StyledInput,

View File

@@ -25,15 +25,17 @@
" "
:maxlength="2048" :maxlength="2048"
/> />
<ButtonStyled circular>
<button <button
v-tooltip="'Clear link'" v-tooltip="'Clear link'"
class="label-button"
aria-label="Clear link" aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.issues.clear" :data-active="editLinks.issues.clear"
@click="editLinks.issues.clear = !editLinks.issues.clear" @click="editLinks.issues.clear = !editLinks.issues.clear"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
<label <label
for="source-code-input" for="source-code-input"
@@ -52,15 +54,17 @@
editLinks.source.clear ? 'Existing link will be cleared' : 'Enter a valid URL' editLinks.source.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
" "
/> />
<ButtonStyled circular>
<button <button
v-tooltip="'Clear link'" v-tooltip="'Clear link'"
class="label-button"
aria-label="Clear link" aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.source.clear" :data-active="editLinks.source.clear"
@click="editLinks.source.clear = !editLinks.source.clear" @click="editLinks.source.clear = !editLinks.source.clear"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
<label <label
for="wiki-page-input" for="wiki-page-input"
@@ -79,15 +83,17 @@
editLinks.wiki.clear ? 'Existing link will be cleared' : 'Enter a valid URL' editLinks.wiki.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
" "
/> />
<ButtonStyled circular>
<button <button
v-tooltip="'Clear link'" v-tooltip="'Clear link'"
class="label-button"
aria-label="Clear link" aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.wiki.clear" :data-active="editLinks.wiki.clear"
@click="editLinks.wiki.clear = !editLinks.wiki.clear" @click="editLinks.wiki.clear = !editLinks.wiki.clear"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
<label for="discord-invite-input" title="An invitation link to your Discord server."> <label for="discord-invite-input" title="An invitation link to your Discord server.">
<span class="label__title">Discord invite</span> <span class="label__title">Discord invite</span>
@@ -105,15 +111,17 @@
: 'Enter a valid Discord invite URL' : 'Enter a valid Discord invite URL'
" "
/> />
<ButtonStyled circular>
<button <button
v-tooltip="'Clear link'" v-tooltip="'Clear link'"
class="label-button"
aria-label="Clear link" aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.discord.clear" :data-active="editLinks.discord.clear"
@click="editLinks.discord.clear = !editLinks.discord.clear" @click="editLinks.discord.clear = !editLinks.discord.clear"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
</ButtonStyled>
</div> </div>
</section> </section>
<p> <p>
@@ -143,14 +151,18 @@
description="Show all projects" description="Show all projects"
/> />
<div class="push-right input-group"> <div class="push-right input-group">
<button class="iconified-button" @click="$refs.editLinksModal.hide()"> <ButtonStyled>
<button @click="$refs.editLinksModal.hide()">
<XIcon /> <XIcon />
Cancel Cancel
</button> </button>
<button class="iconified-button brand-button" @click="onBulkEditLinks"> </ButtonStyled>
<ButtonStyled color="brand">
<button @click="onBulkEditLinks">
<SaveIcon /> <SaveIcon />
Save changes Save changes
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</NewModal> </NewModal>
@@ -159,10 +171,12 @@
<div class="header__row"> <div class="header__row">
<h2 class="header__title text-2xl">Projects</h2> <h2 class="header__title text-2xl">Projects</h2>
<div class="input-group"> <div class="input-group">
<button class="iconified-button brand-button" @click="$refs.modal_creation.show($event)"> <ButtonStyled color="brand">
<button @click="$refs.modal_creation.show($event)">
<PlusIcon /> <PlusIcon />
{{ formatMessage(commonMessages.createAProjectButton) }} {{ formatMessage(commonMessages.createAProjectButton) }}
</button> </button>
</ButtonStyled>
<OrganizationProjectTransferModal <OrganizationProjectTransferModal
:projects="usersOwnedProjects || []" :projects="usersOwnedProjects || []"
@submit="onProjectTransferSubmit" @submit="onProjectTransferSubmit"
@@ -175,14 +189,12 @@
<template v-else> <template v-else>
<p>You can edit multiple projects at once by selecting them below.</p> <p>You can edit multiple projects at once by selecting them below.</p>
<div class="input-group"> <div class="input-group">
<button <ButtonStyled>
class="iconified-button" <button :disabled="selectedProjects.length === 0" @click="$refs.editLinksModal.show()">
:disabled="selectedProjects.length === 0"
@click="$refs.editLinksModal.show()"
>
<EditIcon /> <EditIcon />
Edit links Edit links
</button> </button>
</ButtonStyled>
<div class="push-right"> <div class="push-right">
<div class="labeled-control-row"> <div class="labeled-control-row">
Sort by Sort by
@@ -193,14 +205,15 @@
:options="sortOptions" :options="sortOptions"
@change="sortedProjects = updateSort(sortedProjects, sortBy, descending)" @change="sortedProjects = updateSort(sortedProjects, sortBy, descending)"
/> />
<ButtonStyled circular>
<button <button
v-tooltip="descending ? 'Descending' : 'Ascending'" v-tooltip="descending ? 'Descending' : 'Ascending'"
class="square-button"
@click="updateDescending()" @click="updateDescending()"
> >
<SortDescIcon v-if="descending" /> <SortDescIcon v-if="descending" />
<SortAscIcon v-else /> <SortAscIcon v-else />
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -38,24 +38,24 @@
{{ calculateSavings(price.prices.intervals.monthly, price.prices.intervals.yearly) }}% with {{ calculateSavings(price.prices.intervals.monthly, price.prices.intervals.yearly) }}% with
annual billing! annual billing!
</p> </p>
<nuxt-link <ButtonStyled
v-if="auth.user && isPermission(auth.user.badges, 1 << 0)" v-if="auth.user && isPermission(auth.user.badges, 1 << 0)"
to="/settings/billing" color="purple"
class="btn btn-purple btn-large" size="large"
> >
<nuxt-link to="/settings/billing">
<SettingsIcon aria-hidden="true" /> <SettingsIcon aria-hidden="true" />
Manage subscription Manage subscription
</nuxt-link> </nuxt-link>
<button v-else-if="auth.user" class="btn btn-purple btn-large" @click="purchaseModal.show()"> </ButtonStyled>
Subscribe <ButtonStyled v-else-if="auth.user" color="purple" size="large">
</button> <button @click="purchaseModal.show()">Subscribe</button>
<nuxt-link </ButtonStyled>
v-else <ButtonStyled v-else color="purple" size="large">
:to="`/auth/sign-in?redirect=${encodeURIComponent('/plus?showModal=true')}`" <nuxt-link :to="`/auth/sign-in?redirect=${encodeURIComponent('/plus?showModal=true')}`">
class="btn btn-purple btn-large"
>
Subscribe Subscribe
</nuxt-link> </nuxt-link>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="perks-hero"> <div class="perks-hero">
@@ -86,7 +86,12 @@
</template> </template>
<script setup> <script setup>
import { HeartIcon, ModrinthPlusIcon, SettingsIcon, SparklesIcon, StarIcon } from '@modrinth/assets' import { HeartIcon, ModrinthPlusIcon, SettingsIcon, SparklesIcon, StarIcon } from '@modrinth/assets'
import { injectNotificationManager, PurchaseModal, useFormatPrice } from '@modrinth/ui' import {
ButtonStyled,
injectNotificationManager,
PurchaseModal,
useFormatPrice,
} from '@modrinth/ui'
import { calculateSavings, getCurrency } from '@modrinth/utils' import { calculateSavings, getCurrency } from '@modrinth/utils'
import { useBaseFetch } from '@/composables/fetch.js' import { useBaseFetch } from '@/composables/fetch.js'

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="page"> <div class="page">
<div class="experimental-styles-within flex flex-col gap-2"> <div class="flex flex-col gap-2">
<RadialHeader class="top-box mb-2 flex flex-col items-center justify-center" color="orange"> <RadialHeader class="top-box mb-2 flex flex-col items-center justify-center" color="orange">
<ScaleIcon class="h-12 w-12 text-brand-orange" /> <ScaleIcon class="h-12 w-12 text-brand-orange" />
<h1 class="m-3 gap-2 text-3xl font-extrabold"> <h1 class="m-3 gap-2 text-3xl font-extrabold">

View File

@@ -27,19 +27,18 @@
@keyup.enter="saveEmail()" @keyup.enter="saveEmail()"
/> />
<div class="input-group push-right"> <div class="input-group push-right">
<button class="iconified-button" @click="$refs.changeEmailModal.hide()"> <ButtonStyled>
<button @click="$refs.changeEmailModal.hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
<button </ButtonStyled>
type="button" <ButtonStyled color="brand">
class="iconified-button brand-button" <button :disabled="!email" @click="saveEmail()">
:disabled="!email"
@click="saveEmail()"
>
<SaveIcon /> <SaveIcon />
{{ formatMessage(messages.saveEmailButton) }} {{ formatMessage(messages.saveEmailButton) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -108,34 +107,32 @@
</template> </template>
<p></p> <p></p>
<div class="input-group push-right"> <div class="input-group push-right">
<button class="iconified-button" @click="$refs.managePasswordModal.hide()"> <ButtonStyled>
<button @click="$refs.managePasswordModal.hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
</ButtonStyled>
<template v-if="removePasswordMode"> <template v-if="removePasswordMode">
<button <ButtonStyled color="red">
type="button" <button :disabled="!oldPassword" @click="savePassword">
class="iconified-button danger-button"
:disabled="!oldPassword"
@click="savePassword"
>
<TrashIcon /> <TrashIcon />
{{ formatMessage(messages.removePasswordButton) }} {{ formatMessage(messages.removePasswordButton) }}
</button> </button>
</ButtonStyled>
</template> </template>
<template v-else> <template v-else>
<button <ButtonStyled
v-if="auth.user.has_password && auth.user.auth_providers.length > 0" v-if="auth.user.has_password && auth.user.auth_providers.length > 0"
type="button" color="red"
class="iconified-button danger-button"
@click="removePasswordMode = true"
> >
<button @click="removePasswordMode = true">
<TrashIcon /> <TrashIcon />
{{ formatMessage(messages.removePasswordButton) }} {{ formatMessage(messages.removePasswordButton) }}
</button> </button>
</ButtonStyled>
<ButtonStyled color="brand">
<button <button
type="button"
class="iconified-button brand-button"
:disabled=" :disabled="
newPassword.length == 0 || newPassword.length == 0 ||
(auth.user.has_password && oldPassword.length == 0) || (auth.user.has_password && oldPassword.length == 0) ||
@@ -146,6 +143,7 @@
<SaveIcon /> <SaveIcon />
{{ formatMessage(messages.savePasswordButton) }} {{ formatMessage(messages.savePasswordButton) }}
</button> </button>
</ButtonStyled>
</template> </template>
</div> </div>
</div> </div>
@@ -173,14 +171,18 @@
{{ formatMessage(messages.twoFactorIncorrectError) }} {{ formatMessage(messages.twoFactorIncorrectError) }}
</p> </p>
<div class="input-group push-right"> <div class="input-group push-right">
<button class="iconified-button" @click="$refs.manageTwoFactorModal.hide()"> <ButtonStyled>
<button @click="$refs.manageTwoFactorModal.hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
<button class="iconified-button danger-button" @click="removeTwoFactor"> </ButtonStyled>
<ButtonStyled color="red">
<button @click="removeTwoFactor">
<TrashIcon /> <TrashIcon />
{{ formatMessage(messages.twoFactorRemoveButton) }} {{ formatMessage(messages.twoFactorRemoveButton) }}
</button> </button>
</ButtonStyled>
</div> </div>
</template> </template>
<template v-else> <template v-else>
@@ -247,34 +249,30 @@
</ul> </ul>
</template> </template>
<div class="input-group push-right"> <div class="input-group push-right">
<button v-if="twoFactorStep === 1" class="iconified-button" @click="twoFactorStep = 0"> <ButtonStyled v-if="twoFactorStep === 1">
<button @click="twoFactorStep = 0">
<LeftArrowIcon /> <LeftArrowIcon />
{{ formatMessage(commonMessages.backButton) }} {{ formatMessage(commonMessages.backButton) }}
</button> </button>
<button </ButtonStyled>
v-if="twoFactorStep !== 2" <ButtonStyled v-if="twoFactorStep !== 2">
class="iconified-button" <button @click="$refs.manageTwoFactorModal.hide()">
@click="$refs.manageTwoFactorModal.hide()"
>
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
<button </ButtonStyled>
v-if="twoFactorStep <= 1" <ButtonStyled v-if="twoFactorStep <= 1" color="brand">
class="iconified-button brand-button" <button @click="twoFactorStep === 1 ? verifyTwoFactorCode() : (twoFactorStep = 1)">
@click="twoFactorStep === 1 ? verifyTwoFactorCode() : (twoFactorStep = 1)"
>
<RightArrowIcon /> <RightArrowIcon />
{{ formatMessage(commonMessages.continueButton) }} {{ formatMessage(commonMessages.continueButton) }}
</button> </button>
<button </ButtonStyled>
v-if="twoFactorStep === 2" <ButtonStyled v-if="twoFactorStep === 2" color="brand">
class="iconified-button brand-button" <button @click="$refs.manageTwoFactorModal.hide()">
@click="$refs.manageTwoFactorModal.hide()"
>
<CheckIcon /> <CheckIcon />
{{ formatMessage(messages.completeSetupButton) }} {{ formatMessage(messages.completeSetupButton) }}
</button> </button>
</ButtonStyled>
</div> </div>
</template> </template>
</div> </div>
@@ -295,29 +293,27 @@
<span><component :is="provider.icon" /> {{ provider.display }}</span> <span><component :is="provider.icon" /> {{ provider.display }}</span>
</div> </div>
<div class="table-text manage table-cell"> <div class="table-text manage table-cell">
<button <ButtonStyled v-if="auth.user.auth_providers.includes(provider.id)">
v-if="auth.user.auth_providers.includes(provider.id)" <button @click="handleRemoveAuthProvider(provider.id)">
class="btn"
@click="handleRemoveAuthProvider(provider.id)"
>
<TrashIcon /> {{ formatMessage(commonMessages.removeButton) }} <TrashIcon /> {{ formatMessage(commonMessages.removeButton) }}
</button> </button>
<a </ButtonStyled>
v-else <ButtonStyled v-else>
class="btn" <a :href="`${getAuthUrl(provider.id, '/settings/account')}&token=${auth.token}`">
:href="`${getAuthUrl(provider.id, '/settings/account')}&token=${auth.token}`"
>
<ExternalIcon /> {{ formatMessage(messages.providerAddButton) }} <ExternalIcon /> {{ formatMessage(messages.providerAddButton) }}
</a> </a>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
<p></p> <p></p>
<div class="input-group push-right"> <div class="input-group push-right">
<button class="iconified-button" @click="$refs.manageProvidersModal.hide()"> <ButtonStyled>
<button @click="$refs.manageProvidersModal.hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.closeButton) }} {{ formatMessage(commonMessages.closeButton) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -332,7 +328,8 @@
}}</span> }}</span>
</label> </label>
<div> <div>
<button class="iconified-button" @click="$refs.changeEmailModal.show()"> <ButtonStyled>
<button @click="$refs.changeEmailModal.show()">
<template v-if="auth.user.email"> <template v-if="auth.user.email">
<EditIcon /> <EditIcon />
{{ formatMessage(messages.changeEmailButton) }} {{ formatMessage(messages.changeEmailButton) }}
@@ -342,6 +339,7 @@
{{ formatMessage(messages.addEmailButton) }} {{ formatMessage(messages.addEmailButton) }}
</template> </template>
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="adjacent-input"> <div class="adjacent-input">
@@ -359,8 +357,8 @@
</span> </span>
</label> </label>
<div> <div>
<ButtonStyled>
<button <button
class="iconified-button"
@click=" @click="
() => { () => {
oldPassword = '' oldPassword = ''
@@ -377,6 +375,7 @@
}}</template> }}</template>
<template v-else> {{ formatMessage(messages.addPasswordButton) }} </template> <template v-else> {{ formatMessage(messages.addPasswordButton) }} </template>
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="adjacent-input"> <div class="adjacent-input">
@@ -387,7 +386,8 @@
}}</span> }}</span>
</label> </label>
<div> <div>
<button class="iconified-button" @click="showTwoFactorModal"> <ButtonStyled>
<button @click="showTwoFactorModal">
<template v-if="auth.user.has_totp"> <template v-if="auth.user.has_totp">
<TrashIcon /> {{ formatMessage(messages.twoFactorRemoveButton) }} <TrashIcon /> {{ formatMessage(messages.twoFactorRemoveButton) }}
</template> </template>
@@ -395,6 +395,7 @@
<PlusIcon /> {{ formatMessage(messages.twoFactorSetupButton) }} <PlusIcon /> {{ formatMessage(messages.twoFactorSetupButton) }}
</template> </template>
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="adjacent-input"> <div class="adjacent-input">
@@ -405,9 +406,11 @@
}}</span> }}</span>
</label> </label>
<div> <div>
<button class="iconified-button" @click="$refs.manageProvidersModal.show()"> <ButtonStyled>
<button @click="$refs.manageProvidersModal.show()">
<SettingsIcon /> {{ formatMessage(messages.manageProvidersButton) }} <SettingsIcon /> {{ formatMessage(messages.manageProvidersButton) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</section> </section>
@@ -415,11 +418,14 @@
<section id="data-export" class="universal-card"> <section id="data-export" class="universal-card">
<h2>{{ formatMessage(messages.dataExportTitle) }}</h2> <h2>{{ formatMessage(messages.dataExportTitle) }}</h2>
<p>{{ formatMessage(messages.dataExportDescription) }}</p> <p>{{ formatMessage(messages.dataExportDescription) }}</p>
<a v-if="generated" class="iconified-button" :href="generated" download="export.json"> <ButtonStyled v-if="generated">
<a :href="generated" download="export.json">
<DownloadIcon /> <DownloadIcon />
{{ formatMessage(messages.downloadExportButton) }} {{ formatMessage(messages.downloadExportButton) }}
</a> </a>
<button v-else class="iconified-button" :disabled="generatingExport" @click="exportData"> </ButtonStyled>
<ButtonStyled v-else>
<button :disabled="generatingExport" @click="exportData">
<template v-if="generatingExport"> <template v-if="generatingExport">
<UpdatedIcon /> {{ formatMessage(messages.generatingExportButton) }} <UpdatedIcon /> {{ formatMessage(messages.generatingExportButton) }}
</template> </template>
@@ -427,19 +433,18 @@
<UpdatedIcon /> {{ formatMessage(messages.generateExportButton) }} <UpdatedIcon /> {{ formatMessage(messages.generateExportButton) }}
</template> </template>
</button> </button>
</ButtonStyled>
</section> </section>
<section id="delete-account" class="universal-card"> <section id="delete-account" class="universal-card">
<h2>{{ formatMessage(messages.deleteAccountSectionTitle) }}</h2> <h2>{{ formatMessage(messages.deleteAccountSectionTitle) }}</h2>
<p>{{ formatMessage(messages.deleteAccountSectionDescription) }}</p> <p>{{ formatMessage(messages.deleteAccountSectionDescription) }}</p>
<button <ButtonStyled color="red">
type="button" <button type="button" @click="$refs.modal_confirm.show()">
class="iconified-button danger-button"
@click="$refs.modal_confirm.show()"
>
<TrashIcon /> <TrashIcon />
{{ formatMessage(messages.deleteAccountButton) }} {{ formatMessage(messages.deleteAccountButton) }}
</button> </button>
</ButtonStyled>
</section> </section>
</div> </div>
</template> </template>
@@ -460,6 +465,7 @@ import {
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
ButtonStyled,
commonMessages, commonMessages,
ConfirmModal, ConfirmModal,
defineMessages, defineMessages,

View File

@@ -24,15 +24,17 @@
</label> </label>
<div v-if="editingId" class="icon-submission"> <div v-if="editingId" class="icon-submission">
<Avatar size="md" :src="icon" /> <Avatar size="md" :src="icon" />
<ButtonStyled>
<FileInput <FileInput
:max-size="262144" :max-size="262144"
class="btn" class="button-like"
:prompt="formatMessage(messages.uploadIcon)" :prompt="formatMessage(messages.uploadIcon)"
accept="image/png,image/jpeg,image/gif,image/webp" accept="image/png,image/jpeg,image/gif,image/webp"
@change="onImageSelection" @change="onImageSelection"
> >
<UploadIcon /> <UploadIcon />
</FileInput> </FileInput>
</ButtonStyled>
</div> </div>
<label v-if="editingId" for="app-url"> <label v-if="editingId" for="app-url">
<span class="label__title">{{ formatMessage(messages.urlLabel) }}</span> <span class="label__title">{{ formatMessage(messages.urlLabel) }}</span>
@@ -94,51 +96,46 @@
autocomplete="off" autocomplete="off"
:placeholder="formatMessage(messages.redirectUriPlaceholder)" :placeholder="formatMessage(messages.redirectUriPlaceholder)"
/> />
<Button v-if="index !== 0" icon-only @click="() => redirectUris.splice(index, 1)"> <ButtonStyled v-if="index !== 0" circular>
<button @click="() => redirectUris.splice(index, 1)">
<TrashIcon /> <TrashIcon />
</Button> </button>
<Button </ButtonStyled>
v-if="index === 0" <ButtonStyled v-if="index === 0" color="brand">
color="primary" <button @click="() => redirectUris.push('')">
icon-only
@click="() => redirectUris.push('')"
>
<PlusIcon /> {{ formatMessage(messages.addMore) }} <PlusIcon /> {{ formatMessage(messages.addMore) }}
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div v-if="redirectUris.length <= 0"> <div v-if="redirectUris.length <= 0">
<Button color="primary" icon-only @click="() => redirectUris.push('')"> <ButtonStyled color="brand">
<button @click="() => redirectUris.push('')">
<PlusIcon /> {{ formatMessage(messages.addRedirectUri) }} <PlusIcon /> {{ formatMessage(messages.addRedirectUri) }}
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="submit-row input-group push-right"> <div class="submit-row input-group push-right">
<button class="iconified-button" @click="$refs.appModal.hide()"> <ButtonStyled>
<button @click="$refs.appModal.hide()">
<XIcon /> <XIcon />
{{ formatMessage(messages.cancel) }} {{ formatMessage(messages.cancel) }}
</button> </button>
<button </ButtonStyled>
v-if="editingId" <ButtonStyled v-if="editingId" color="brand">
:disabled="!canSubmit" <button :disabled="!canSubmit" @click="editApp">
type="button"
class="iconified-button brand-button"
@click="editApp"
>
<SaveIcon /> <SaveIcon />
{{ formatMessage(messages.saveChanges) }} {{ formatMessage(messages.saveChanges) }}
</button> </button>
<button </ButtonStyled>
v-else <ButtonStyled v-else color="brand">
:disabled="!canSubmit" <button :disabled="!canSubmit" @click="createApp">
type="button"
class="iconified-button brand-button"
@click="createApp"
>
<PlusIcon /> <PlusIcon />
{{ formatMessage(messages.createApp) }} {{ formatMessage(messages.createApp) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -147,8 +144,8 @@
<div class="header__title"> <div class="header__title">
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.applications) }}</h2> <h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.applications) }}</h2>
</div> </div>
<ButtonStyled color="brand">
<button <button
class="btn btn-primary"
@click=" @click="
() => { () => {
name = null name = null
@@ -156,13 +153,13 @@
scopesVal = 0 scopesVal = 0
redirectUris = [''] redirectUris = ['']
editingId = null editingId = null
expires = null
$refs.appModal.show() $refs.appModal.show()
} }
" "
> >
<PlusIcon /> {{ formatMessage(messages.newApplication) }} <PlusIcon /> {{ formatMessage(messages.newApplication) }}
</button> </button>
</ButtonStyled>
</div> </div>
<p> <p>
<IntlFormatted :message-id="messages.descriptionIntro"> <IntlFormatted :message-id="messages.descriptionIntro">
@@ -210,8 +207,8 @@
</div> </div>
</div> </div>
<div class="input-group"> <div class="input-group">
<Button <ButtonStyled>
icon-only <button
@click=" @click="
() => { () => {
setForm({ setForm({
@@ -224,10 +221,10 @@
> >
<EditIcon /> <EditIcon />
{{ formatMessage(messages.edit) }} {{ formatMessage(messages.edit) }}
</Button> </button>
<Button </ButtonStyled>
color="danger" <ButtonStyled color="red">
icon-only <button
@click=" @click="
() => { () => {
editingId = app.id editingId = app.id
@@ -237,7 +234,8 @@
> >
<TrashIcon /> <TrashIcon />
{{ formatMessage(messages.delete) }} {{ formatMessage(messages.delete) }}
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -246,7 +244,7 @@
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, UploadIcon, XIcon } from '@modrinth/assets' import { EditIcon, PlusIcon, SaveIcon, TrashIcon, UploadIcon, XIcon } from '@modrinth/assets'
import { import {
Avatar, Avatar,
Button, ButtonStyled,
Checkbox, Checkbox,
commonMessages, commonMessages,
commonSettingsMessages, commonSettingsMessages,

View File

@@ -69,9 +69,8 @@
</div> </div>
<div class="input-group"> <div class="input-group">
<Button <ButtonStyled color="red">
color="danger" <button
icon-only
@click=" @click="
() => { () => {
revokingId = authorization.app_id revokingId = authorization.app_id
@@ -81,7 +80,8 @@
> >
<TrashIcon /> <TrashIcon />
{{ formatMessage(messages.revokeAction) }} {{ formatMessage(messages.revokeAction) }}
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -90,7 +90,7 @@
import { CheckIcon, TrashIcon } from '@modrinth/assets' import { CheckIcon, TrashIcon } from '@modrinth/assets'
import { import {
Avatar, Avatar,
Button, ButtonStyled,
commonMessages, commonMessages,
commonSettingsMessages, commonSettingsMessages,
ConfirmModal, ConfirmModal,

View File

@@ -1,7 +1,7 @@
<template> <template>
<ServersUpgradeModalWrapper ref="upgradeModal" /> <ServersUpgradeModalWrapper ref="upgradeModal" />
<ResubscribeModal ref="pyroResubscribeModal" @resubscribe="handlePyroResubscribeConfirm" /> <ResubscribeModal ref="pyroResubscribeModal" @resubscribe="handlePyroResubscribeConfirm" />
<section class="universal-card experimental-styles-within"> <section class="universal-card">
<h2>{{ formatMessage(messages.subscriptionTitle) }}</h2> <h2>{{ formatMessage(messages.subscriptionTitle) }}</h2>
<p>{{ formatMessage(messages.subscriptionDescription) }}</p> <p>{{ formatMessage(messages.subscriptionDescription) }}</p>
<div class="universal-card recessed"> <div class="universal-card recessed">
@@ -533,7 +533,7 @@
</div> </div>
</section> </section>
<section class="universal-card experimental-styles-within"> <section class="universal-card">
<ConfirmModal <ConfirmModal
ref="modal_confirm" ref="modal_confirm"
:title="formatMessage(deleteModalMessages.title)" :title="formatMessage(deleteModalMessages.title)"
@@ -573,12 +573,16 @@
<div class="header__title"> <div class="header__title">
<h2 class="text-2xl">{{ formatMessage(messages.paymentMethodTitle) }}</h2> <h2 class="text-2xl">{{ formatMessage(messages.paymentMethodTitle) }}</h2>
</div> </div>
<nuxt-link class="btn" to="/settings/billing/charges"> <ButtonStyled>
<nuxt-link to="/settings/billing/charges">
<HistoryIcon /> {{ formatMessage(messages.paymentMethodHistory) }} <HistoryIcon /> {{ formatMessage(messages.paymentMethodHistory) }}
</nuxt-link> </nuxt-link>
<button class="btn" @click="addPaymentMethod"> </ButtonStyled>
<ButtonStyled>
<button @click="addPaymentMethod">
<PlusIcon /> {{ formatMessage(messages.paymentMethodAdd) }} <PlusIcon /> {{ formatMessage(messages.paymentMethodAdd) }}
</button> </button>
</ButtonStyled>
</div> </div>
<div <div
v-if="!paymentMethods || paymentMethods.length === 0" v-if="!paymentMethods || paymentMethods.length === 0"
@@ -637,9 +641,10 @@
</div> </div>
</div> </div>
</div> </div>
<ButtonStyled circular type="transparent">
<OverflowMenu <OverflowMenu
:dropdown-id="`${baseId}-payment-method-overflow-${index}`" :dropdown-id="`${baseId}-payment-method-overflow-${index}`"
class="btn icon-only transparent" class="btn-dropdown-animation !w-10"
:options=" :options="
[ [
{ {
@@ -672,6 +677,7 @@
{{ formatMessage(commonMessages.deleteLabel) }} {{ formatMessage(commonMessages.deleteLabel) }}
</template> </template>
</OverflowMenu> </OverflowMenu>
</ButtonStyled>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -9,9 +9,11 @@
</strong> </strong>
</template> </template>
</IntlFormatted> </IntlFormatted>
<Button :action="() => disableDeveloperMode()"> <ButtonStyled color="red" type="highlight">
<button class="mt-3" @click="disableDeveloperMode()">
{{ formatMessage(developerModeBanner.deactivate) }} {{ formatMessage(developerModeBanner.deactivate) }}
</Button> </button>
</ButtonStyled>
</MessageBanner> </MessageBanner>
<section class="universal-card"> <section class="universal-card">
<h2 class="text-2xl">{{ formatMessage(colorTheme.title) }}</h2> <h2 class="text-2xl">{{ formatMessage(colorTheme.title) }}</h2>
@@ -178,7 +180,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets' import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
import { import {
Button, ButtonStyled,
defineMessages, defineMessages,
injectNotificationManager, injectNotificationManager,
IntlFormatted, IntlFormatted,
@@ -506,9 +508,5 @@ const listTypes = computed(() => {
margin-bottom: 2px; margin-bottom: 2px;
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.btn {
margin-top: var(--gap-sm);
}
} }
</style> </style>

View File

@@ -66,31 +66,25 @@
<p></p> <p></p>
</div> </div>
<div class="input-group push-right"> <div class="ml-auto flex gap-2">
<button class="iconified-button" @click="$refs.patModal.hide()"> <ButtonStyled type="outlined">
<button @click="$refs.patModal.hide()">
<XIcon /> <XIcon />
{{ formatMessage(commonMessages.cancelButton) }} {{ formatMessage(commonMessages.cancelButton) }}
</button> </button>
<button </ButtonStyled>
v-if="editPatId !== null" <ButtonStyled v-if="editPatId !== null" color="brand">
:disabled="loading || !name || !expires" <button :disabled="loading || !name || !expires" @click="editPat">
type="button"
class="iconified-button brand-button"
@click="editPat"
>
<SaveIcon /> <SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }} {{ formatMessage(commonMessages.saveChangesButton) }}
</button> </button>
<button </ButtonStyled>
v-else <ButtonStyled v-else color="brand">
:disabled="loading || !name || !expires" <button :disabled="loading || !name || !expires" @click="createPat">
type="button"
class="iconified-button brand-button"
@click="createPat"
>
<PlusIcon /> <PlusIcon />
{{ formatMessage(createModalMessages.action) }} {{ formatMessage(createModalMessages.action) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</NewModal> </NewModal>
@@ -99,8 +93,8 @@
<div class="header__title"> <div class="header__title">
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.pats) }}</h2> <h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.pats) }}</h2>
</div> </div>
<ButtonStyled color="brand">
<button <button
class="btn btn-primary"
@click=" @click="
() => { () => {
name = null name = null
@@ -113,6 +107,7 @@
> >
<PlusIcon /> {{ formatMessage(messages.create) }} <PlusIcon /> {{ formatMessage(messages.create) }}
</button> </button>
</ButtonStyled>
</div> </div>
<p> <p>
<IntlFormatted :message-id="messages.description"> <IntlFormatted :message-id="messages.description">
@@ -172,8 +167,8 @@
</div> </div>
</div> </div>
<div class="token-actions ml-auto flex flex-col gap-2"> <div class="token-actions ml-auto flex flex-col gap-2">
<ButtonStyled>
<button <button
class="iconified-button raised-button"
@click=" @click="
() => { () => {
editPatId = pat.id editPatId = pat.id
@@ -186,8 +181,9 @@
> >
<EditIcon /> {{ formatMessage(tokenMessages.edit) }} <EditIcon /> {{ formatMessage(tokenMessages.edit) }}
</button> </button>
</ButtonStyled>
<ButtonStyled>
<button <button
class="iconified-button raised-button"
@click=" @click="
() => { () => {
deletePatIndex = pat.id deletePatIndex = pat.id
@@ -197,6 +193,7 @@
> >
<TrashIcon /> {{ formatMessage(tokenMessages.revoke) }} <TrashIcon /> {{ formatMessage(tokenMessages.revoke) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -204,6 +201,7 @@
<script setup> <script setup>
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, XIcon } from '@modrinth/assets' import { EditIcon, PlusIcon, SaveIcon, TrashIcon, XIcon } from '@modrinth/assets'
import { import {
ButtonStyled,
Checkbox, Checkbox,
commonMessages, commonMessages,
commonSettingsMessages, commonSettingsMessages,

View File

@@ -21,24 +21,28 @@
circle circle
:alt="auth.user.username" :alt="auth.user.username"
/> />
<div class="input-stack"> <div class="flex flex-col gap-2">
<ButtonStyled>
<FileInput <FileInput
:max-size="262144" :max-size="262144"
:show-icon="true" :show-icon="true"
class="btn" class="button-like"
:prompt="formatMessage(commonMessages.uploadImageButton)" :prompt="formatMessage(commonMessages.uploadImageButton)"
accept="image/png,image/jpeg,image/gif,image/webp" accept="image/png,image/jpeg,image/gif,image/webp"
@change="showPreviewImage" @change="showPreviewImage"
> >
<UploadIcon /> <UploadIcon />
</FileInput> </FileInput>
<Button v-if="avatarUrl !== null" :action="removePreviewImage"> </ButtonStyled>
<ButtonStyled v-if="avatarUrl !== null">
<button @click="removePreviewImage">
<TrashIcon /> <TrashIcon />
{{ formatMessage(commonMessages.removeImageButton) }} {{ formatMessage(commonMessages.removeImageButton) }}
</Button> </button>
<Button </ButtonStyled>
v-if="previewImage" <ButtonStyled v-if="previewImage">
:action=" <button
@click="
() => { () => {
icon = null icon = null
previewImage = null previewImage = null
@@ -47,7 +51,8 @@
> >
<UndoIcon /> <UndoIcon />
{{ formatMessage(commonMessages.resetButton) }} {{ formatMessage(commonMessages.resetButton) }}
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<label for="username-field"> <label for="username-field">
@@ -65,9 +70,11 @@
</label> </label>
<StyledInput id="bio-field" v-model="current.bio" multiline /> <StyledInput id="bio-field" v-model="current.bio" multiline />
<div class="input-group mt-4"> <div class="input-group mt-4">
<Button :link="`/user/${auth.user.username}`"> <ButtonStyled>
<NuxtLink :to="`/user/${auth.user.username}`">
<UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }} <UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }}
</Button> </NuxtLink>
</ButtonStyled>
</div> </div>
</section> </section>
<UnsavedChangesPopup <UnsavedChangesPopup
@@ -84,7 +91,7 @@
import { TrashIcon, UndoIcon, UploadIcon, UserIcon } from '@modrinth/assets' import { TrashIcon, UndoIcon, UploadIcon, UserIcon } from '@modrinth/assets'
import { import {
Avatar, Avatar,
Button, ButtonStyled,
commonMessages, commonMessages,
defineMessages, defineMessages,
FileInput, FileInput,

View File

@@ -34,9 +34,11 @@
</div> </div>
<div class="input-group"> <div class="input-group">
<i v-if="session.current">{{ formatMessage(messages.currentSessionLabel) }}</i> <i v-if="session.current">{{ formatMessage(messages.currentSessionLabel) }}</i>
<button v-else class="iconified-button raised-button" @click="revokeSession(session.id)"> <ButtonStyled v-else>
<button @click="revokeSession(session.id)">
<XIcon /> {{ formatMessage(messages.revokeSessionButton) }} <XIcon /> {{ formatMessage(messages.revokeSessionButton) }}
</button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
</div> </div>
@@ -44,6 +46,7 @@
<script setup> <script setup>
import { XIcon } from '@modrinth/assets' import { XIcon } from '@modrinth/assets'
import { import {
ButtonStyled,
commonMessages, commonMessages,
commonSettingsMessages, commonSettingsMessages,
defineMessages, defineMessages,

View File

@@ -1,5 +1,5 @@
<template> <template>
<div v-if="user" class="experimental-styles-within"> <div v-if="user">
<ModalCreation ref="modal_creation" /> <ModalCreation ref="modal_creation" />
<CollectionCreateModal ref="modal_collection_creation" /> <CollectionCreateModal ref="modal_collection_creation" />
<NewModal ref="editRoleModal" header="Edit role"> <NewModal ref="editRoleModal" header="Edit role">

View File

@@ -41,10 +41,6 @@
.universal-body { .universal-body {
@extend .universal-labels; @extend .universal-labels;
.multiselect {
width: 15rem;
}
> :where(input + *, .input-group + *, .chips + *, .input-div + *) { > :where(input + *, .input-group + *, .chips + *, .input-div + *) {
margin-block-start: var(--gap-md); margin-block-start: var(--gap-md);
} }
@@ -162,10 +158,6 @@
max-width: 100%; max-width: 100%;
align-items: center; align-items: center;
.multiselect {
width: 15rem;
}
input { input {
flex-shrink: 2; flex-shrink: 2;
} }
@@ -189,20 +181,6 @@
margin-right: 0; margin-right: 0;
} }
.input-stack {
display: flex;
flex-direction: column;
> * {
margin-bottom: var(--gap-md);
}
> .multiselect {
width: unset;
height: inherit;
}
}
.standard-body { .standard-body {
:last-child { :last-child {
margin-bottom: 0; margin-bottom: 0;
@@ -1020,130 +998,6 @@ select {
filter: brightness(1.2); filter: brightness(1.2);
} }
.multiselect {
color: var(--color-base) !important;
outline: 2px solid transparent;
width: 100% !important;
.multiselect__input:focus-visible {
outline: none !important;
box-shadow: none !important;
padding: 0 !important;
min-height: 0 !important;
font-weight: normal !important;
margin-left: 0.5rem;
margin-bottom: 10px;
}
input {
background: transparent;
box-shadow: none;
border: none !important;
&:focus {
box-shadow: none;
}
}
input::placeholder {
color: var(--color-base);
}
.multiselect__tags {
border-radius: var(--radius-md);
background: var(--color-button-bg);
box-shadow: var(--shadow-inset-sm);
border: none;
cursor: pointer;
padding-left: 7px;
padding-top: 10px;
font-size: 1rem;
transition: background-color 0.1s ease-in-out;
&:active {
filter: brightness(1.25);
.multiselect__spinner {
filter: brightness(1.25);
}
}
.multiselect__single {
background: transparent;
}
.multiselect__tag {
border-radius: var(--radius-md);
color: var(--color-base);
background: transparent;
border: 2px solid var(--color-brand);
}
.multiselect__tag-icon {
background: transparent;
&:after {
color: var(--color-contrast);
}
}
.multiselect__placeholder {
color: var(--color-base);
margin-left: 0.5rem;
margin-bottom: 8px;
opacity: 0.6;
font-size: 1rem;
line-height: 16px;
}
}
.multiselect__content-wrapper {
background: var(--color-button-bg);
border: none;
overflow-x: hidden;
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
width: 100%;
.multiselect__element {
.multiselect__option--highlight {
background: var(--color-button-bg);
filter: brightness(1.25);
color: var(--color-contrast);
}
.multiselect__option--selected {
background: var(--color-brand);
font-weight: bold;
color: var(--color-accent-contrast);
}
}
}
.multiselect__spinner {
background: var(--color-button-bg);
&:active {
filter: brightness(1.25);
}
}
&.multiselect--disabled {
background: none;
.multiselect__current,
.multiselect__select {
background: none;
}
}
}
.multiselect--above .multiselect__content-wrapper {
border-top: none !important;
border-top-left-radius: var(--radius-md) !important;
border-top-right-radius: var(--radius-md) !important;
}
.preview-radio { .preview-radio {
width: 100% !important; width: 100% !important;
border-radius: var(--radius-md); border-radius: var(--radius-md);

View File

@@ -2,7 +2,7 @@
<img <img
v-if="src && !failed" v-if="src && !failed"
ref="img" ref="img"
class="`experimental-styles-within avatar shrink-0" class="avatar shrink-0"
:style="`--_size: ${cssSize}`" :style="`--_size: ${cssSize}`"
:class="{ :class="{
circle: circle, circle: circle,
@@ -18,7 +18,7 @@
/> />
<svg <svg
v-else v-else
class="`experimental-styles-within avatar shrink-0" class="avatar shrink-0"
:style="`--_size: ${cssSize}${tint ? `;--_tint:oklch(50% 75% ${tint})` : ''}`" :style="`--_size: ${cssSize}${tint ? `;--_tint:oklch(50% 75% ${tint})` : ''}`"
:class="{ :class="{
tint: tint, tint: tint,

View File

@@ -2,7 +2,7 @@
import { DropdownIcon } from '@modrinth/assets' import { DropdownIcon } from '@modrinth/assets'
import { reactive } from 'vue' import { reactive } from 'vue'
import Button from './Button.vue' import ButtonStyled from './ButtonStyled.vue'
const props = defineProps({ const props = defineProps({
collapsible: { collapsible: {
@@ -33,9 +33,11 @@ function toggleCollapsed() {
<div v-if="!!$slots.header || collapsible" class="header"> <div v-if="!!$slots.header || collapsible" class="header">
<slot name="header"></slot> <slot name="header"></slot>
<div v-if="collapsible" class="btn-group"> <div v-if="collapsible" class="btn-group">
<Button :action="toggleCollapsed"> <ButtonStyled circular>
<button @click="toggleCollapsed">
<DropdownIcon :style="{ transform: `rotate(${state.collapsed ? 0 : 180}deg)` }" /> <DropdownIcon :style="{ transform: `rotate(${state.collapsed ? 0 : 180}deg)` }" />
</Button> </button>
</ButtonStyled>
</div> </div>
</div> </div>
<slot v-if="!state.collapsed" /> <slot v-if="!state.collapsed" />

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