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"
> >
<Avatar <span>{{ formatMessage(messages.notSignedIn) }}</span>
size="36px" <ButtonStyled color="brand">
:src=" <button color="primary" :disabled="loginDisabled" @click="login()">
selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png' <LogInIcon v-if="!loginDisabled" />
" <SpinnerIcon v-else class="animate-spin" />
/> {{ formatMessage(messages.signInToMinecraft) }}
<div class="flex flex-col w-full"> </button>
<span>{{ selectedAccount ? selectedAccount.profile.name : 'Select account' }}</span> </ButtonStyled>
<span class="text-secondary text-xs">Minecraft account</span>
</div>
<DropdownIcon class="w-5 h-5 shrink-0" />
</div> </div>
<transition name="fade"> <Accordion
<Card v-else
v-if="showCard || mode === 'isolated'" class="w-full mt-2 bg-button-bg border border-solid border-surface-5 rounded-xl overflow-clip"
ref="card" button-class="button-base w-full bg-transparent px-3 py-2 border-0 cursor-pointer"
class="account-card" :open-by-default="false"
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }" >
> <template #title>
<div v-if="selectedAccount" class="selected account"> <div class="flex gap-2 w-full min-w-0">
<Avatar size="xs" :src="avatarUrl" /> <Avatar
<div> size="36px"
<h4>{{ selectedAccount.profile.name }}</h4> :src="
<p>Selected</p> selectedAccount
</div> ? avatarUrl
<Button : 'https://launcher-files.modrinth.com/assets/steve_head.png'
v-tooltip="'Log out'" "
icon-only />
color="raised" <div class="flex flex-col items-start w-full min-w-0">
@click="logout(selectedAccount.profile.id)" <span class="truncate w-full text-left">{{
> selectedAccount ? selectedAccount.profile.name : formatMessage(messages.selectAccount)
<TrashIcon /> }}</span>
</Button> <span class="text-secondary text-xs">{{ formatMessage(messages.minecraftAccount) }}</span>
</div>
<div v-else class="logged-out account">
<h4>Not signed in</h4>
<Button
v-tooltip="'Log in'"
: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>
</div> </div>
<Button v-if="accounts.length > 0" @click="login()"> </template>
<PlusIcon /> <div class="bg-button-bg pt-1 pb-2 border border-solid border-surface-5">
Add account <template v-if="accounts.length > 0">
</Button> <div v-for="account in accounts" :key="account.profile.id" class="flex gap-1 items-center">
</Card> <button
</transition> 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)"
>
<RadioButtonCheckedIcon
v-if="selectedAccount && selectedAccount.profile.id === account.profile.id"
class="w-5 h-5 text-brand shrink-0"
/>
<RadioButtonIcon v-else class="w-5 h-5 text-secondary shrink-0" />
<Avatar :src="getAccountAvatarUrl(account)" size="24px" />
<p
class="m-0 truncate min-w-0"
:class="
selectedAccount && selectedAccount.profile.id === account.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 />
</button>
</ButtonStyled>
</div>
</template>
<div class="flex flex-col gap-2 px-2 pt-2">
<ButtonStyled v-if="accounts.length > 0" class="w-full">
<button :disabled="loginDisabled" @click="login()">
<PlusIcon />
{{ formatMessage(messages.addAccount) }}
</button>
</ButtonStyled>
</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 <PlusIcon v-if="!showingFiles" />
@click="() => (showingFiles = !showingFiles)" <XIcon v-else />
> </button>
<PlusIcon v-if="!showingFiles" /> </ButtonStyled>
<XIcon v-else />
</Button>
</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">
<XIcon /> <button @click="exportModal.hide">
{{ formatMessage(commonMessages.cancelButton) }} <XIcon />
</Button> {{ formatMessage(commonMessages.cancelButton) }}
<Button color="primary" @click="exportPack"> </button>
<PackageIcon /> </ButtonStyled>
{{ formatMessage(messages.exportButton) }} <ButtonStyled color="brand">
</Button> <button @click="exportPack">
<PackageIcon />
{{ formatMessage(messages.exportButton) }}
</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">
<XIcon /> <button @click="$refs.detectJavaModal.hide()">
Cancel <XIcon />
</Button> Cancel
</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" <DownloadIcon />
@click="reinstallJava" {{ installingJava ? 'Installing...' : 'Install recommended' }}
> </button>
<DownloadIcon /> </ButtonStyled>
{{ installingJava ? 'Installing...' : 'Install recommended' }} <ButtonStyled>
</Button> <button :disabled="props.disabled" @click="autoDetect">
<Button :disabled="props.disabled" @click="autoDetect"> <SearchIcon />
<SearchIcon /> Detect
Detect </button>
</Button> </ButtonStyled>
<Button :disabled="props.disabled" @click="handleJavaFileInput()"> <ButtonStyled>
<FolderSearchIcon /> <button :disabled="props.disabled" @click="handleJavaFileInput()">
Browse <FolderSearchIcon />
</Button> Browse
<Button v-if="testingJava" disabled> Testing... </Button> </button>
<Button v-else-if="testingJavaSuccess === true"> </ButtonStyled>
<CheckIcon class="test-success" /> <ButtonStyled v-if="testingJava">
Success <button disabled>Testing...</button>
</Button> </ButtonStyled>
<Button v-else-if="testingJavaSuccess === false"> <ButtonStyled v-else-if="testingJavaSuccess === true">
<XIcon class="test-fail" /> <button disabled>
Failed <CheckIcon />
</Button> Success
<Button v-else :disabled="props.disabled" @click="testJava"> </button>
<PlayIcon /> </ButtonStyled>
Test <ButtonStyled v-else-if="testingJavaSuccess === false">
</Button> <button disabled>
<XIcon />
Failed
</button>
</ButtonStyled>
<ButtonStyled v-else>
<button :disabled="props.disabled" @click="testJava">
<PlayIcon />
Test
</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'"
:disabled="inProgress || installing || version.id === installedVersion"
@click.stop="() => switchVersion(version.id)"
> >
<SwapIcon v-if="version.id !== installedVersion" /> <button
<CheckIcon v-else /> :disabled="inProgress || installing || version.id === installedVersion"
</Button> @click.stop="() => switchVersion(version.id)"
>
<SwapIcon v-if="version.id !== installedVersion" />
<CheckIcon v-else />
</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>
<DownloadIcon /> {{ installing ? 'Installing' : 'Install' }} </ButtonStyled>
</Button> <ButtonStyled color="brand">
<button :disabled="installing" @click="install()">
<DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}
</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>
<FolderSearchIcon /> <button class="ml-1.5" @click="findLauncherDir">
</Button> <FolderSearchIcon />
</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">
v-tooltip="'Delete skin'" <button
aria-label="Delete skin" v-tooltip="'Delete skin'"
color="red" aria-label="Delete skin"
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">
<PlusIcon /> <button :disabled="offline" @click="showCreationModal?.()">
Create new instance <PlusIcon />
</Button> Create new instance
</button>
</ButtonStyled>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -39,35 +39,40 @@
</div> </div>
<div class="controls"> <div class="controls">
<div class="buttons"> <div class="buttons">
<Button class="close" icon-only @click="hideImage"> <ButtonStyled circular>
<XIcon aria-hidden="true" /> <button class="close" @click="hideImage">
</Button> <XIcon aria-hidden="true" />
<a </button>
class="open btn icon-only" </ButtonStyled>
target="_blank" <ButtonStyled circular>
:href=" <a
expandedGalleryItem.raw_url class="open btn icon-only"
? expandedGalleryItem.raw_url target="_blank"
: 'https://cdn.modrinth.com/placeholder-banner.svg' :href="
" expandedGalleryItem.raw_url
> ? expandedGalleryItem.raw_url
<ExternalIcon aria-hidden="true" /> : 'https://cdn.modrinth.com/placeholder-banner.svg'
</a> "
<Button icon-only @click="zoomedIn = !zoomedIn"> >
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" /> <ExternalIcon aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" /> </a>
</Button> </ButtonStyled>
<Button <ButtonStyled circular>
v-if="filteredGallery.length > 1" <button @click="zoomedIn = !zoomedIn">
class="previous" <ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
icon-only <ContractIcon v-else aria-hidden="true" />
@click="previousImage()" </button>
> </ButtonStyled>
<LeftArrowIcon aria-hidden="true" /> <ButtonStyled v-if="filteredGallery.length > 1" circular>
</Button> <button class="previous" @click="previousImage()">
<Button v-if="filteredGallery.length > 1" class="next" icon-only @click="nextImage()"> <LeftArrowIcon aria-hidden="true" />
<RightArrowIcon aria-hidden="true" /> </button>
</Button> </ButtonStyled>
<ButtonStyled v-if="filteredGallery.length > 1" circular>
<button class="next" @click="nextImage()">
<RightArrowIcon aria-hidden="true" />
</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,34 +14,38 @@
<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" />
<CheckIcon v-else /> <CheckIcon v-else />
{{ {{
installing installing
? 'Installing...' ? 'Installing...'
: installed && installedVersion === version.id : installed && installedVersion === version.id
? 'Installed' ? 'Installed'
: 'Install' : 'Install'
}} }}
</Button> </button>
<Button> </ButtonStyled>
<ReportIcon /> <ButtonStyled>
Report <button>
</Button> <ReportIcon />
<a Report
:href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`" </button>
rel="external" </ButtonStyled>
class="btn" <ButtonStyled>
> <a
<ExternalIcon /> :href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`"
Modrinth website rel="external"
</a> >
Modrinth website
<ExternalIcon />
</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" <DownloadIcon v-if="!installed" />
:action="() => install(version.id)" <CheckIcon v-else />
:disabled="installed" {{ installed ? 'Installed' : 'Install' }}
> </button>
<DownloadIcon v-if="!installed" /> </ButtonStyled>
<CheckIcon v-else />
{{ installed ? 'Installed' : 'Install' }}
</Button>
</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,181 +726,87 @@ 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); }
.flex-card {
display: flex;
flex-direction: column;
gap: var(--gap-12);
padding: var(--gap-16);
h2 {
font-size: var(--text-18);
font-weight: var(--weight-extrabold);
color: var(--color-contrast);
line-height: initial;
margin: 0;
} }
.tag-list__item { h3 {
background-color: var(--_bg-color, var(--color-button-bg)); font-size: var(--text-16);
padding: var(--gap-4) var(--gap-8);
border-radius: var(--radius-max);
font-weight: var(--weight-bold); 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); color: var(--color-base);
font-weight: var(--weight-bold); margin: 0;
} }
.status-list__item { > section {
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 {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--gap-12);
padding: var(--gap-16);
h2 {
font-size: var(--text-18);
font-weight: var(--weight-extrabold);
color: var(--color-contrast);
line-height: initial;
margin: 0;
}
h3 {
font-size: var(--text-16);
font-weight: var(--weight-bold);
color: var(--color-base);
margin: 0;
}
> section {
display: flex;
flex-direction: column;
gap: var(--gap-8);
}
}
.list-style {
display: flex;
flex-direction: column;
gap: var(--gap-12);
font-weight: var(--weight-bold);
hr {
width: 100%;
border-color: var(--color-button-border);
margin-block: var(--gap-2);
}
}
.iconified-list-item {
display: flex;
gap: var(--gap-8); gap: var(--gap-8);
vertical-align: middle; }
align-items: center; }
width: fit-content;
svg { .list-style {
width: var(--icon-16); display: flex;
height: var(--icon-16); flex-direction: column;
} gap: var(--gap-12);
font-weight: var(--weight-bold);
> svg:first-child { hr {
flex-shrink: 0; width: 100%;
} border-color: var(--color-button-border);
margin-block: var(--gap-2);
}
}
.iconified-list-item {
display: flex;
gap: var(--gap-8);
vertical-align: middle;
align-items: center;
width: fit-content;
svg {
width: var(--icon-16);
height: var(--icon-16);
} }
.links-list { > svg:first-child {
@extend .list-style; flex-shrink: 0;
> a {
@extend .iconified-list-item;
&:hover {
text-decoration: underline;
}
}
} }
}
.details-list { .details-list {
@extend .list-style; @extend .list-style;
} }
.details-list__item { .details-list__item {
@extend .iconified-list-item; @extend .iconified-list-item;
.details-list__item__text--style-secondary { .details-list__item__text--style-secondary {
color: var(--color-secondary); color: var(--color-secondary);
font-weight: var(--weight-normal); font-weight: var(--weight-normal);
font-size: var(--text-14); font-size: var(--text-14);
}
} }
} }

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">
<XIcon /> <button @click="hide">
</button> <XIcon />
</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,47 +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'">
<button <ButtonStyled circular color="brand" type="transparent">
v-tooltip="`Accept`"
class="iconified-button square-button brand-button button-transparent"
@click="
() => {
acceptTeamInvite(notification.body.team_id)
read()
}
"
>
<CheckIcon />
</button>
<button
v-tooltip="`Decline`"
class="iconified-button square-button danger-button button-transparent"
@click="
() => {
removeSelfFromTeam(notification.body.team_id)
read()
}
"
>
<XIcon />
</button>
</template>
<button
v-else-if="!notification.read"
v-tooltip="`Mark as read`"
class="iconified-button square-button button-transparent"
@click="read()"
>
<XIcon />
</button>
</div>
<div v-else class="notification__actions">
<div v-if="type !== null" class="input-group">
<template
v-if="(type === 'team_invite' || type === 'organization_invite') && !notification.read"
>
<button <button
class="iconified-button brand-button" v-tooltip="`Accept`"
@click=" @click="
() => { () => {
acceptTeamInvite(notification.body.team_id) acceptTeamInvite(notification.body.team_id)
@@ -248,10 +210,11 @@
" "
> >
<CheckIcon /> <CheckIcon />
Accept
</button> </button>
</ButtonStyled>
<ButtonStyled circular color="red" type="transparent">
<button <button
class="iconified-button danger-button" v-tooltip="`Decline`"
@click=" @click="
() => { () => {
removeSelfFromTeam(notification.body.team_id) removeSelfFromTeam(notification.body.team_id)
@@ -260,51 +223,75 @@
" "
> >
<XIcon /> <XIcon />
Decline
</button> </button>
</template> </ButtonStyled>
<button </template>
v-else-if="!notification.read" <ButtonStyled v-else-if="!notification.read" circular type="transparent">
class="iconified-button" <button v-tooltip="`Mark as read`" @click="read()">
:class="{ 'raised-button': raised }" <XIcon />
@click="read()"
>
<CheckIcon />
Mark as read
</button> </button>
</ButtonStyled>
</div>
<div v-else class="notification__actions">
<div v-if="type !== null" class="input-group">
<template
v-if="(type === 'team_invite' || type === 'organization_invite') && !notification.read"
>
<ButtonStyled color="brand">
<button
@click="
() => {
acceptTeamInvite(notification.body.team_id)
read()
}
"
>
<CheckIcon />
Accept
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button
@click="
() => {
removeSelfFromTeam(notification.body.team_id)
read()
}
"
>
<XIcon />
Decline
</button>
</ButtonStyled>
</template>
<ButtonStyled v-else-if="!notification.read">
<button @click="read()">
<CheckIcon />
Mark as read
</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" <ExternalIcon />
:class="{ 'raised-button': raised }" Open link
:to="notification.link" </nuxt-link>
target="_blank" </ButtonStyled>
> <ButtonStyled v-for="(action, actionIndex) in notification.actions" :key="actionIndex">
<ExternalIcon /> <button @click="performAction(notification, actionIndex)">
Open link <CheckIcon v-if="action.title === 'Accept'" />
</nuxt-link> <XIcon v-else-if="action.title === 'Deny'" />
<button {{ action.title }}
v-for="(action, actionIndex) in notification.actions" </button>
:key="actionIndex" </ButtonStyled>
class="iconified-button" <ButtonStyled v-if="notification.actions.length === 0 && !notification.read">
:class="{ 'raised-button': raised }" <button @click="performAction(notification, null)">
@click="performAction(notification, actionIndex)" <CheckIcon />
> Mark as read
<CheckIcon v-if="action.title === 'Accept'" /> </button>
<XIcon v-else-if="action.title === 'Deny'" /> </ButtonStyled>
{{ action.title }}
</button>
<button
v-if="notification.actions.length === 0 && !notification.read"
class="iconified-button"
:class="{ 'raised-button': raised }"
@click="performAction(notification, null)"
>
<CheckIcon />
Mark as read
</button>
<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,50 +67,55 @@
</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 />
> </nuxt-link>
<SettingsIcon /> </ButtonStyled>
</nuxt-link>
</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">
<XIcon /> <button @click="$refs.modalOpen?.hide()">
Cancel <XIcon />
</Button> Cancel
<Button :disabled="!selectedProjects?.length" color="primary" @click="onSubmitHandler()"> </button>
<TransferIcon /> </ButtonStyled>
<span> <ButtonStyled color="brand">
Transfer <button :disabled="!selectedProjects?.length" @click="onSubmitHandler()">
<TransferIcon />
<span> <span>
{{ Transfer
selectedProjects.length === props.projects.length <span>
? 'All' {{
: selectedProjects.length selectedProjects.length === props.projects.length
}} ? 'All'
: selectedProjects.length
}}
</span>
<span>
{{ ' ' }}
{{ selectedProjects.length === 1 ? 'project' : 'projects' }}
</span>
</span> </span>
<span> </button>
{{ ' ' }} </ButtonStyled>
{{ selectedProjects.length === 1 ? 'project' : 'projects' }}
</span>
</span>
</Button>
</div> </div>
</div> </div>
</Modal> </Modal>
<Button @click="$refs.modalOpen?.show()"> <ButtonStyled>
<TransferIcon /> <button @click="$refs.modalOpen?.show()">
<span>Transfer projects</span> <TransferIcon />
</Button> <span>Transfer projects</span>
</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>
<SettingsIcon aria-hidden="true" /> <nuxt-link to="/settings/billing">
{{ formatMessage(messages.action) }} <SettingsIcon aria-hidden="true" />
</nuxt-link> {{ formatMessage(messages.action) }}
</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">
{{ formatMessage(verifyEmailBannerMessages.action) }} <button @click="handleResendEmailVerification">
</button> {{ formatMessage(verifyEmailBannerMessages.action) }}
<nuxt-link v-else class="btn" to="/settings/account"> </button>
<SettingsIcon aria-hidden="true" /> </ButtonStyled>
{{ formatMessage(addEmailBannerMessages.action) }} <ButtonStyled v-else>
</nuxt-link> <nuxt-link to="/settings/account">
<SettingsIcon aria-hidden="true" />
{{ formatMessage(addEmailBannerMessages.action) }}
</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">
<PaletteIcon /> <button v-tooltip="'Toggle project colors'" @click="onToggleColors">
</Button> <PaletteIcon />
<Button v-tooltip="'Download this data as CSV'" icon-only @click="onDownloadSetAsCSV"> </button>
<DownloadIcon /> </ButtonStyled>
</Button> <ButtonStyled circular type="outlined">
<Button v-tooltip="'Refresh the chart'" icon-only @click="resetCharts"> <button v-tooltip="'Download this data as CSV'" @click="onDownloadSetAsCSV">
<UpdatedIcon /> <DownloadIcon />
</Button> </button>
</ButtonStyled>
<ButtonStyled circular type="outlined">
<button v-tooltip="'Refresh the chart'" @click="resetCharts">
<UpdatedIcon />
</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" <ScaleIcon aria-hidden="true" />
@click="resubmit()" Resubmit for review
> </button>
<ScaleIcon aria-hidden="true" /> </ButtonStyled>
Resubmit for review
</button>
</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" <ReplyIcon aria-hidden="true" />
@click="sendReplyFromModal()" Reply to thread
> </button>
<ReplyIcon aria-hidden="true" /> </ButtonStyled>
Reply to thread
</button>
</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)">
<CheckCircleIcon aria-hidden="true" /> <button @click="reopenReport()">
Reopen thread <CheckCircleIcon aria-hidden="true" />
</button> Reopen thread
</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,196 +97,175 @@
/> />
</div> </div>
<div class="input-group"> <div class="input-group">
<button <ButtonStyled color="brand">
v-if="sortedMessages.length > 0" <button
class="btn btn-primary" v-if="sortedMessages.length > 0"
:disabled="!replyBody" :disabled="!replyBody"
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()" @click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
> >
<ReplyIcon aria-hidden="true" /> <ReplyIcon aria-hidden="true" />
Reply Reply
</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> </ButtonStyled>
<button <ButtonStyled v-if="isStaff(auth.user)">
v-if="isStaff(auth.user)" <button :disabled="!replyBody" @click="sendReply(null, true)">
class="btn" <ScaleIcon aria-hidden="true" />
:disabled="!replyBody" Add private note
@click="sendReply(null, true)" </button>
> </ButtonStyled>
<ScaleIcon aria-hidden="true" />
Add private note
</button>
<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" <ScaleIcon aria-hidden="true" />
@click="openResubmitModal(true)" Resubmit for review with reply
> </button>
<ScaleIcon aria-hidden="true" /> <button v-else @click="openResubmitModal(false)">
Resubmit for review with reply <ScaleIcon aria-hidden="true" />
</button> Resubmit for review
<button </button>
v-else </ButtonStyled>
class="iconified-button moderation-button"
@click="openResubmitModal(false)"
>
<ScaleIcon aria-hidden="true" />
Resubmit for review
</button>
</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" <CheckCircleIcon aria-hidden="true" />
@click="closeReport(true)" Close with reply
> </button>
<CheckCircleIcon aria-hidden="true" /> <button v-else @click="closeReport()">
Close with reply <CheckCircleIcon aria-hidden="true" />
</button> Close thread
<button v-else class="iconified-button danger-button" @click="closeReport()"> </button>
<CheckCircleIcon aria-hidden="true" /> </ButtonStyled>
Close thread
</button>
</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" <CheckIcon aria-hidden="true" />
:disabled="isApproved(project)" Approve with reply
@click="sendReply(requestedStatus)" </button>
> </ButtonStyled>
<CheckIcon aria-hidden="true" /> <ButtonStyled v-else color="green">
Approve with reply <button :disabled="isApproved(project)" @click="setStatus(requestedStatus)">
</button> <CheckIcon aria-hidden="true" />
<button Approve
v-else </button>
class="btn btn-green" </ButtonStyled>
:disabled="isApproved(project)"
@click="setStatus(requestedStatus)"
>
<CheckIcon aria-hidden="true" />
Approve
</button>
<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" <XIcon aria-hidden="true" />
:disabled="project.status === 'rejected'" Reject with reply
@click="sendReply('rejected')" </button>
> </ButtonStyled>
<XIcon aria-hidden="true" /> <ButtonStyled v-else color="red">
Reject with reply <button :disabled="project.status === 'rejected'" @click="setStatus('rejected')">
</button> <XIcon aria-hidden="true" />
<button Reject
v-else </button>
class="btn btn-danger" </ButtonStyled>
:disabled="project.status === 'rejected'" <ButtonStyled color="red">
@click="setStatus('rejected')" <OverflowMenu
> class="btn-dropdown-animation"
<XIcon aria-hidden="true" /> :options="
Reject replyBody
</button> ? [
<OverflowMenu {
class="btn btn-danger btn-dropdown-animation icon-only" id: 'withhold-reply',
:options=" color: 'danger',
replyBody action: () => {
? [ sendReply('withheld')
{ },
id: 'withhold-reply', hoverFilled: true,
color: 'danger', disabled: project.status === 'withheld',
action: () => {
sendReply('withheld')
}, },
hoverFilled: true, {
disabled: project.status === 'withheld', id: 'set-to-draft-reply',
}, action: () => {
{ sendReply('draft')
id: 'set-to-draft-reply', },
action: () => { hoverFilled: true,
sendReply('draft') disabled: project.status === 'draft',
}, },
hoverFilled: true, {
disabled: project.status === 'draft', id: 'send-to-review-reply',
}, action: () => {
{ sendReply('processing', true)
id: 'send-to-review-reply', },
action: () => { hoverFilled: true,
sendReply('processing', true) disabled: project.status === 'processing',
}, },
hoverFilled: true, ]
disabled: project.status === 'processing', : [
}, {
] id: 'withhold',
: [ color: 'danger',
{ action: () => {
id: 'withhold', setStatus('withheld')
color: 'danger', },
action: () => { hoverFilled: true,
setStatus('withheld') disabled: project.status === 'withheld',
}, },
hoverFilled: true, {
disabled: project.status === 'withheld', id: 'set-to-draft',
}, action: () => {
{ setStatus('draft')
id: 'set-to-draft', },
action: () => { hoverFilled: true,
setStatus('draft') disabled: project.status === 'draft',
}, },
hoverFilled: true, {
disabled: project.status === 'draft', id: 'send-to-review',
}, action: () => {
{ setStatus('processing')
id: 'send-to-review', },
action: () => { hoverFilled: true,
setStatus('processing') disabled: project.status === 'processing',
}, },
hoverFilled: true, ]
disabled: project.status === 'processing', "
}, >
] <DropdownIcon aria-hidden="true" />
" <template #withhold-reply>
> <EyeOffIcon aria-hidden="true" />
<DropdownIcon style="rotate: 180deg" aria-hidden="true" /> Withhold with reply
<template #withhold-reply> </template>
<EyeOffIcon aria-hidden="true" /> <template #withhold>
Withhold with reply <EyeOffIcon aria-hidden="true" />
</template> Withhold
<template #withhold> </template>
<EyeOffIcon aria-hidden="true" /> <template #set-to-draft-reply>
Withhold <FileTextIcon aria-hidden="true" />
</template> Set to draft with reply
<template #set-to-draft-reply> </template>
<FileTextIcon aria-hidden="true" /> <template #set-to-draft>
Set to draft with reply <FileTextIcon aria-hidden="true" />
</template> Set to draft
<template #set-to-draft> </template>
<FileTextIcon aria-hidden="true" /> <template #send-to-review-reply>
Set to draft <ScaleIcon aria-hidden="true" />
</template> Send to review with reply
<template #send-to-review-reply> </template>
<ScaleIcon aria-hidden="true" /> <template #send-to-review>
Send to review with reply <ScaleIcon aria-hidden="true" />
</template> Send to review
<template #send-to-review> </template>
<ScaleIcon aria-hidden="true" /> </OverflowMenu>
Send to review </ButtonStyled>
</template>
</OverflowMenu>
</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,20 +104,22 @@
</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">
<OverflowMenu <ButtonStyled circular type="transparent">
class="btn btn-transparent icon-only" <OverflowMenu
:options="[ class="btn-dropdown-animation"
{ :options="[
id: 'delete', {
action: () => deleteMessage(), id: 'delete',
color: 'red', action: () => deleteMessage(),
hoverFilled: true, color: 'red',
}, hoverFilled: true,
]" },
> ]"
<MoreHorizontalIcon /> >
<template #delete> <TrashIcon /> Delete </template> <MoreHorizontalIcon />
</OverflowMenu> <template #delete> <TrashIcon /> Delete </template>
</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>
<button <ButtonStyled>
class="btn collection-button" <button
@click="(event) => $refs.modal_collection.show(event)" class="mx-3 mb-3"
> @click="(event) => $refs.modal_collection.show(event)"
<PlusIcon aria-hidden="true" /> >
{{ formatMessage(messages.createNewCollection) }} <PlusIcon aria-hidden="true" />
</button> {{ formatMessage(messages.createNewCollection) }}
</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>
<a <ButtonStyled color="brand" type="transparent">
:href="version.primaryFile?.url" <a
class="iconified-button download" class="ml-auto"
:title="`Download ${version.name}`" :href="version.primaryFile?.url"
> :title="`Download ${version.name}`"
<DownloadIcon aria-hidden="true" /> >
Download <DownloadIcon aria-hidden="true" />
</a> Download
</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,23 +10,24 @@
<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>
<FileInput <ButtonStyled v-if="editIndex === -1" type="outlined">
v-if="editIndex === -1" <FileInput
class="iconified-button raised-button" class="button-like"
prompt="Replace" prompt="Replace"
:accept="acceptFileTypes" :accept="acceptFileTypes"
:max-size="5242880" :max-size="5242880"
should-always-reset should-always-reset
aria-label="Replace image" aria-label="Replace image"
@change=" @change="
(x) => { (x) => {
editFile = x[0] editFile = x[0]
showPreviewImage() showPreviewImage()
} }
" "
> >
<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" <StarIcon aria-hidden="true" />
class="iconified-button" Set as banner
@click="editFeatured = true" </button>
> </ButtonStyled>
<StarIcon aria-hidden="true" /> <ButtonStyled v-else>
Feature image <button id="gallery-image-featured" class="w-fit" @click="editFeatured = false">
</button> <StarIcon fill="currentColor" aria-hidden="true" />
<button Unset as banner
v-else </button>
id="gallery-image-featured" </ButtonStyled>
class="iconified-button"
@click="editFeatured = false"
>
<StarIcon fill="currentColor" aria-hidden="true" />
Unfeature image
</button>
<div class="button-group"> <div class="button-group">
<button class="iconified-button" @click="modalEditItem?.hide()"> <ButtonStyled type="outlined">
<XIcon aria-hidden="true" /> <button @click="modalEditItem?.hide()">
Cancel <XIcon aria-hidden="true" />
</button> Cancel
<button </button>
v-if="editIndex === -1" </ButtonStyled>
class="iconified-button brand-button" <ButtonStyled v-if="editIndex === -1" color="brand">
:disabled="shouldPreventActions" <button :disabled="shouldPreventActions" @click="createGalleryItem">
@click="createGalleryItem" <PlusIcon aria-hidden="true" />
> Add gallery image
<PlusIcon aria-hidden="true" /> </button>
Add gallery image </ButtonStyled>
</button> <ButtonStyled v-else color="brand">
<button <button :disabled="shouldPreventActions" @click="editGalleryItem">
v-else <SaveIcon aria-hidden="true" />
class="iconified-button brand-button" Save changes
:disabled="shouldPreventActions" </button>
@click="editGalleryItem" </ButtonStyled>
>
<SaveIcon aria-hidden="true" />
Save changes
</button>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -155,39 +145,41 @@
</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>
<XIcon aria-hidden="true" /> <button class="close" @click="expandedGalleryItem = null">
</button> <XIcon aria-hidden="true" />
<a </button>
class="open circle-button" </ButtonStyled>
target="_blank" <ButtonStyled circular>
:href=" <a
expandedGalleryItem?.raw_url class="open"
? expandedGalleryItem?.raw_url target="_blank"
: 'https://cdn.modrinth.com/placeholder-banner.svg' :href="
" expandedGalleryItem?.raw_url
> ? expandedGalleryItem?.raw_url
<ExternalIcon aria-hidden="true" /> : 'https://cdn.modrinth.com/placeholder-banner.svg'
</a> "
<button class="circle-button" @click="zoomedIn = !zoomedIn"> >
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" /> <ExternalIcon aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" /> </a>
</button> </ButtonStyled>
<button <ButtonStyled circular>
v-if="filteredGallery.length > 1" <button @click="zoomedIn = !zoomedIn">
class="previous circle-button" <ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
@click="previousImage()" <ContractIcon v-else aria-hidden="true" />
> </button>
<LeftArrowIcon aria-hidden="true" /> </ButtonStyled>
</button> <ButtonStyled v-if="filteredGallery.length > 1" circular>
<button <button class="previous" @click="previousImage()">
v-if="filteredGallery.length > 1" <LeftArrowIcon aria-hidden="true" />
class="next circle-button" </button>
@click="nextImage()" </ButtonStyled>
> <ButtonStyled v-if="filteredGallery.length > 1" circular>
<RightArrowIcon aria-hidden="true" /> <button class="next" @click="nextImage()">
</button> <RightArrowIcon aria-hidden="true" />
</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">
<FileInput <ButtonStyled color="brand">
:max-size="5242880" <FileInput
:accept="acceptFileTypes" :max-size="5242880"
prompt="Upload an image" :accept="acceptFileTypes"
aria-label="Upload an image" prompt="Upload an image"
class="iconified-button brand-button" aria-label="Upload an image"
:disabled="!isPermission(currentMember?.permissions, 1 << 2)" class="button-like"
@change="handleFiles" :disabled="!isPermission(currentMember?.permissions, 1 << 2)"
> @change="handleFiles"
<UploadIcon aria-hidden="true" /> >
</FileInput> <UploadIcon aria-hidden="true" />
</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,35 +233,37 @@
{{ 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">
<button <ButtonStyled>
class="iconified-button" <button
@click=" @click="
() => { () => {
resetEdit() resetEdit()
editIndex = index editIndex = index
editTitle = item.title ?? '' editTitle = item.title ?? ''
editDescription = item.description ?? '' editDescription = item.description ?? ''
editFeatured = item.featured editFeatured = item.featured
editOrder = item.ordering editOrder = item.ordering
modalEditItem?.show() modalEditItem?.show()
} }
" "
> >
<EditIcon aria-hidden="true" /> <EditIcon aria-hidden="true" />
Edit Edit
</button> </button>
<button </ButtonStyled>
class="iconified-button" <ButtonStyled>
@click=" <button
() => { @click="
deleteIndex = index () => {
modalConfirm?.show() deleteIndex = index
} modalConfirm?.show()
" }
> "
<TrashIcon aria-hidden="true" /> >
Remove <TrashIcon aria-hidden="true" />
</button> Remove
</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,23 +10,24 @@
<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>
<FileInput <ButtonStyled v-if="editIndex === -1" type="outlined">
v-if="editIndex === -1" <FileInput
class="iconified-button raised-button" class="button-like"
prompt="Replace" prompt="Replace"
:accept="acceptFileTypes" :accept="acceptFileTypes"
:max-size="5242880" :max-size="5242880"
should-always-reset should-always-reset
aria-label="Replace image" aria-label="Replace image"
@change=" @change="
(x) => { (x) => {
editFile = x[0] editFile = x[0]
showPreviewImage() showPreviewImage()
} }
" "
> >
<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" <StarIcon aria-hidden="true" />
class="iconified-button" Set as banner
@click="editFeatured = true" </button>
> </ButtonStyled>
<StarIcon aria-hidden="true" /> <ButtonStyled v-else>
Feature image <button id="gallery-image-featured" class="w-fit" @click="editFeatured = false">
</button> <StarIcon fill="currentColor" aria-hidden="true" />
<button Unset as banner
v-else </button>
id="gallery-image-featured" </ButtonStyled>
class="iconified-button"
@click="editFeatured = false"
>
<StarIcon fill="currentColor" aria-hidden="true" />
Unfeature image
</button>
<div class="button-group"> <div class="button-group">
<button class="iconified-button" @click="modal_edit_item.hide()"> <ButtonStyled type="outlined">
<XIcon aria-hidden="true" /> <button @click="modal_edit_item.hide()">
Cancel <XIcon aria-hidden="true" />
</button> Cancel
<button </button>
v-if="editIndex === -1" </ButtonStyled>
class="iconified-button brand-button" <ButtonStyled v-if="editIndex === -1" color="brand">
:disabled="shouldPreventActions" <button :disabled="shouldPreventActions" @click="createGalleryItem">
@click="createGalleryItem" <PlusIcon aria-hidden="true" />
> Add gallery image
<PlusIcon aria-hidden="true" /> </button>
Add gallery image </ButtonStyled>
</button> <ButtonStyled v-else color="brand">
<button <button :disabled="shouldPreventActions" @click="editGalleryItem">
v-else <SaveIcon aria-hidden="true" />
class="iconified-button brand-button" Save changes
:disabled="shouldPreventActions" </button>
@click="editGalleryItem" </ButtonStyled>
>
<SaveIcon aria-hidden="true" />
Save changes
</button>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -155,56 +145,60 @@
</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>
<XIcon aria-hidden="true" /> <button class="close" @click="expandedGalleryItem = null">
</button> <XIcon aria-hidden="true" />
<a </button>
class="open circle-button" </ButtonStyled>
target="_blank" <ButtonStyled circular>
:href=" <a
expandedGalleryItem.raw_url class="open"
? expandedGalleryItem.raw_url target="_blank"
: 'https://cdn.modrinth.com/placeholder-banner.svg' :href="
" expandedGalleryItem.raw_url
> ? expandedGalleryItem.raw_url
<ExternalIcon aria-hidden="true" /> : 'https://cdn.modrinth.com/placeholder-banner.svg'
</a> "
<button class="circle-button" @click="zoomedIn = !zoomedIn"> >
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" /> <ExternalIcon aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" /> </a>
</button> </ButtonStyled>
<button <ButtonStyled circular>
v-if="filteredGallery.length > 1" <button @click="zoomedIn = !zoomedIn">
class="previous circle-button" <ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
@click="previousImage()" <ContractIcon v-else aria-hidden="true" />
> </button>
<LeftArrowIcon aria-hidden="true" /> </ButtonStyled>
</button> <ButtonStyled v-if="filteredGallery.length > 1" circular>
<button <button class="previous" @click="previousImage()">
v-if="filteredGallery.length > 1" <LeftArrowIcon aria-hidden="true" />
class="next circle-button" </button>
@click="nextImage()" </ButtonStyled>
> <ButtonStyled v-if="filteredGallery.length > 1" circular>
<RightArrowIcon aria-hidden="true" /> <button class="next" @click="nextImage()">
</button> <RightArrowIcon aria-hidden="true" />
</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">
<FileInput <ButtonStyled color="brand">
:max-size="5242880" <FileInput
:accept="acceptFileTypes" :max-size="5242880"
prompt="Upload an image" :accept="acceptFileTypes"
aria-label="Upload an image" prompt="Upload an image"
class="iconified-button brand-button" aria-label="Upload an image"
:disabled="!isPermission(currentMember?.permissions, 1 << 2)" class="button-like"
@change="handleFiles" :disabled="!isPermission(currentMember?.permissions, 1 << 2)"
> @change="handleFiles"
<UploadIcon aria-hidden="true" /> >
</FileInput> <UploadIcon aria-hidden="true" />
</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,35 +232,37 @@
{{ 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">
<button <ButtonStyled>
class="iconified-button" <button
@click=" @click="
() => { () => {
resetEdit() resetEdit()
editIndex = index editIndex = index
editTitle = item.title editTitle = item.title
editDescription = item.description editDescription = item.description
editFeatured = item.featured editFeatured = item.featured
editOrder = item.ordering editOrder = item.ordering
modal_edit_item.show() modal_edit_item.show()
} }
" "
> >
<EditIcon aria-hidden="true" /> <EditIcon aria-hidden="true" />
Edit Edit
</button> </button>
<button </ButtonStyled>
class="iconified-button" <ButtonStyled>
@click=" <button
() => { @click="
deleteIndex = index () => {
modal_confirm.show() deleteIndex = index
} modal_confirm.show()
" }
> "
<TrashIcon aria-hidden="true" /> >
Remove <TrashIcon aria-hidden="true" />
</button> Remove
</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,29 +81,28 @@
size="md" size="md"
class="project__icon" class="project__icon"
/> />
<div class="input-stack"> <div class="flex flex-col gap-2">
<FileInput <ButtonStyled>
id="project-icon" <FileInput
:max-size="262144000" id="project-icon"
:show-icon="true" :max-size="262144000"
accept="image/png,image/jpeg,image/gif,image/webp" :show-icon="true"
class="choose-image iconified-button" accept="image/png,image/jpeg,image/gif,image/webp"
prompt="Upload icon" class="button-like choose-image"
aria-label="Upload icon" prompt="Upload icon"
:disabled="!hasPermission" aria-label="Upload icon"
@change="showPreviewImage" :disabled="!hasPermission"
> @change="showPreviewImage"
<UploadIcon aria-hidden="true" /> >
</FileInput> <UploadIcon aria-hidden="true" />
<button </FileInput>
v-if="!deletedIcon && (previewImage || project.icon_url)" </ButtonStyled>
class="iconified-button" <ButtonStyled v-if="!deletedIcon && (previewImage || project.icon_url)">
:disabled="!hasPermission" <button :disabled="!hasPermission" @click="markIconForDeletion">
@click="markIconForDeletion" <TrashIcon aria-hidden="true" />
> Remove icon
<TrashIcon aria-hidden="true" /> </button>
Remove icon </ButtonStyled>
</button>
</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">
<FileInput <ButtonStyled>
:max-size="524288" <FileInput
:show-icon="true" :max-size="524288"
accept="image/png,image/jpeg,image/gif,image/webp" :show-icon="true"
class="iconified-button" accept="image/png,image/jpeg,image/gif,image/webp"
prompt="Upload banner" class="button-like"
:disabled="!hasPermission" prompt="Upload banner"
@change="showBannerPreview" :disabled="!hasPermission"
> @change="showBannerPreview"
<UploadIcon aria-hidden="true" /> >
</FileInput> <UploadIcon aria-hidden="true" />
<button </FileInput>
v-if="!deletedBanner && (bannerPreview || bannerGalleryImage?.url)" </ButtonStyled>
class="iconified-button" <ButtonStyled v-if="!deletedBanner && (bannerPreview || bannerGalleryImage?.url)">
:disabled="!hasPermission" <button :disabled="!hasPermission" @click="markBannerForDeletion">
@click="markBannerForDeletion" <TrashIcon aria-hidden="true" />
> Remove banner
<TrashIcon aria-hidden="true" /> </button>
Remove banner </ButtonStyled>
</button>
</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" <TrashIcon aria-hidden="true" />
:disabled="!hasDeletePermission" Delete project
@click="$refs.modal_confirm.show()" </button>
> </ButtonStyled>
<TrashIcon aria-hidden="true" />
Delete project
</button>
</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" <SaveIcon />
:disabled="!hasServerChanges" Save changes
@click="saveServerChanges()" </button>
> </ButtonStyled>
<SaveIcon />
Save changes
</button>
</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" <SaveIcon />
:disabled="!hasChanges" Save changes
@click="saveChanges()" </button>
> </ButtonStyled>
<SaveIcon />
Save changes
</button>
</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,31 +41,33 @@
@keypress.enter="inviteTeamMember()" @keypress.enter="inviteTeamMember()"
/> />
<label for="username" class="hidden">Username</label> <label for="username" class="hidden">Username</label>
<button <ButtonStyled color="brand">
class="iconified-button brand-button" <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>
<button <ButtonStyled color="red">
class="iconified-button danger-button" <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!' : ''
" "
@click="leaveProject()" @click="leaveProject()"
> >
<UserXIcon /> <UserXIcon />
Leave project Leave project
</button> </button>
</ButtonStyled>
</div> </div>
</Card> </Card>
<div <div
@@ -88,16 +90,18 @@
<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>
class="square-button dropdown-icon" <button
@click=" class="dropdown-icon"
openTeamMembers.indexOf(member.user.id) === -1 @click="
? openTeamMembers.push(member.user.id) openTeamMembers.indexOf(member.user.id) === -1
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id)) ? openTeamMembers.push(member.user.id)
" : (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
> "
<DropdownIcon /> >
</button> <DropdownIcon />
</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">
<button <ButtonStyled color="brand">
class="iconified-button brand-button" <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>
<button </ButtonStyled>
v-if="!member.is_owner" <ButtonStyled v-if="!member.is_owner" color="red">
class="iconified-button danger-button" <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 />
> Transfer ownership
<TransferIcon /> </button>
Transfer ownership </ButtonStyled>
</button>
</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" <CheckIcon />
@click="openTransferToOrgModal($event)" <span class="w-max"> Transfer management </span>
> </button>
<CheckIcon /> </ButtonStyled>
<span class="w-max"> Transfer management </span>
</button>
</div> </div>
<button v-if="organization" class="btn" @click="$refs.modal_remove.show()"> <ButtonStyled v-if="organization">
<OrganizationIcon /> <button @click="$refs.modal_remove.show()">
Remove from organization <OrganizationIcon />
</button> Remove from organization
</button>
</ButtonStyled>
</section> </section>
<div <div
v-for="(member, index) in allOrgMembers" v-for="(member, index) in allOrgMembers"
@@ -339,16 +342,18 @@
<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>
class="square-button dropdown-icon" <button
@click=" class="dropdown-icon"
openTeamMembers.indexOf(member.user.id) === -1 @click="
? openTeamMembers.push(member.user.id) openTeamMembers.indexOf(member.user.id) === -1
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id)) ? openTeamMembers.push(member.user.id)
" : (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
> "
<DropdownIcon /> >
</button> <DropdownIcon />
</button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
@@ -522,17 +527,18 @@
we don't allow clicking the button in that last case. we don't allow clicking the button in that last case.
--> -->
<button <ButtonStyled color="brand">
class="iconified-button brand-button" <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)
" "
@click="updateOrgMember(index)" @click="updateOrgMember(index)"
> >
<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,84 +178,82 @@ 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" v-model="query"
v-model="query" type="search"
type="search" :icon="SearchIcon"
:icon="SearchIcon" placeholder="Search templates..."
placeholder="Search templates..." wrapper-class="w-72"
wrapper-class="w-72" />
/>
<ButtonStyled color="brand"> <ButtonStyled color="brand">
<button :disabled="filtered.length === 0" @click="openAll"> <button :disabled="filtered.length === 0" @click="openAll">
<LibraryIcon class="h-4 w-4" aria-hidden="true" /> <LibraryIcon class="h-4 w-4" aria-hidden="true" />
Open all ({{ counts.shown }}) Open all ({{ counts.shown }})
</button> </button>
</ButtonStyled> </ButtonStyled>
<span class="text-sm text-secondary"> <span class="text-sm text-secondary">
Showing <span class="font-medium text-contrast">{{ counts.shown }}</span> of Showing <span class="font-medium text-contrast">{{ counts.shown }}</span> of
<span class="font-medium text-contrast">{{ counts.total }}</span> <span class="font-medium text-contrast">{{ counts.total }}</span>
</span> </span>
</div> </div>
<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.
</div>
<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
v-for="id in filtered"
:key="id"
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"
> >
No templates match your search. <div class="mb-3">
</div> <div class="font-mono text-sm font-semibold tracking-tight text-contrast">
{{ id }}
<ul v-else class="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<li
v-for="id in filtered"
: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"
>
<div class="mb-3">
<div class="font-mono text-sm font-semibold tracking-tight text-contrast">
{{ id }}
</div>
<div class="mt-1 truncate text-xs text-secondary">
/_internal/templates/email/{{ id }}
</div>
</div> </div>
<div class="mt-1 truncate text-xs text-secondary">
<div class="mt-auto flex gap-2"> /_internal/templates/email/{{ id }}
<ButtonStyled color="brand" class="flex-1">
<button class="w-full justify-center" @click="openPreview(id, $event)">
<PlayIcon class="h-4 w-4" aria-hidden="true" />
Preview
</button>
</ButtonStyled>
<ButtonStyled>
<button class="justify-center" title="Copy preview URL" @click="copy(id)">
<CopyIcon class="h-4 w-4" aria-hidden="true" />
</button>
</ButtonStyled>
</div> </div>
</li> </div>
</ul>
</Card>
<p class="mt-2 text-xs text-secondary"> <div class="mt-auto flex gap-2">
<ButtonStyled color="brand">
<button @click="openPreview(id, $event)">
<PlayIcon aria-hidden="true" />
Preview
</button>
</ButtonStyled>
<ButtonStyled circular type="outlined">
<button title="Copy preview URL" @click="copy(id)">
<CopyIcon aria-hidden="true" />
</button>
</ButtonStyled>
</div>
</li>
</ul>
<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"
> >

File diff suppressed because one or more lines are too long

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">
<XIcon /> <button class="wide-button" :disabled="pending" @click="onReject">
{{ formatMessage(messages.decline) }} <XIcon />
</Button> {{ formatMessage(messages.decline) }}
<Button class="wide-button" color="primary" large :action="onAuthorize" :disabled="pending"> </button>
<CheckIcon /> </ButtonStyled>
{{ formatMessage(messages.authorize) }} <ButtonStyled color="brand" size="large">
</Button> <button class="wide-button" :disabled="pending" @click="onAuthorize">
<CheckIcon />
{{ formatMessage(messages.authorize) }}
</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" />
<button <ButtonStyled color="brand">
class="btn btn-primary centered-btn" <button
:disabled="globals?.captcha_enabled ? !token : false" class="mx-auto"
@click="recovery" :disabled="globals?.captcha_enabled ? !token : false"
> @click="recovery"
<SendIcon /> {{ formatMessage(methodChoiceMessages.action) }} >
</button> <SendIcon /> {{ formatMessage(methodChoiceMessages.action) }}
</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">
{{ formatMessage(postChallengeMessages.action) }} <button class="auth-form__input continue-btn" @click="changePassword">
</button> {{ formatMessage(postChallengeMessages.action) }}
</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">
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon /> <button class="continue-btn" @click="begin2FASignIn">
</button> {{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
</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>
<DiscordColorIcon /> <a :href="getAuthUrl('discord', redirectTarget)">
<span>Discord</span> <DiscordColorIcon />
</a> <span>Discord</span>
<a class="btn" :href="getAuthUrl('github', redirectTarget)"> </a>
<GitHubColorIcon /> </ButtonStyled>
<span>GitHub</span> <ButtonStyled>
</a> <a :href="getAuthUrl('github', redirectTarget)">
<a class="btn" :href="getAuthUrl('microsoft', redirectTarget)"> <GitHubColorIcon />
<MicrosoftColorIcon /> <span>GitHub</span>
<span>Microsoft</span> </a>
</a> </ButtonStyled>
<a class="btn" :href="getAuthUrl('google', redirectTarget)"> <ButtonStyled>
<GoogleColorIcon /> <a :href="getAuthUrl('microsoft', redirectTarget)">
<span>Google</span> <MicrosoftColorIcon />
</a> <span>Microsoft</span>
<a class="btn" :href="getAuthUrl('steam', redirectTarget)"> </a>
<SteamColorIcon /> </ButtonStyled>
<span>Steam</span> <ButtonStyled>
</a> <a :href="getAuthUrl('google', redirectTarget)">
<a class="btn" :href="getAuthUrl('gitlab', redirectTarget)"> <GoogleColorIcon />
<GitLabColorIcon /> <span>Google</span>
<span>GitLab</span> </a>
</a> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('steam', redirectTarget)">
<SteamColorIcon />
<span>Steam</span>
</a>
</ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('gitlab', redirectTarget)">
<GitLabColorIcon />
<span>GitLab</span>
</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" />
<button <ButtonStyled color="brand">
class="btn btn-primary continue-btn centered-btn" <button
:disabled="globals?.captcha_enabled ? !token : false" class="continue-btn centered-btn"
@click="beginPasswordSignIn()" :disabled="globals?.captcha_enabled ? !token : false"
> @click="beginPasswordSignIn()"
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon /> >
</button> {{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
</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>
<DiscordColorIcon /> <a class="discord-btn" :href="getAuthUrl('discord', redirectTarget)">
<span>Discord</span> <DiscordColorIcon />
</a> <span>Discord</span>
<a class="btn" :href="getAuthUrl('github', redirectTarget)"> </a>
<GitHubColorIcon /> </ButtonStyled>
<span>GitHub</span> <ButtonStyled>
</a> <a :href="getAuthUrl('github', redirectTarget)">
<a class="btn" :href="getAuthUrl('microsoft', redirectTarget)"> <GitHubColorIcon />
<MicrosoftColorIcon /> <span>GitHub</span>
<span>Microsoft</span> </a>
</a> </ButtonStyled>
<a class="btn" :href="getAuthUrl('google', redirectTarget)"> <ButtonStyled>
<GoogleColorIcon /> <a :href="getAuthUrl('microsoft', redirectTarget)">
<span>Google</span> <MicrosoftColorIcon />
</a> <span>Microsoft</span>
<a class="btn" :href="getAuthUrl('steam', redirectTarget)"> </a>
<SteamColorIcon /> </ButtonStyled>
<span>Steam</span> <ButtonStyled>
</a> <a :href="getAuthUrl('google', redirectTarget)">
<a class="btn" :href="getAuthUrl('gitlab', redirectTarget)"> <GoogleColorIcon />
<GitLabColorIcon /> <span>Google</span>
<span>GitLab</span> </a>
</a> </ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('steam', redirectTarget)">
<SteamColorIcon />
<span>Steam</span>
</a>
</ButtonStyled>
<ButtonStyled>
<a :href="getAuthUrl('gitlab', redirectTarget)">
<GitLabColorIcon />
<span>GitLab</span>
</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" />
<button <ButtonStyled color="brand">
class="btn btn-primary continue-btn centered-btn" <button
:disabled="globals?.captcha_enabled ? !token : false" class="continue-btn centered-btn"
@click="createAccount" :disabled="globals?.captcha_enabled ? !token : false"
> @click="createAccount"
{{ formatMessage(messages.createAccountButton) }} <RightArrowIcon /> >
</button> {{ formatMessage(messages.createAccountButton) }} <RightArrowIcon />
</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>
<SettingsIcon /> {{ formatMessage(messages.accountSettings) }} <NuxtLink to="/settings/account">
</NuxtLink> <SettingsIcon /> {{ formatMessage(messages.accountSettings) }}
</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">
{{ formatMessage(commonMessages.continueButton) }} <button class="centered-btn" @click="continueSignUp">
<RightArrowIcon /> {{ formatMessage(commonMessages.continueButton) }}
</button> <RightArrowIcon />
</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" <SpinnerIcon v-if="removing" class="animate-spin" aria-hidden="true" />
:disabled="removing" <XIcon v-else aria-hidden="true" />
@click="() => removeProject(project)" {{ formatMessage(messages.removeProjectButton) }}
> </button>
<SpinnerIcon v-if="removing" class="animate-spin" aria-hidden="true" /> </ButtonStyled>
<XIcon v-else aria-hidden="true" /> <ButtonStyled v-if="collection.id === 'following'">
{{ formatMessage(messages.removeProjectButton) }} <button @click="unfollowProject(project)">
</button> <HeartMinusIcon aria-hidden="true" />
<button {{ formatMessage(messages.unfollowProjectButton) }}
v-if="collection.id === 'following'" </button>
class="iconified-button" </ButtonStyled>
@click="unfollowProject(project)"
>
<HeartMinusIcon aria-hidden="true" />
{{ formatMessage(messages.unfollowProjectButton) }}
</button>
</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" <PlusIcon aria-hidden="true" />
@click="(event) => $refs.modal_creation.show(event)" {{ formatMessage(messages.createNewButton) }}
> </button>
<PlusIcon aria-hidden="true" /> </ButtonStyled>
{{ formatMessage(messages.createNewButton) }}
</Button>
</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>
<HistoryIcon /> <nuxt-link to="/dashboard/notifications/history" class="!mt-4 w-fit">
{{ formatMessage(messages.viewNotificationHistory) }} <HistoryIcon />
</nuxt-link> {{ formatMessage(messages.viewNotificationHistory) }}
</div> </nuxt-link>
</section> </ButtonStyled>
</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">
<HistoryIcon /> <button @click="updateRoute()">
{{ formatMessage(messages.viewHistory) }} <HistoryIcon />
</Button> {{ formatMessage(messages.viewHistory) }}
<Button v-if="notifications.length > 0" color="danger" @click="readAll()"> </button>
<CheckCheckIcon /> </ButtonStyled>
{{ formatMessage(messages.markAllAsRead) }} <ButtonStyled v-if="notifications.length > 0" color="red">
</Button> <button @click="readAll()">
<CheckCheckIcon />
{{ formatMessage(messages.markAllAsRead) }}
</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">
<PlusIcon aria-hidden="true" /> <button @click="openCreateOrgModal">
{{ formatMessage(messages.createOrganization) }} <PlusIcon aria-hidden="true" />
</button> {{ formatMessage(messages.createOrganization) }}
</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"
/> />
<button <ButtonStyled circular>
v-tooltip="formatMessage(messages.clearLinkLabel)" <button
:aria-label="formatMessage(messages.clearLinkLabel)" v-tooltip="formatMessage(messages.clearLinkLabel)"
class="square-button label-button" class="label-button"
:data-active="editLinks.issues.clear" :aria-label="formatMessage(messages.clearLinkLabel)"
@click="editLinks.issues.clear = !editLinks.issues.clear" :data-active="editLinks.issues.clear"
> @click="editLinks.issues.clear = !editLinks.issues.clear"
<TrashIcon /> >
</button> <TrashIcon />
</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)"
/> />
<button <ButtonStyled circular>
v-tooltip="formatMessage(messages.clearLinkLabel)" <button
:aria-label="formatMessage(messages.clearLinkLabel)" v-tooltip="formatMessage(messages.clearLinkLabel)"
class="square-button label-button" class="label-button"
:data-active="editLinks.source.clear" :aria-label="formatMessage(messages.clearLinkLabel)"
@click="editLinks.source.clear = !editLinks.source.clear" :data-active="editLinks.source.clear"
> @click="editLinks.source.clear = !editLinks.source.clear"
<TrashIcon /> >
</button> <TrashIcon />
</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)"
/> />
<button <ButtonStyled circular>
v-tooltip="formatMessage(messages.clearLinkLabel)" <button
:aria-label="formatMessage(messages.clearLinkLabel)" v-tooltip="formatMessage(messages.clearLinkLabel)"
class="square-button label-button" class="label-button"
:data-active="editLinks.wiki.clear" :aria-label="formatMessage(messages.clearLinkLabel)"
@click="editLinks.wiki.clear = !editLinks.wiki.clear" :data-active="editLinks.wiki.clear"
> @click="editLinks.wiki.clear = !editLinks.wiki.clear"
<TrashIcon /> >
</button> <TrashIcon />
</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)"
/> />
<button <ButtonStyled circular>
v-tooltip="formatMessage(messages.clearLinkLabel)" <button
:aria-label="formatMessage(messages.clearLinkLabel)" v-tooltip="formatMessage(messages.clearLinkLabel)"
class="square-button label-button" class="label-button"
:data-active="editLinks.discord.clear" :aria-label="formatMessage(messages.clearLinkLabel)"
@click="editLinks.discord.clear = !editLinks.discord.clear" :data-active="editLinks.discord.clear"
> @click="editLinks.discord.clear = !editLinks.discord.clear"
<TrashIcon /> >
</button> <TrashIcon />
</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">
<XIcon /> <button @click="$refs.editLinksModal.hide()">
{{ formatMessage(commonMessages.cancelButton) }} <XIcon />
</button> {{ formatMessage(commonMessages.cancelButton) }}
<button class="iconified-button brand-button" @click="bulkEditLinks()"> </button>
<SaveIcon /> </ButtonStyled>
{{ formatMessage(commonMessages.saveChangesButton) }} <ButtonStyled color="brand">
</button> <button @click="bulkEditLinks()">
<SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }}
</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">
<PlusIcon /> <button @click="$refs.modal_creation.show($event)">
{{ formatMessage(commonMessages.createAProjectButton) }} <PlusIcon />
</button> {{ formatMessage(commonMessages.createAProjectButton) }}
</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" <EditIcon />
@click="$refs.editLinksModal.show()" {{ formatMessage(messages.editLinksButton) }}
> </button>
<EditIcon /> </ButtonStyled>
{{ formatMessage(messages.editLinksButton) }}
</button>
<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)"
/> />
<button <ButtonStyled circular>
v-tooltip="formatMessage(descending ? messages.descending : messages.ascending)" <button
class="square-button" v-tooltip="formatMessage(descending ? messages.descending : messages.ascending)"
@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">
<CheckIcon /> <button @click="onAcceptInvite">
Accept <CheckIcon />
</button> Accept
<button class="iconified-button danger-button" @click="onDeclineInvite"> </button>
<XIcon /> </ButtonStyled>
Decline <ButtonStyled color="red">
</button> <button @click="onDeclineInvite">
<XIcon />
Decline
</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">
<FileInput <ButtonStyled>
id="project-icon" <FileInput
:max-size="262144" id="project-icon"
:show-icon="true" :max-size="262144"
accept="image/png,image/jpeg,image/gif,image/webp" :show-icon="true"
class="btn" accept="image/png,image/jpeg,image/gif,image/webp"
prompt="Upload icon" class="button-like"
:disabled="!hasPermission" prompt="Upload icon"
@change="showPreviewImage" :disabled="!hasPermission"
> @change="showPreviewImage"
<UploadIcon /> >
</FileInput> <UploadIcon />
<Button </FileInput>
v-if="!deletedIcon && (previewImage || organization.icon_url)" </ButtonStyled>
:disabled="!hasPermission" <ButtonStyled v-if="!deletedIcon && (previewImage || organization.icon_url)">
@click="markIconForDeletion" <button :disabled="!hasPermission" @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">
<TrashIcon /> <button @click="() => $refs.modal_deletion.show()">
Delete organization <TrashIcon />
</Button> Delete organization
</button>
</ButtonStyled>
</div> </div>
<UnsavedChangesPopup <UnsavedChangesPopup
:original="originalState" :original="originalState"

View File

@@ -43,19 +43,20 @@
@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,
organizationPermissions.MANAGE_INVITES, organizationPermissions.MANAGE_INVITES,
) )
" "
@click="() => onInviteTeamMember(organization.team_id, currentUsername)" @click="() => onInviteTeamMember(organization.team_id, currentUsername)"
> >
<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,18 +96,18 @@
<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 ? openTeamMembers.push(member.user.id)
? openTeamMembers.push(member.user.id) : (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id)) "
" >
> <DropdownIcon />
<DropdownIcon /> </button>
</Button> </ButtonStyled>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
@@ -188,44 +190,44 @@
</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,
organizationPermissions.EDIT_MEMBER, organizationPermissions.EDIT_MEMBER,
) )
" "
@click="onUpdateTeamMember(organization.team_id, member)" @click="onUpdateTeamMember(organization.team_id, member)"
> >
<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,
organizationPermissions.EDIT_MEMBER, organizationPermissions.EDIT_MEMBER,
) && ) &&
!isPermission( !isPermission(
currentMember.organization_permissions, currentMember.organization_permissions,
organizationPermissions.REMOVE_MEMBER, organizationPermissions.REMOVE_MEMBER,
) )
" "
@click="onRemoveMember(organization.team_id, member)" @click="onRemoveMember(organization.team_id, member)"
> >
<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"
/> />
<button <ButtonStyled circular>
v-tooltip="'Clear link'" <button
aria-label="Clear link" v-tooltip="'Clear link'"
class="square-button label-button" class="label-button"
:data-active="editLinks.issues.clear" aria-label="Clear link"
@click="editLinks.issues.clear = !editLinks.issues.clear" :data-active="editLinks.issues.clear"
> @click="editLinks.issues.clear = !editLinks.issues.clear"
<TrashIcon /> >
</button> <TrashIcon />
</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'
" "
/> />
<button <ButtonStyled circular>
v-tooltip="'Clear link'" <button
aria-label="Clear link" v-tooltip="'Clear link'"
class="square-button label-button" class="label-button"
:data-active="editLinks.source.clear" aria-label="Clear link"
@click="editLinks.source.clear = !editLinks.source.clear" :data-active="editLinks.source.clear"
> @click="editLinks.source.clear = !editLinks.source.clear"
<TrashIcon /> >
</button> <TrashIcon />
</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'
" "
/> />
<button <ButtonStyled circular>
v-tooltip="'Clear link'" <button
aria-label="Clear link" v-tooltip="'Clear link'"
class="square-button label-button" class="label-button"
:data-active="editLinks.wiki.clear" aria-label="Clear link"
@click="editLinks.wiki.clear = !editLinks.wiki.clear" :data-active="editLinks.wiki.clear"
> @click="editLinks.wiki.clear = !editLinks.wiki.clear"
<TrashIcon /> >
</button> <TrashIcon />
</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'
" "
/> />
<button <ButtonStyled circular>
v-tooltip="'Clear link'" <button
aria-label="Clear link" v-tooltip="'Clear link'"
class="square-button label-button" class="label-button"
:data-active="editLinks.discord.clear" aria-label="Clear link"
@click="editLinks.discord.clear = !editLinks.discord.clear" :data-active="editLinks.discord.clear"
> @click="editLinks.discord.clear = !editLinks.discord.clear"
<TrashIcon /> >
</button> <TrashIcon />
</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>
<XIcon /> <button @click="$refs.editLinksModal.hide()">
Cancel <XIcon />
</button> Cancel
<button class="iconified-button brand-button" @click="onBulkEditLinks"> </button>
<SaveIcon /> </ButtonStyled>
Save changes <ButtonStyled color="brand">
</button> <button @click="onBulkEditLinks">
<SaveIcon />
Save changes
</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">
<PlusIcon /> <button @click="$refs.modal_creation.show($event)">
{{ formatMessage(commonMessages.createAProjectButton) }} <PlusIcon />
</button> {{ formatMessage(commonMessages.createAProjectButton) }}
</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" <EditIcon />
@click="$refs.editLinksModal.show()" Edit links
> </button>
<EditIcon /> </ButtonStyled>
Edit links
</button>
<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)"
/> />
<button <ButtonStyled circular>
v-tooltip="descending ? 'Descending' : 'Ascending'" <button
class="square-button" v-tooltip="descending ? 'Descending' : 'Ascending'"
@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"
> >
<SettingsIcon aria-hidden="true" /> <nuxt-link to="/settings/billing">
Manage subscription <SettingsIcon aria-hidden="true" />
</nuxt-link> Manage subscription
<button v-else-if="auth.user" class="btn btn-purple btn-large" @click="purchaseModal.show()"> </nuxt-link>
Subscribe </ButtonStyled>
</button> <ButtonStyled v-else-if="auth.user" color="purple" size="large">
<nuxt-link <button @click="purchaseModal.show()">Subscribe</button>
v-else </ButtonStyled>
:to="`/auth/sign-in?redirect=${encodeURIComponent('/plus?showModal=true')}`" <ButtonStyled v-else color="purple" size="large">
class="btn btn-purple btn-large" <nuxt-link :to="`/auth/sign-in?redirect=${encodeURIComponent('/plus?showModal=true')}`">
> 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>
<XIcon /> <button @click="$refs.changeEmailModal.hide()">
{{ formatMessage(commonMessages.cancelButton) }} <XIcon />
</button> {{ formatMessage(commonMessages.cancelButton) }}
<button </button>
type="button" </ButtonStyled>
class="iconified-button brand-button" <ButtonStyled color="brand">
:disabled="!email" <button :disabled="!email" @click="saveEmail()">
@click="saveEmail()" <SaveIcon />
> {{ formatMessage(messages.saveEmailButton) }}
<SaveIcon /> </button>
{{ formatMessage(messages.saveEmailButton) }} </ButtonStyled>
</button>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -108,44 +107,43 @@
</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>
<XIcon /> <button @click="$refs.managePasswordModal.hide()">
{{ formatMessage(commonMessages.cancelButton) }} <XIcon />
</button> {{ formatMessage(commonMessages.cancelButton) }}
<template v-if="removePasswordMode">
<button
type="button"
class="iconified-button danger-button"
:disabled="!oldPassword"
@click="savePassword"
>
<TrashIcon />
{{ formatMessage(messages.removePasswordButton) }}
</button> </button>
</ButtonStyled>
<template v-if="removePasswordMode">
<ButtonStyled color="red">
<button :disabled="!oldPassword" @click="savePassword">
<TrashIcon />
{{ formatMessage(messages.removePasswordButton) }}
</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"
> >
<TrashIcon /> <button @click="removePasswordMode = true">
{{ formatMessage(messages.removePasswordButton) }} <TrashIcon />
</button> {{ formatMessage(messages.removePasswordButton) }}
<button </button>
type="button" </ButtonStyled>
class="iconified-button brand-button" <ButtonStyled color="brand">
:disabled=" <button
newPassword.length == 0 || :disabled="
(auth.user.has_password && oldPassword.length == 0) || newPassword.length == 0 ||
newPassword !== confirmNewPassword (auth.user.has_password && oldPassword.length == 0) ||
" newPassword !== confirmNewPassword
@click="savePassword" "
> @click="savePassword"
<SaveIcon /> >
{{ formatMessage(messages.savePasswordButton) }} <SaveIcon />
</button> {{ formatMessage(messages.savePasswordButton) }}
</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>
<XIcon /> <button @click="$refs.manageTwoFactorModal.hide()">
{{ formatMessage(commonMessages.cancelButton) }} <XIcon />
</button> {{ formatMessage(commonMessages.cancelButton) }}
<button class="iconified-button danger-button" @click="removeTwoFactor"> </button>
<TrashIcon /> </ButtonStyled>
{{ formatMessage(messages.twoFactorRemoveButton) }} <ButtonStyled color="red">
</button> <button @click="removeTwoFactor">
<TrashIcon />
{{ formatMessage(messages.twoFactorRemoveButton) }}
</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">
<LeftArrowIcon /> <button @click="twoFactorStep = 0">
{{ formatMessage(commonMessages.backButton) }} <LeftArrowIcon />
</button> {{ formatMessage(commonMessages.backButton) }}
<button </button>
v-if="twoFactorStep !== 2" </ButtonStyled>
class="iconified-button" <ButtonStyled v-if="twoFactorStep !== 2">
@click="$refs.manageTwoFactorModal.hide()" <button @click="$refs.manageTwoFactorModal.hide()">
> <XIcon />
<XIcon /> {{ formatMessage(commonMessages.cancelButton) }}
{{ formatMessage(commonMessages.cancelButton) }} </button>
</button> </ButtonStyled>
<button <ButtonStyled v-if="twoFactorStep <= 1" color="brand">
v-if="twoFactorStep <= 1" <button @click="twoFactorStep === 1 ? verifyTwoFactorCode() : (twoFactorStep = 1)">
class="iconified-button brand-button" <RightArrowIcon />
@click="twoFactorStep === 1 ? verifyTwoFactorCode() : (twoFactorStep = 1)" {{ formatMessage(commonMessages.continueButton) }}
> </button>
<RightArrowIcon /> </ButtonStyled>
{{ formatMessage(commonMessages.continueButton) }} <ButtonStyled v-if="twoFactorStep === 2" color="brand">
</button> <button @click="$refs.manageTwoFactorModal.hide()">
<button <CheckIcon />
v-if="twoFactorStep === 2" {{ formatMessage(messages.completeSetupButton) }}
class="iconified-button brand-button" </button>
@click="$refs.manageTwoFactorModal.hide()" </ButtonStyled>
>
<CheckIcon />
{{ formatMessage(messages.completeSetupButton) }}
</button>
</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" <TrashIcon /> {{ formatMessage(commonMessages.removeButton) }}
@click="handleRemoveAuthProvider(provider.id)" </button>
> </ButtonStyled>
<TrashIcon /> {{ formatMessage(commonMessages.removeButton) }} <ButtonStyled v-else>
</button> <a :href="`${getAuthUrl(provider.id, '/settings/account')}&token=${auth.token}`">
<a <ExternalIcon /> {{ formatMessage(messages.providerAddButton) }}
v-else </a>
class="btn" </ButtonStyled>
:href="`${getAuthUrl(provider.id, '/settings/account')}&token=${auth.token}`"
>
<ExternalIcon /> {{ formatMessage(messages.providerAddButton) }}
</a>
</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>
<XIcon /> <button @click="$refs.manageProvidersModal.hide()">
{{ formatMessage(commonMessages.closeButton) }} <XIcon />
</button> {{ formatMessage(commonMessages.closeButton) }}
</button>
</ButtonStyled>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -332,16 +328,18 @@
}}</span> }}</span>
</label> </label>
<div> <div>
<button class="iconified-button" @click="$refs.changeEmailModal.show()"> <ButtonStyled>
<template v-if="auth.user.email"> <button @click="$refs.changeEmailModal.show()">
<EditIcon /> <template v-if="auth.user.email">
{{ formatMessage(messages.changeEmailButton) }} <EditIcon />
</template> {{ formatMessage(messages.changeEmailButton) }}
<template v-else> </template>
<PlusIcon /> <template v-else>
{{ formatMessage(messages.addEmailButton) }} <PlusIcon />
</template> {{ formatMessage(messages.addEmailButton) }}
</button> </template>
</button>
</ButtonStyled>
</div> </div>
</div> </div>
<div class="adjacent-input"> <div class="adjacent-input">
@@ -359,24 +357,25 @@
</span> </span>
</label> </label>
<div> <div>
<button <ButtonStyled>
class="iconified-button" <button
@click=" @click="
() => { () => {
oldPassword = '' oldPassword = ''
newPassword = '' newPassword = ''
confirmNewPassword = '' confirmNewPassword = ''
removePasswordMode = false removePasswordMode = false
$refs.managePasswordModal.show() $refs.managePasswordModal.show()
} }
" "
> >
<KeyIcon /> <KeyIcon />
<template v-if="auth.user.has_password">{{ <template v-if="auth.user.has_password">{{
formatMessage(messages.changePasswordButton) formatMessage(messages.changePasswordButton)
}}</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,14 +386,16 @@
}}</span> }}</span>
</label> </label>
<div> <div>
<button class="iconified-button" @click="showTwoFactorModal"> <ButtonStyled>
<template v-if="auth.user.has_totp"> <button @click="showTwoFactorModal">
<TrashIcon /> {{ formatMessage(messages.twoFactorRemoveButton) }} <template v-if="auth.user.has_totp">
</template> <TrashIcon /> {{ formatMessage(messages.twoFactorRemoveButton) }}
<template v-else> </template>
<PlusIcon /> {{ formatMessage(messages.twoFactorSetupButton) }} <template v-else>
</template> <PlusIcon /> {{ formatMessage(messages.twoFactorSetupButton) }}
</button> </template>
</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>
<SettingsIcon /> {{ formatMessage(messages.manageProvidersButton) }} <button @click="$refs.manageProvidersModal.show()">
</button> <SettingsIcon /> {{ formatMessage(messages.manageProvidersButton) }}
</button>
</ButtonStyled>
</div> </div>
</div> </div>
</section> </section>
@@ -415,31 +418,33 @@
<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">
<DownloadIcon /> <a :href="generated" download="export.json">
{{ formatMessage(messages.downloadExportButton) }} <DownloadIcon />
</a> {{ formatMessage(messages.downloadExportButton) }}
<button v-else class="iconified-button" :disabled="generatingExport" @click="exportData"> </a>
<template v-if="generatingExport"> </ButtonStyled>
<UpdatedIcon /> {{ formatMessage(messages.generatingExportButton) }} <ButtonStyled v-else>
</template> <button :disabled="generatingExport" @click="exportData">
<template v-else> <template v-if="generatingExport">
<UpdatedIcon /> {{ formatMessage(messages.generateExportButton) }} <UpdatedIcon /> {{ formatMessage(messages.generatingExportButton) }}
</template> </template>
</button> <template v-else>
<UpdatedIcon /> {{ formatMessage(messages.generateExportButton) }}
</template>
</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" <TrashIcon />
@click="$refs.modal_confirm.show()" {{ formatMessage(messages.deleteAccountButton) }}
> </button>
<TrashIcon /> </ButtonStyled>
{{ formatMessage(messages.deleteAccountButton) }}
</button>
</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" />
<FileInput <ButtonStyled>
:max-size="262144" <FileInput
class="btn" :max-size="262144"
:prompt="formatMessage(messages.uploadIcon)" class="button-like"
accept="image/png,image/jpeg,image/gif,image/webp" :prompt="formatMessage(messages.uploadIcon)"
@change="onImageSelection" accept="image/png,image/jpeg,image/gif,image/webp"
> @change="onImageSelection"
<UploadIcon /> >
</FileInput> <UploadIcon />
</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>
<TrashIcon /> <button @click="() => redirectUris.splice(index, 1)">
</Button> <TrashIcon />
<Button </button>
v-if="index === 0" </ButtonStyled>
color="primary" <ButtonStyled v-if="index === 0" color="brand">
icon-only <button @click="() => redirectUris.push('')">
@click="() => redirectUris.push('')" <PlusIcon /> {{ formatMessage(messages.addMore) }}
> </button>
<PlusIcon /> {{ formatMessage(messages.addMore) }} </ButtonStyled>
</Button>
</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">
<PlusIcon /> {{ formatMessage(messages.addRedirectUri) }} <button @click="() => redirectUris.push('')">
</Button> <PlusIcon /> {{ formatMessage(messages.addRedirectUri) }}
</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>
<XIcon /> <button @click="$refs.appModal.hide()">
{{ formatMessage(messages.cancel) }} <XIcon />
</button> {{ formatMessage(messages.cancel) }}
<button </button>
v-if="editingId" </ButtonStyled>
:disabled="!canSubmit" <ButtonStyled v-if="editingId" color="brand">
type="button" <button :disabled="!canSubmit" @click="editApp">
class="iconified-button brand-button" <SaveIcon />
@click="editApp" {{ formatMessage(messages.saveChanges) }}
> </button>
<SaveIcon /> </ButtonStyled>
{{ formatMessage(messages.saveChanges) }} <ButtonStyled v-else color="brand">
</button> <button :disabled="!canSubmit" @click="createApp">
<button <PlusIcon />
v-else {{ formatMessage(messages.createApp) }}
:disabled="!canSubmit" </button>
type="button" </ButtonStyled>
class="iconified-button brand-button"
@click="createApp"
>
<PlusIcon />
{{ formatMessage(messages.createApp) }}
</button>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -147,22 +144,22 @@
<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>
<button <ButtonStyled color="brand">
class="btn btn-primary" <button
@click=" @click="
() => { () => {
name = null name = null
icon = null icon = null
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,34 +207,35 @@
</div> </div>
</div> </div>
<div class="input-group"> <div class="input-group">
<Button <ButtonStyled>
icon-only <button
@click=" @click="
() => { () => {
setForm({ setForm({
...app, ...app,
redirect_uris: app.redirect_uris.map((u) => u.uri) || [], redirect_uris: app.redirect_uris.map((u) => u.uri) || [],
}) })
$refs.appModal.show() $refs.appModal.show()
} }
" "
> >
<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
$refs.modal_confirm.show() $refs.modal_confirm.show()
} }
" "
> >
<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,19 +69,19 @@
</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 $refs.modal_confirm.show()
$refs.modal_confirm.show() }
} "
" >
> <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>
<HistoryIcon /> {{ formatMessage(messages.paymentMethodHistory) }} <nuxt-link to="/settings/billing/charges">
</nuxt-link> <HistoryIcon /> {{ formatMessage(messages.paymentMethodHistory) }}
<button class="btn" @click="addPaymentMethod"> </nuxt-link>
<PlusIcon /> {{ formatMessage(messages.paymentMethodAdd) }} </ButtonStyled>
</button> <ButtonStyled>
<button @click="addPaymentMethod">
<PlusIcon /> {{ formatMessage(messages.paymentMethodAdd) }}
</button>
</ButtonStyled>
</div> </div>
<div <div
v-if="!paymentMethods || paymentMethods.length === 0" v-if="!paymentMethods || paymentMethods.length === 0"
@@ -637,41 +641,43 @@
</div> </div>
</div> </div>
</div> </div>
<OverflowMenu <ButtonStyled circular type="transparent">
:dropdown-id="`${baseId}-payment-method-overflow-${index}`" <OverflowMenu
class="btn icon-only transparent" :dropdown-id="`${baseId}-payment-method-overflow-${index}`"
:options=" class="btn-dropdown-animation !w-10"
[ :options="
{ [
id: 'primary', {
action: () => editPaymentMethod(index, true), id: 'primary',
}, action: () => editPaymentMethod(index, true),
{
id: 'remove',
action: () => {
removePaymentMethodIndex = index
$refs.modal_confirm.show()
}, },
color: 'red', {
hoverOnly: true, id: 'remove',
}, action: () => {
].slice(primaryPaymentMethodId === method.id ? 1 : 0, 2) removePaymentMethodIndex = index
" $refs.modal_confirm.show()
> },
<MoreVerticalIcon /> color: 'red',
<template #primary> hoverOnly: true,
<StarIcon /> },
{{ formatMessage(messages.paymentMethodMakePrimary) }} ].slice(primaryPaymentMethodId === method.id ? 1 : 0, 2)
</template> "
<template #edit> >
<EditIcon /> <MoreVerticalIcon />
{{ formatMessage(commonMessages.editButton) }} <template #primary>
</template> <StarIcon />
<template #remove> {{ formatMessage(messages.paymentMethodMakePrimary) }}
<TrashIcon /> </template>
{{ formatMessage(commonMessages.deleteLabel) }} <template #edit>
</template> <EditIcon />
</OverflowMenu> {{ formatMessage(commonMessages.editButton) }}
</template>
<template #remove>
<TrashIcon />
{{ formatMessage(commonMessages.deleteLabel) }}
</template>
</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">
{{ formatMessage(developerModeBanner.deactivate) }} <button class="mt-3" @click="disableDeveloperMode()">
</Button> {{ formatMessage(developerModeBanner.deactivate) }}
</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">
<XIcon /> <button @click="$refs.patModal.hide()">
{{ formatMessage(commonMessages.cancelButton) }} <XIcon />
</button> {{ formatMessage(commonMessages.cancelButton) }}
<button </button>
v-if="editPatId !== null" </ButtonStyled>
:disabled="loading || !name || !expires" <ButtonStyled v-if="editPatId !== null" color="brand">
type="button" <button :disabled="loading || !name || !expires" @click="editPat">
class="iconified-button brand-button" <SaveIcon />
@click="editPat" {{ formatMessage(commonMessages.saveChangesButton) }}
> </button>
<SaveIcon /> </ButtonStyled>
{{ formatMessage(commonMessages.saveChangesButton) }} <ButtonStyled v-else color="brand">
</button> <button :disabled="loading || !name || !expires" @click="createPat">
<button <PlusIcon />
v-else {{ formatMessage(createModalMessages.action) }}
:disabled="loading || !name || !expires" </button>
type="button" </ButtonStyled>
class="iconified-button brand-button"
@click="createPat"
>
<PlusIcon />
{{ formatMessage(createModalMessages.action) }}
</button>
</div> </div>
</div> </div>
</NewModal> </NewModal>
@@ -99,20 +93,21 @@
<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>
<button <ButtonStyled color="brand">
class="btn btn-primary" <button
@click=" @click="
() => { () => {
name = null name = null
scopesVal = 0 scopesVal = 0
expires = null expires = null
editPatId = null editPatId = null
$refs.patModal.show() $refs.patModal.show()
} }
" "
> >
<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,31 +167,33 @@
</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">
<button <ButtonStyled>
class="iconified-button raised-button" <button
@click=" @click="
() => { () => {
editPatId = pat.id editPatId = pat.id
name = pat.name name = pat.name
scopesVal = pat.scopes scopesVal = pat.scopes
expires = $dayjs(pat.expires).format('YYYY-MM-DD') expires = $dayjs(pat.expires).format('YYYY-MM-DD')
$refs.patModal.show() $refs.patModal.show()
} }
" "
> >
<EditIcon /> {{ formatMessage(tokenMessages.edit) }} <EditIcon /> {{ formatMessage(tokenMessages.edit) }}
</button> </button>
<button </ButtonStyled>
class="iconified-button raised-button" <ButtonStyled>
@click=" <button
() => { @click="
deletePatIndex = pat.id () => {
$refs.modal_confirm.show() deletePatIndex = pat.id
} $refs.modal_confirm.show()
" }
> "
<TrashIcon /> {{ formatMessage(tokenMessages.revoke) }} >
</button> <TrashIcon /> {{ formatMessage(tokenMessages.revoke) }}
</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,33 +21,38 @@
circle circle
:alt="auth.user.username" :alt="auth.user.username"
/> />
<div class="input-stack"> <div class="flex flex-col gap-2">
<FileInput <ButtonStyled>
:max-size="262144" <FileInput
:show-icon="true" :max-size="262144"
class="btn" :show-icon="true"
:prompt="formatMessage(commonMessages.uploadImageButton)" class="button-like"
accept="image/png,image/jpeg,image/gif,image/webp" :prompt="formatMessage(commonMessages.uploadImageButton)"
@change="showPreviewImage" accept="image/png,image/jpeg,image/gif,image/webp"
> @change="showPreviewImage"
<UploadIcon /> >
</FileInput> <UploadIcon />
<Button v-if="avatarUrl !== null" :action="removePreviewImage"> </FileInput>
<TrashIcon /> </ButtonStyled>
{{ formatMessage(commonMessages.removeImageButton) }} <ButtonStyled v-if="avatarUrl !== null">
</Button> <button @click="removePreviewImage">
<Button <TrashIcon />
v-if="previewImage" {{ formatMessage(commonMessages.removeImageButton) }}
:action=" </button>
() => { </ButtonStyled>
icon = null <ButtonStyled v-if="previewImage">
previewImage = null <button
} @click="
" () => {
> icon = null
<UndoIcon /> previewImage = null
{{ formatMessage(commonMessages.resetButton) }} }
</Button> "
>
<UndoIcon />
{{ formatMessage(commonMessages.resetButton) }}
</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>
<UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }} <NuxtLink :to="`/user/${auth.user.username}`">
</Button> <UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }}
</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>
<XIcon /> {{ formatMessage(messages.revokeSessionButton) }} <button @click="revokeSession(session.id)">
</button> <XIcon /> {{ formatMessage(messages.revokeSessionButton) }}
</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>
<DropdownIcon :style="{ transform: `rotate(${state.collapsed ? 0 : 180}deg)` }" /> <button @click="toggleCollapsed">
</Button> <DropdownIcon :style="{ transform: `rotate(${state.collapsed ? 0 : 180}deg)` }" />
</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