feat: blocking frontend (#6911)

* feat: blocking api interfaces

* feat: block action on user pages + shared instance flows

* feat: safety settings subpage

* feat: interaction source settings

* feat: finish social settings

* feat: profile settings xplat

* fix: prepr

* feat: click on friends

* default instance -> game options & fix feature flag width

* fix: scroll indicators

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Calum H.
2026-07-28 20:02:58 +00:00
committed by GitHub
co-authored by Prospector
parent cbb31f31c0
commit f89c116bf8
103 changed files with 2166 additions and 1075 deletions
@@ -1,6 +1,8 @@
<template>
<div class="mx-auto flex flex-col items-center p-6 text-center">
<component :is="illustration" v-if="illustration" class="h-[200px] w-auto" />
<slot name="illustration">
<component :is="illustration" v-if="illustration" class="h-[200px] w-auto" />
</slot>
<div class="flex flex-col items-center gap-1.5">
<span class="text-2xl font-semibold text-contrast">
<slot name="heading">{{ heading }}</slot>
@@ -17,6 +17,7 @@ const props = defineProps<{
ariaLabel?: string
belowModal?: boolean
hideWhenModalOpen?: boolean
inline?: boolean
}>()
const INTERCOM_BUBBLE_GAP = 8
@@ -24,6 +25,7 @@ const INTERCOM_BUBBLE_GAP = 8
const barEl = ref<HTMLElement | null>(null)
const toolbarEl = ref<HTMLElement | null>(null)
const compact = ref(false)
const attentionRequested = ref(false)
const { stackCount } = useModalStack()
const pageContext = injectPageContext(null)
@@ -75,6 +77,7 @@ function updateIntercomBubbleClearance() {
if (
typeof window === 'undefined' ||
props.inline ||
!shown.value ||
stackCount.value > 0 ||
!barEl.value ||
@@ -105,7 +108,7 @@ function updateIntercomBubbleClearance() {
function updateBodyState(isShown = shown.value) {
if (typeof document === 'undefined') return
if (isShown) {
if (isShown && !props.inline) {
visibleFloatingActionBars.add(floatingActionBarId)
} else {
visibleFloatingActionBars.delete(floatingActionBarId)
@@ -149,10 +152,10 @@ watch(
)
watch(
shown,
[shown, () => props.inline],
async (isShown) => {
await nextTick()
updateBodyState(isShown)
updateBodyState(isShown[0])
scheduleIntercomBubbleClearanceUpdate()
},
{ immediate: true },
@@ -187,24 +190,40 @@ onUnmounted(() => {
if (typeof document === 'undefined') return
updateFloatingActionBarBodyClass()
})
async function nudge(): Promise<void> {
attentionRequested.value = false
await nextTick()
attentionRequested.value = true
}
defineExpose({ nudge })
</script>
<template>
<Teleport to="body">
<Teleport to="body" :disabled="inline">
<Transition name="floating-action-bar" appear>
<div
v-if="shown"
ref="barEl"
class="floating-action-bar drop-shadow-2xl fixed p-4 bottom-0"
:style="barStyle"
class="floating-action-bar drop-shadow-2xl"
:class="inline ? 'floating-action-bar--inline z-10' : 'fixed bottom-0 p-4'"
:style="inline ? undefined : barStyle"
aria-live="polite"
>
<div
ref="toolbarEl"
role="toolbar"
:aria-label="ariaLabel"
class="relative overflow-clip flex items-center gap-1.5 rounded-[20px] bg-surface-3 border border-surface-5 border-solid mx-auto md:max-w-[60vw] px-3 py-2.5 shadow-[0px_1px_3px_0px_rgba(0,0,0,0.3),0px_6px_10px_0px_rgba(0,0,0,0.15)]"
:class="{ 'bar-compact': compact }"
class="relative overflow-clip flex items-center gap-1.5 rounded-[20px] bg-surface-3 border border-surface-5 border-solid px-3 py-2.5 shadow-[0px_1px_3px_0px_rgba(0,0,0,0.3),0px_6px_10px_0px_rgba(0,0,0,0.15)]"
:class="[
{
'bar-compact': compact,
'floating-action-bar-attention': attentionRequested,
},
inline ? 'w-full' : 'mx-auto md:max-w-[60vw]',
]"
@animationend="attentionRequested = false"
>
<slot />
</div>
@@ -220,6 +239,31 @@ onUnmounted(() => {
transition: bottom 0.25s ease-in-out;
}
.floating-action-bar--inline {
left: auto;
right: auto;
}
.floating-action-bar-attention {
animation: floating-action-bar-attention 300ms ease-in-out;
}
@keyframes floating-action-bar-attention {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-0.4rem);
}
50% {
transform: translateX(0.4rem);
}
75% {
transform: translateX(-0.2rem);
}
}
.floating-action-bar-enter-active {
transition:
transform 0.25s cubic-bezier(0.15, 1.4, 0.64, 0.96),
@@ -243,14 +287,20 @@ onUnmounted(() => {
}
@media (any-hover: none) and (max-width: 640px) {
.floating-action-bar {
.floating-action-bar:not(.floating-action-bar--inline) {
bottom: var(--size-mobile-navbar-height);
}
.expanded-mobile-nav .floating-action-bar {
.expanded-mobile-nav .floating-action-bar:not(.floating-action-bar--inline) {
bottom: var(--size-mobile-navbar-height-expanded);
}
}
@media (prefers-reduced-motion: reduce) {
.floating-action-bar-attention {
animation: none;
}
}
</style>
<style>
@@ -1,7 +1,7 @@
<script setup lang="ts" generic="T">
import { HistoryIcon, SaveIcon, SpinnerIcon } from '@modrinth/assets'
import { isEqual } from 'es-toolkit'
import { type Component, computed } from 'vue'
import { type Component, computed, ref } from 'vue'
import { defineMessage, type MessageDescriptor, useVIntl } from '../../composables/i18n'
import { commonMessages } from '../../utils'
@@ -24,6 +24,7 @@ const props = withDefaults(
saveLabel?: MessageDescriptor | string
savingLabel?: MessageDescriptor | string
saveIcon?: Component
inline?: boolean
}>(),
{
canReset: true,
@@ -36,6 +37,7 @@ const props = withDefaults(
saveLabel: () => commonMessages.saveButton,
savingLabel: () => commonMessages.savingButton,
saveIcon: SaveIcon,
inline: false,
},
)
@@ -46,10 +48,18 @@ const shown = computed(() =>
function localizeIfPossible(message: MessageDescriptor | string) {
return typeof message === 'string' ? message : formatMessage(message)
}
const actionBar = ref<InstanceType<typeof FloatingActionBar> | null>(null)
function nudge(): void {
void actionBar.value?.nudge()
}
defineExpose({ nudge })
</script>
<template>
<FloatingActionBar :shown="shown">
<FloatingActionBar ref="actionBar" :shown="shown" :inline="inline">
<p class="m-0 font-semibold text-sm md:text-base">{{ localizeIfPossible(text) }}</p>
<div class="ml-auto flex gap-2">
<ButtonStyled v-if="canReset" type="transparent">