diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
index 564be92c7d3..54e2cfebe3a 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -11,8 +11,6 @@ import {
import {
ArrowBigUpDashIcon,
ArrowLeftRightIcon,
- ChevronLeftIcon,
- ChevronRightIcon,
CompassIcon,
ImageIcon,
LogInIcon,
@@ -21,7 +19,6 @@ import {
PlayIcon,
PlusIcon,
RefreshCwIcon,
- RightArrowIcon,
ServerStackIcon,
SettingsIcon,
ShirtIcon,
@@ -54,7 +51,6 @@ import {
providePageContext,
providePopupNotificationManager,
TeleportOverflowMenu,
- TextLogo,
TooltipDirective,
useDebugLogger,
useFormatBytes,
@@ -75,9 +71,8 @@ import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { computed, nextTick, onMounted, onUnmounted, provide, ref, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
+import AppTitleBar from '@/app/shell/title-bar/index.vue'
import AccountsCard from '@/components/ui/AccountsCard.vue'
-import AppActionBar from '@/components/ui/AppActionBar.vue'
-import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
import ErrorModal from '@/components/ui/ErrorModal.vue'
import FriendsList from '@/components/ui/friends/FriendsList.vue'
import HostingUpdateRequired from '@/components/ui/HostingUpdateRequired.vue'
@@ -104,7 +99,6 @@ import {
markSyncInstancesUpdateNotificationShown,
shouldShowSyncInstancesUpdateNotification,
} from '@/components/ui/sync-instances-update-modal/show-notification'
-import WindowControls from '@/components/ui/WindowControls.vue'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import { useAppEvent } from '@/composables/use-app-event'
import { useAppSettings } from '@/composables/use-app-settings.ts'
@@ -188,7 +182,6 @@ import {
openAppUpdateChangelog,
setAppUpdateActions,
} from '@/providers/app-update.ts'
-import { createBreadcrumbManager, provideBreadcrumbManager } from '@/providers/breadcrumbs'
import { createContentInstall, provideContentInstall } from '@/providers/content-install'
import {
provideAppUpdateDownloadProgress,
@@ -200,6 +193,7 @@ import { setupAppEventsProvider } from '@/providers/setup/app-events'
import { setupAuthProvider } from '@/providers/setup/auth'
import { setupLoadingStateProvider } from '@/providers/setup/loading-state'
import { setupAppUserPreferencesProvider } from '@/providers/setup/user-preferences.ts'
+import { createBreadcrumbManager, provideBreadcrumbManager } from '@/shared/breadcrumbs'
import { appMessages } from '@/utils/app-messages'
import { AppNotificationManager } from './providers/app-notifications'
@@ -597,9 +591,6 @@ const messages = defineMessages({
defaultMessage: 'Dismiss',
},
warning: { id: 'app.notification.warning', defaultMessage: 'Warning' },
- goBack: { id: 'app.navigation.go-back', defaultMessage: 'Go back' },
- goForward: { id: 'app.navigation.go-forward', defaultMessage: 'Go forward' },
- nextImage: { id: 'app.navigation.next-image', defaultMessage: 'Next image' },
updateDownloadMissingVersion: {
id: 'app.update.download-error.missing-version',
defaultMessage: 'Failed to download update: no version available',
@@ -2390,56 +2381,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
-
+
{
diff --git a/apps/app-frontend/src/components/ui/WindowControls.vue b/apps/app-frontend/src/app/shell/title-bar/window-controls.vue
similarity index 86%
rename from apps/app-frontend/src/components/ui/WindowControls.vue
rename to apps/app-frontend/src/app/shell/title-bar/window-controls.vue
index 9608b4ef18e..3b622d9a9ab 100644
--- a/apps/app-frontend/src/components/ui/WindowControls.vue
+++ b/apps/app-frontend/src/app/shell/title-bar/window-controls.vue
@@ -9,7 +9,7 @@
@@ -17,7 +17,7 @@
@@ -26,7 +26,7 @@
@@ -113,13 +113,7 @@ const handleClose = async () => {
}
diff --git a/apps/app-frontend/src/components/ui/HostingUpdateRequired.vue b/apps/app-frontend/src/components/ui/HostingUpdateRequired.vue
index 6ad1c50d22a..465b8b8221c 100644
--- a/apps/app-frontend/src/components/ui/HostingUpdateRequired.vue
+++ b/apps/app-frontend/src/components/ui/HostingUpdateRequired.vue
@@ -8,7 +8,7 @@ import {
downloadAvailableAppUpdate,
installAvailableAppUpdate,
} from '@/providers/app-update'
-import { useRootBreadcrumb } from '@/providers/breadcrumbs'
+import { useRootBreadcrumb } from '@/shared/breadcrumbs'
const { formatMessage } = useVIntl()
diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue
index 2fb77e28e9f..d859d2b88c5 100644
--- a/apps/app-frontend/src/pages/Browse.vue
+++ b/apps/app-frontend/src/pages/Browse.vue
@@ -56,13 +56,13 @@ import {
instanceKeys,
instanceLinkedProjectQueryOptions,
} from '@/pages/instance/query-options'
-import { type BreadcrumbDefinition, injectBreadcrumbManager } from '@/providers/breadcrumbs'
import { injectContentInstall } from '@/providers/content-install'
import { injectServerInstall } from '@/providers/server-install'
import {
createServerInstallContent,
provideServerInstallContent,
} from '@/providers/setup/server-install-content'
+import { type BreadcrumbDefinition, injectBreadcrumbManager } from '@/shared/breadcrumbs'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
diff --git a/apps/app-frontend/src/pages/Index.vue b/apps/app-frontend/src/pages/Index.vue
index cdad0e8818a..eb9496e9085 100644
--- a/apps/app-frontend/src/pages/Index.vue
+++ b/apps/app-frontend/src/pages/Index.vue
@@ -13,8 +13,8 @@ import RecentWorldsList from '@/components/ui/world/RecentWorldsList.vue'
import { useAppSettings } from '@/composables/use-app-settings.ts'
import { traceStartupStep } from '@/helpers/startup-debug'
import { instanceListQueryOptions } from '@/pages/instance/query-options'
-import { useRootBreadcrumb } from '@/providers/breadcrumbs'
import { injectOnboardingChecklist } from '@/providers/onboarding-checklist'
+import { useRootBreadcrumb } from '@/shared/breadcrumbs'
defineOptions({
name: 'LibraryPage',
diff --git a/apps/app-frontend/src/pages/Screenshots.vue b/apps/app-frontend/src/pages/Screenshots.vue
index 00835b8f65e..f53f6ddf87a 100644
--- a/apps/app-frontend/src/pages/Screenshots.vue
+++ b/apps/app-frontend/src/pages/Screenshots.vue
@@ -4,7 +4,7 @@ import { defineMessages, useVIntl } from '@modrinth/ui'
import { onActivated } from 'vue'
import ScreenshotsPage from '@/components/ui/screenshots-page/index.vue'
-import { useRootBreadcrumb } from '@/providers/breadcrumbs'
+import { useRootBreadcrumb } from '@/shared/breadcrumbs'
defineOptions({ name: 'ScreenshotsPage' })
diff --git a/apps/app-frontend/src/pages/Servers.vue b/apps/app-frontend/src/pages/Servers.vue
index b71d6005ea5..2c3b7be11d5 100644
--- a/apps/app-frontend/src/pages/Servers.vue
+++ b/apps/app-frontend/src/pages/Servers.vue
@@ -5,7 +5,7 @@ import { injectModrinthClient, ServersManagePageIndex } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import { computed } from 'vue'
-import { useRootBreadcrumb } from '@/providers/breadcrumbs'
+import { useRootBreadcrumb } from '@/shared/breadcrumbs'
import { config } from '../config'
diff --git a/apps/app-frontend/src/pages/Skins.vue b/apps/app-frontend/src/pages/Skins.vue
index 783eff63c55..ca84f5764f0 100644
--- a/apps/app-frontend/src/pages/Skins.vue
+++ b/apps/app-frontend/src/pages/Skins.vue
@@ -54,7 +54,7 @@ import {
set_custom_skin_order,
} from '@/helpers/skins.ts'
import { hasPride26Badge } from '@/helpers/user-campaigns.ts'
-import { useRootBreadcrumb } from '@/providers/breadcrumbs'
+import { useRootBreadcrumb } from '@/shared/breadcrumbs'
import { appMessages } from '@/utils/app-messages'
useRootBreadcrumb({
diff --git a/apps/app-frontend/src/pages/User.vue b/apps/app-frontend/src/pages/User.vue
index 5519607fa91..0fcf57dc344 100644
--- a/apps/app-frontend/src/pages/User.vue
+++ b/apps/app-frontend/src/pages/User.vue
@@ -64,8 +64,8 @@ import {
unblock_user,
} from '@/helpers/users'
import { appSettingsModalOpenProfileKey } from '@/providers/app-settings-modal'
-import { useBreadcrumb } from '@/providers/breadcrumbs'
import { injectContentInstall } from '@/providers/content-install'
+import { useBreadcrumb } from '@/shared/breadcrumbs'
const route = useRoute()
const router = useRouter()
diff --git a/apps/app-frontend/src/pages/hosting/manage/Index.vue b/apps/app-frontend/src/pages/hosting/manage/Index.vue
index e182d1386fc..358ea3f7bdf 100644
--- a/apps/app-frontend/src/pages/hosting/manage/Index.vue
+++ b/apps/app-frontend/src/pages/hosting/manage/Index.vue
@@ -64,7 +64,7 @@ import { useRoute, useRouter } from 'vue-router'
import { useAppSettings } from '@/composables/use-app-settings.ts'
import { get_user } from '@/helpers/cache'
import { get as getCreds } from '@/helpers/mr_auth'
-import { provideBreadcrumbParent, useBreadcrumb } from '@/providers/breadcrumbs'
+import { provideBreadcrumbParent, useBreadcrumb } from '@/shared/breadcrumbs'
const route = useRoute()
const router = useRouter()
diff --git a/apps/app-frontend/src/pages/instance/layout.vue b/apps/app-frontend/src/pages/instance/layout.vue
index 441dafe01ed..6088bfd0bea 100644
--- a/apps/app-frontend/src/pages/instance/layout.vue
+++ b/apps/app-frontend/src/pages/instance/layout.vue
@@ -151,9 +151,9 @@ import { useSharedInstanceErrors } from '@/helpers/shared-instance-errors'
import type { GameInstance } from '@/helpers/types'
import { createInstanceShortcut, showInstanceInFolder } from '@/helpers/utils.js'
import type { ServerStatus } from '@/helpers/worlds'
-import { useRootBreadcrumb } from '@/providers/breadcrumbs'
import { provideInstanceBackup } from '@/providers/instance-backup'
import { injectServerInstall } from '@/providers/server-install'
+import { useRootBreadcrumb } from '@/shared/breadcrumbs'
import InstanceAdmonitions from './components/admonitions/index.vue'
import InstancePageHeader from './components/page-header/index.vue'
diff --git a/apps/app-frontend/src/pages/project/Index.vue b/apps/app-frontend/src/pages/project/Index.vue
index 410a9a52bcc..cce3ce51943 100644
--- a/apps/app-frontend/src/pages/project/Index.vue
+++ b/apps/app-frontend/src/pages/project/Index.vue
@@ -308,10 +308,10 @@ import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
import { get_by_instance_id } from '@/helpers/process'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import { getServerAddress } from '@/helpers/worlds'
-import { provideBreadcrumbParent, useBreadcrumb } from '@/providers/breadcrumbs'
import { injectContentInstall } from '@/providers/content-install'
import { injectServerInstall } from '@/providers/server-install'
import { createServerInstallContent } from '@/providers/setup/server-install-content'
+import { provideBreadcrumbParent, useBreadcrumb } from '@/shared/breadcrumbs'
dayjs.extend(relativeTime)
diff --git a/apps/app-frontend/src/pages/project/Version.vue b/apps/app-frontend/src/pages/project/Version.vue
index 635adc7dd65..bae5edd5abd 100644
--- a/apps/app-frontend/src/pages/project/Version.vue
+++ b/apps/app-frontend/src/pages/project/Version.vue
@@ -102,7 +102,7 @@ import { useRoute, useRouter } from 'vue-router'
import { SwapIcon } from '@/assets/icons'
import { get_project_many, get_version_many } from '@/helpers/cache.js'
-import { useBreadcrumb } from '@/providers/breadcrumbs'
+import { useBreadcrumb } from '@/shared/breadcrumbs'
const { formatMessage } = useVIntl()
const formatBytes = useFormatBytes()
diff --git a/apps/app-frontend/src/shared/breadcrumbs/context.ts b/apps/app-frontend/src/shared/breadcrumbs/context.ts
new file mode 100644
index 00000000000..43f20422ef9
--- /dev/null
+++ b/apps/app-frontend/src/shared/breadcrumbs/context.ts
@@ -0,0 +1,58 @@
+import { createContext } from '@modrinth/ui'
+import type { Component, ComputedRef, MaybeRefOrGetter } from 'vue'
+import type { RouteLocationRaw } from 'vue-router'
+
+export type BreadcrumbVisual =
+ | {
+ type: 'icon'
+ component: Component
+ }
+ | {
+ type: 'image'
+ src?: string | null
+ alt?: string
+ circle?: boolean
+ tintBy?: string | null
+ }
+
+export interface BreadcrumbDefinition {
+ slot: string
+ id: MaybeRefOrGetter
+ label: MaybeRefOrGetter
+ to?: MaybeRefOrGetter
+ visual?: MaybeRefOrGetter
+}
+
+export interface ResolvedBreadcrumb {
+ slot: string
+ id: string
+ label: string
+ to?: RouteLocationRaw
+ visual?: BreadcrumbVisual
+}
+
+export interface BreadcrumbHandle {
+ readonly slot: string
+ activate: () => void
+ reset: () => void
+ pop: () => void
+}
+
+export interface BreadcrumbManager {
+ readonly entries: ComputedRef
+ reset: (definition: BreadcrumbDefinition) => BreadcrumbHandle
+ push: (
+ definition: BreadcrumbDefinition,
+ options?: { parent?: BreadcrumbHandle },
+ ) => BreadcrumbHandle
+ find: (slot: string) => BreadcrumbHandle | undefined
+}
+
+export const [injectBreadcrumbManager, provideBreadcrumbManager] = createContext(
+ 'root',
+ 'breadcrumbManager',
+)
+export const [injectBreadcrumbParent, provideBreadcrumbParent] = createContext(
+ 'BreadcrumbParent',
+ 'breadcrumbParent',
+)
diff --git a/apps/app-frontend/src/providers/breadcrumbs.ts b/apps/app-frontend/src/shared/breadcrumbs/create-breadcrumb-manager.ts
similarity index 57%
rename from apps/app-frontend/src/providers/breadcrumbs.ts
rename to apps/app-frontend/src/shared/breadcrumbs/create-breadcrumb-manager.ts
index 58b6054b76f..b552d351ef7 100644
--- a/apps/app-frontend/src/providers/breadcrumbs.ts
+++ b/apps/app-frontend/src/shared/breadcrumbs/create-breadcrumb-manager.ts
@@ -1,60 +1,11 @@
-import { createContext } from '@modrinth/ui'
-import {
- type Component,
- computed,
- type ComputedRef,
- type MaybeRefOrGetter,
- shallowRef,
- toValue,
- watch,
-} from 'vue'
-import type { RouteLocationRaw } from 'vue-router'
-
-export type BreadcrumbVisual =
- | {
- type: 'icon'
- component: Component
- }
- | {
- type: 'image'
- src?: string | null
- alt?: string
- circle?: boolean
- tintBy?: string | null
- }
-
-export interface BreadcrumbDefinition {
- slot: string
- id: MaybeRefOrGetter
- label: MaybeRefOrGetter
- to?: MaybeRefOrGetter
- visual?: MaybeRefOrGetter
-}
-
-export interface ResolvedBreadcrumb {
- slot: string
- id: string
- label: string
- to?: RouteLocationRaw
- visual?: BreadcrumbVisual
-}
-
-export interface BreadcrumbHandle {
- readonly slot: string
- activate: () => void
- reset: () => void
- pop: () => void
-}
+import { computed, shallowRef, toValue } from 'vue'
-export interface BreadcrumbManager {
- readonly entries: ComputedRef
- reset: (definition: BreadcrumbDefinition) => BreadcrumbHandle
- push: (
- definition: BreadcrumbDefinition,
- options?: { parent?: BreadcrumbHandle },
- ) => BreadcrumbHandle
- find: (slot: string) => BreadcrumbHandle | undefined
-}
+import type {
+ BreadcrumbDefinition,
+ BreadcrumbHandle,
+ BreadcrumbManager,
+ ResolvedBreadcrumb,
+} from './context'
interface InternalBreadcrumb {
token: symbol
@@ -65,17 +16,6 @@ interface InternalBreadcrumb {
handle: BreadcrumbHandle
}
-const [injectBreadcrumbManager, provideBreadcrumbManager] = createContext(
- 'root',
- 'breadcrumbManager',
-)
-const [injectBreadcrumbParent, provideBreadcrumbParent] = createContext(
- 'BreadcrumbParent',
- 'breadcrumbParent',
-)
-
-export { injectBreadcrumbManager, provideBreadcrumbManager, provideBreadcrumbParent }
-
export function createBreadcrumbManager(): BreadcrumbManager {
const stack = shallowRef([])
@@ -181,29 +121,3 @@ export function createBreadcrumbManager(): BreadcrumbManager {
find: (slot) => stack.value.find((entry) => entry.definition.slot === slot)?.handle,
}
}
-
-function watchBreadcrumbIdentity(definition: BreadcrumbDefinition, handle: BreadcrumbHandle) {
- watch(
- () => toValue(definition.id),
- () => handle.activate(),
- { flush: 'sync' },
- )
-}
-
-export function useRootBreadcrumb(definition: BreadcrumbDefinition): BreadcrumbHandle {
- const manager = injectBreadcrumbManager()
- const handle = manager.reset(definition)
- watchBreadcrumbIdentity(definition, handle)
- return handle
-}
-
-export function useBreadcrumb(
- definition: BreadcrumbDefinition,
- options: { parent?: BreadcrumbHandle } = {},
-): BreadcrumbHandle {
- const manager = injectBreadcrumbManager()
- const parent = options.parent ?? injectBreadcrumbParent(null) ?? undefined
- const handle = manager.push(definition, { parent })
- watchBreadcrumbIdentity(definition, handle)
- return handle
-}
diff --git a/apps/app-frontend/src/shared/breadcrumbs/index.ts b/apps/app-frontend/src/shared/breadcrumbs/index.ts
new file mode 100644
index 00000000000..b5b23afda57
--- /dev/null
+++ b/apps/app-frontend/src/shared/breadcrumbs/index.ts
@@ -0,0 +1,12 @@
+export {
+ type BreadcrumbDefinition,
+ type BreadcrumbHandle,
+ type BreadcrumbManager,
+ type BreadcrumbVisual,
+ injectBreadcrumbManager,
+ provideBreadcrumbManager,
+ provideBreadcrumbParent,
+ type ResolvedBreadcrumb,
+} from './context'
+export { createBreadcrumbManager } from './create-breadcrumb-manager'
+export { useBreadcrumb, useRootBreadcrumb } from './use-breadcrumb'
diff --git a/apps/app-frontend/src/shared/breadcrumbs/use-breadcrumb.ts b/apps/app-frontend/src/shared/breadcrumbs/use-breadcrumb.ts
new file mode 100644
index 00000000000..9d7a81c99ea
--- /dev/null
+++ b/apps/app-frontend/src/shared/breadcrumbs/use-breadcrumb.ts
@@ -0,0 +1,34 @@
+import { toValue, watch } from 'vue'
+
+import {
+ type BreadcrumbDefinition,
+ type BreadcrumbHandle,
+ injectBreadcrumbManager,
+ injectBreadcrumbParent,
+} from './context'
+
+function watchBreadcrumbIdentity(definition: BreadcrumbDefinition, handle: BreadcrumbHandle) {
+ watch(
+ () => toValue(definition.id),
+ () => handle.activate(),
+ { flush: 'sync' },
+ )
+}
+
+export function useRootBreadcrumb(definition: BreadcrumbDefinition): BreadcrumbHandle {
+ const manager = injectBreadcrumbManager()
+ const handle = manager.reset(definition)
+ watchBreadcrumbIdentity(definition, handle)
+ return handle
+}
+
+export function useBreadcrumb(
+ definition: BreadcrumbDefinition,
+ options: { parent?: BreadcrumbHandle } = {},
+): BreadcrumbHandle {
+ const manager = injectBreadcrumbManager()
+ const parent = options.parent ?? injectBreadcrumbParent(null) ?? undefined
+ const handle = manager.push(definition, { parent })
+ watchBreadcrumbIdentity(definition, handle)
+ return handle
+}
diff --git a/apps/app-frontend/tailwind.config.ts b/apps/app-frontend/tailwind.config.ts
index e81246e2165..301221bab4e 100644
--- a/apps/app-frontend/tailwind.config.ts
+++ b/apps/app-frontend/tailwind.config.ts
@@ -3,6 +3,7 @@ import type { Config } from 'tailwindcss'
const config: Config = {
content: [
+ './src/app/**/*.{js,vue,ts}',
'./src/components/**/*.{js,vue,ts}',
'./src/layouts/**/*.vue',
'./src/pages/**/*.vue',