{
+ const { t } = useTranslation();
+ const location = useLocation();
+ const { sidebarPlaylistList } = useGeneralSettings();
+
+ const translatedSidebarItemMap = useMemo(
+ () => ({
+ Albums: t('page.sidebar.albums', { postProcess: 'titleCase' }),
+ Artists: t('page.sidebar.albumArtists', { postProcess: 'titleCase' }),
+ 'Artists-all': t('page.sidebar.artists', { postProcess: 'titleCase' }),
+ Genres: t('page.sidebar.genres', { postProcess: 'titleCase' }),
+ Home: t('page.sidebar.home', { postProcess: 'titleCase' }),
+ 'Now Playing': t('page.sidebar.nowPlaying', { postProcess: 'titleCase' }),
+ Playlists: t('page.sidebar.playlists', { postProcess: 'titleCase' }),
+ Search: t('page.sidebar.search', { postProcess: 'titleCase' }),
+ Settings: t('page.sidebar.settings', { postProcess: 'titleCase' }),
+ Tracks: t('page.sidebar.tracks', { postProcess: 'titleCase' }),
+ }),
+ [t],
+ );
+
+ const { sidebarItems } = useGeneralSettings();
+
+ const sidebarItemsWithRoute: SidebarItemType[] = useMemo(() => {
+ if (!sidebarItems) return [];
+
+ const items = sidebarItems
+ .filter((item) => !item.disabled)
+ .map((item) => ({
+ ...item,
+ label:
+ translatedSidebarItemMap[item.id as keyof typeof translatedSidebarItemMap] ??
+ item.label,
+ }));
+
+ return items;
+ }, [sidebarItems, translatedSidebarItemMap]);
+
+ return (
+
+ );
+};
diff --git a/src/renderer/features/titlebar/components/app-menu.tsx b/src/renderer/features/titlebar/components/app-menu.tsx
index a272e313c..8764ea065 100644
--- a/src/renderer/features/titlebar/components/app-menu.tsx
+++ b/src/renderer/features/titlebar/components/app-menu.tsx
@@ -248,7 +248,6 @@ export const AppMenu = () => {
return (
{item.items.map((subItem) => {
- console.log(subItem.id);
return {renderMenuItem(subItem)};
})}
diff --git a/src/renderer/hooks/use-is-mobile.ts b/src/renderer/hooks/use-is-mobile.ts
new file mode 100644
index 000000000..ac24942e3
--- /dev/null
+++ b/src/renderer/hooks/use-is-mobile.ts
@@ -0,0 +1,6 @@
+import { useMediaQuery } from '@mantine/hooks';
+
+export const useIsMobile = () => {
+ const isMobile = useMediaQuery('(max-width: 768px)');
+ return isMobile;
+};
diff --git a/src/renderer/layouts/mobile-layout/mobile-layout.module.css b/src/renderer/layouts/mobile-layout/mobile-layout.module.css
new file mode 100644
index 000000000..030762029
--- /dev/null
+++ b/src/renderer/layouts/mobile-layout/mobile-layout.module.css
@@ -0,0 +1,55 @@
+.layout {
+ position: relative;
+ display: grid;
+ grid-template-areas:
+ 'window-bar'
+ 'main-content'
+ 'player';
+ grid-template-rows: 0 calc(100vh - 90px) 90px;
+ grid-template-columns: 1fr;
+ gap: 0;
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+ background: var(--theme-colors-background);
+}
+
+.drawer-button {
+ position: absolute;
+ bottom: calc(90px + 0.75rem);
+ left: 0.75rem;
+ z-index: 100;
+ background: color-mix(in srgb, var(--theme-colors-background) 90%, transparent);
+ border: 1px solid var(--theme-colors-border);
+ backdrop-filter: blur(10px);
+}
+
+.main-content {
+ position: relative;
+ grid-area: main-content;
+ overflow-x: hidden;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+.full-screen-player-overlay {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 200;
+ visibility: hidden;
+ pointer-events: none;
+ background: var(--theme-colors-background);
+ opacity: 0;
+ transition:
+ opacity 0.3s ease-in-out,
+ visibility 0.3s ease-in-out;
+}
+
+.full-screen-player-visible {
+ visibility: visible;
+ pointer-events: auto;
+ opacity: 1;
+}
diff --git a/src/renderer/layouts/mobile-layout/mobile-layout.tsx b/src/renderer/layouts/mobile-layout/mobile-layout.tsx
new file mode 100644
index 000000000..b10a57269
--- /dev/null
+++ b/src/renderer/layouts/mobile-layout/mobile-layout.tsx
@@ -0,0 +1,92 @@
+import clsx from 'clsx';
+import { lazy } from 'react';
+import { Outlet, useNavigate } from 'react-router';
+
+import styles from './mobile-layout.module.css';
+
+import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
+import { MobileFullscreenPlayer } from '/@/renderer/features/player/components/mobile-fullscreen-player';
+import { CommandPalette } from '/@/renderer/features/search/components/command-palette';
+import { MobileSidebar } from '/@/renderer/features/sidebar/components/mobile-sidebar';
+import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar';
+import { AppRoute } from '/@/renderer/router/routes';
+import { useFullScreenPlayerStore } from '/@/renderer/store';
+import { useCommandPalette } from '/@/renderer/store';
+import { useHotkeySettings } from '/@/renderer/store/settings.store';
+import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
+import { Drawer } from '/@/shared/components/drawer/drawer';
+import { useDisclosure } from '/@/shared/hooks/use-disclosure';
+import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
+
+const WindowBar = lazy(() =>
+ import('/@/renderer/layouts/window-bar').then((module) => ({
+ default: module.WindowBar,
+ })),
+);
+
+interface MobileLayoutProps {
+ shell?: boolean;
+}
+
+export const MobileLayout = ({ shell }: MobileLayoutProps) => {
+ const { opened, ...handlers } = useCommandPalette();
+ const { bindings } = useHotkeySettings();
+ const navigate = useNavigate();
+ const [sidebarOpened, { close: closeSidebar, open: openSidebar }] = useDisclosure(false);
+ const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore();
+
+ useHotkeys([
+ [bindings.globalSearch.hotkey, () => handlers.open()],
+ [bindings.browserBack.hotkey, () => navigate(-1)],
+ [bindings.browserForward.hotkey, () => navigate(1)],
+ [bindings.navigateHome.hotkey, () => navigate(AppRoute.HOME)],
+ ]);
+
+ return (
+ <>
+
+ {!shell &&
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/src/renderer/layouts/responsive-layout.tsx b/src/renderer/layouts/responsive-layout.tsx
new file mode 100644
index 000000000..b6d6ea73b
--- /dev/null
+++ b/src/renderer/layouts/responsive-layout.tsx
@@ -0,0 +1,17 @@
+import { useIsMobile } from '/@/renderer/hooks/use-is-mobile';
+import { DefaultLayout } from '/@/renderer/layouts/default-layout';
+import { MobileLayout } from '/@/renderer/layouts/mobile-layout/mobile-layout';
+
+interface ResponsiveLayoutProps {
+ shell?: boolean;
+}
+
+export const ResponsiveLayout = ({ shell }: ResponsiveLayoutProps) => {
+ const isMobile = useIsMobile();
+
+ if (isMobile) {
+ return
;
+ }
+
+ return
;
+};
diff --git a/src/renderer/router/app-router.tsx b/src/renderer/router/app-router.tsx
index 9c59848f3..5371989d7 100644
--- a/src/renderer/router/app-router.tsx
+++ b/src/renderer/router/app-router.tsx
@@ -6,7 +6,7 @@ import { AppRoute } from './routes';
import { RouterErrorBoundary } from '/@/renderer/components/error-boundary/router-error-boundary';
import { AddToPlaylistContextModal } from '/@/renderer/features/playlists/components/add-to-playlist-context-modal';
import { ShareItemContextModal } from '/@/renderer/features/sharing/components/share-item-context-modal';
-import { DefaultLayout } from '/@/renderer/layouts/default-layout';
+import { ResponsiveLayout } from '/@/renderer/layouts/responsive-layout';
import { AppOutlet } from '/@/renderer/router/app-outlet';
import { TitlebarOutlet } from '/@/renderer/router/titlebar-outlet';
import { BaseContextModal, ModalsProvider } from '/@/shared/components/modal/modal';
@@ -85,7 +85,7 @@ export const AppRouter = () => {
}>
} errorElement={}>
- }>
+ }>
}
errorElement={}
@@ -206,7 +206,7 @@ export const AppRouter = () => {
}>
- }>
+ }>
}
path={AppRoute.ACTION_REQUIRED}
diff --git a/src/shared/components/drawer/drawer.tsx b/src/shared/components/drawer/drawer.tsx
new file mode 100644
index 000000000..95536bbfe
--- /dev/null
+++ b/src/shared/components/drawer/drawer.tsx
@@ -0,0 +1,10 @@
+import { Drawer as MantineDrawer, DrawerProps as MantineDrawerProps } from '@mantine/core';
+import { ReactNode } from 'react';
+
+interface DrawerProps extends MantineDrawerProps {
+ children?: ReactNode;
+}
+
+export const Drawer = ({ children, ...props }: DrawerProps) => {
+ return {children};
+};