mobile
This commit is contained in:
@@ -10,8 +10,8 @@ import { useAuth } from 'hooks/useAuth';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
|
||||
const initialState: LoginFormState = {
|
||||
email: 'pastilhas@pastilhas.dev',
|
||||
password: '1234567890',
|
||||
// email: 'pastilhas@pastilhas.dev',
|
||||
// password: '1234567890',
|
||||
};
|
||||
export function Login() {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceLayout } from 'officerdev';
|
||||
@@ -55,7 +56,8 @@ export const Automation = () => {
|
||||
staleTime: Infinity,
|
||||
});
|
||||
const [savedSizes, setSavedSizes] = useUserState<number[] | null>('automation:chat-sizes', null);
|
||||
const [selection] = usePanelChannel<AutomationSelection>('automation:selected-capability', null);
|
||||
const [selection, setSelection] = usePanelChannel<AutomationSelection>('automation:selected-capability', null);
|
||||
const isMobile = useIsMobile();
|
||||
const editing = selection?.editing ?? false;
|
||||
const prevEditing = useRef(editing);
|
||||
|
||||
@@ -94,11 +96,21 @@ export const Automation = () => {
|
||||
[],
|
||||
);
|
||||
|
||||
const mobilePanelId = isMobile && selection ? 'automation-right' : undefined;
|
||||
const onMobileBack = useCallback(() => setSelection(null), [setSelection]);
|
||||
|
||||
if (!isFetched) return null;
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={handleLayoutChange} components={panelComponents} />
|
||||
<WorkspaceLayout
|
||||
layout={layout}
|
||||
onLayoutChange={handleLayoutChange}
|
||||
components={panelComponents}
|
||||
isMobile={isMobile}
|
||||
mobilePanelId={mobilePanelId}
|
||||
onMobileBack={mobilePanelId ? onMobileBack : null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import type { LayoutNode, SelectedSession } from 'officerdev';
|
||||
import { WorkspaceView } from 'officerdev';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { useWorkspacesState } from 'state/useWorkspacesState';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import { useChatSessions } from 'state/useChatSessions';
|
||||
@@ -37,6 +38,9 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => {
|
||||
const { sessions } = useChatSessions();
|
||||
const [, setSelected] = usePanelChannel<SelectedSession>('chat:selected-session', null);
|
||||
const rawWorkspace = useWorkspacesState<LayoutNode>('screens/chat', defaultLayout);
|
||||
const isMobile = useIsMobile();
|
||||
const navigate = useNavigate();
|
||||
const mobilePanelId = isMobile && (sessionId || isNew) ? 'chat-detail' : undefined;
|
||||
|
||||
// Normalize synchronously so the wrong panel never renders
|
||||
const workspace = useMemo(() => {
|
||||
@@ -64,7 +68,13 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => {
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceView workspace={workspace} />
|
||||
<WorkspaceView
|
||||
workspace={workspace}
|
||||
mobilePanelId={mobilePanelId}
|
||||
onMobilePanelChange={(id) => {
|
||||
if (!id) navigate('/chat', { replace: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,10 +11,10 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden h-dvh outline-none inset-0">
|
||||
<Header />
|
||||
<Header dockItems={visibleItems} />
|
||||
<section className="relative h-dvh snap-start overflow-hidden">
|
||||
<Background />
|
||||
<Dock items={visibleItems} />
|
||||
<Dock items={visibleItems} className="hidden md:flex" />
|
||||
<div className="absolute inset-0 z-2 pt-[52px] md:pt-[64px] pb-2 overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,81 @@
|
||||
import { Link } from 'react-router';
|
||||
import { useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router';
|
||||
import { Menu } from 'lucide-react';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import type { DockItem } from '../Dock';
|
||||
import { UserMenu } from './UserMenu';
|
||||
|
||||
export function Header() {
|
||||
type HeaderProps = {
|
||||
dockItems?: DockItem[];
|
||||
};
|
||||
|
||||
export function Header({ dockItems }: HeaderProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
const location = useLocation();
|
||||
|
||||
const isActive = (to: string) => (to === '/' ? location.pathname === '/' : location.pathname.startsWith(to));
|
||||
|
||||
return (
|
||||
<header className="fixed z-10 w-full">
|
||||
<div
|
||||
className="shrink-0 border-b backdrop-blur-xl shadow-lg px-3 py-2 md:px-6 md:py-3 flex items-center justify-between"
|
||||
style={{ backgroundColor: 'rgba(255, 255, 255, 0.25)', borderColor: 'rgba(255, 255, 255, 0.35)' }}
|
||||
>
|
||||
<Link to="/">
|
||||
<img
|
||||
src="/static/officer-logo.svg"
|
||||
alt="Officer"
|
||||
className="h-8 md:h-12 w-auto"
|
||||
style={{ transform: 'skew(-15deg, -2deg)' }}
|
||||
/>
|
||||
</Link>
|
||||
<div className="flex items-center gap-2">
|
||||
{isMobile && dockItems && (
|
||||
<button onClick={() => setOpen(true)} className="p-1.5 rounded-md hover:bg-white/20 transition-colors">
|
||||
<Menu className="h-5 w-5 text-white" />
|
||||
</button>
|
||||
)}
|
||||
<Link to="/">
|
||||
<img
|
||||
src="/static/officer-logo.svg"
|
||||
alt="Officer"
|
||||
className="h-8 md:h-12 w-auto"
|
||||
style={{ transform: 'skew(-15deg, -2deg)' }}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<UserMenu />
|
||||
</div>
|
||||
|
||||
{isMobile && dockItems && (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetContent side="left">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Navigation</SheetTitle>
|
||||
</SheetHeader>
|
||||
<nav className="flex flex-col gap-1 mt-4 px-2">
|
||||
{dockItems.map((item) => {
|
||||
const active = isActive(item.to);
|
||||
return (
|
||||
<Link
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
onClick={() => setOpen(false)}
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors ${active ? 'bg-white/20' : 'hover:bg-white/10'}`}
|
||||
>
|
||||
<div
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center shrink-0"
|
||||
style={{
|
||||
background: item.color,
|
||||
boxShadow: active ? `0 0 12px ${item.color}40` : 'none',
|
||||
}}
|
||||
>
|
||||
<item.icon className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<span className={`text-sm ${active ? 'text-white font-medium' : 'text-white/80'}`}>
|
||||
{item.label}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { useWorkspacesState } from 'state/useWorkspacesState';
|
||||
import type { LayoutNode, ProjectType } from 'officerdev';
|
||||
import {
|
||||
@@ -20,10 +21,20 @@ import { defaultLayout } from './defaultLayout';
|
||||
|
||||
export const ProjectListScreen = () => {
|
||||
const workspace = useWorkspacesState<LayoutNode>('screens/projects', defaultLayout);
|
||||
const isMobile = useIsMobile();
|
||||
const [selected, setSelected] = useGlobal<string | null>(SELECTED_PROJECT, null);
|
||||
const [creating] = useGlobal<boolean>(CREATING_PROJECT, false);
|
||||
const mobilePanelId = isMobile && (selected || creating) ? 'proj-home-right' : undefined;
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<WorkspaceView workspace={workspace} />
|
||||
<WorkspaceView
|
||||
workspace={workspace}
|
||||
mobilePanelId={mobilePanelId}
|
||||
onMobilePanelChange={(id) => {
|
||||
if (!id) setSelected(null);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import type { LayoutNode } from 'officerdev';
|
||||
import { WorkspaceView } from 'officerdev';
|
||||
import { WorkspaceView, SELECTED_WORKSPACE_KEY } from 'officerdev';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
import { useWorkspacesState } from 'state/useWorkspacesState';
|
||||
import { defaultLayout } from './defaultLayout';
|
||||
|
||||
export const WorkspacesScreen = () => {
|
||||
const workspace = useWorkspacesState<LayoutNode>('screens/workspaces', defaultLayout);
|
||||
const isMobile = useIsMobile();
|
||||
const [selected, setSelected] = useGlobal<string | null>(SELECTED_WORKSPACE_KEY, null);
|
||||
const mobilePanelId = isMobile && selected ? 'ws-home-right' : undefined;
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<WorkspaceView workspace={workspace} />
|
||||
<WorkspaceView
|
||||
workspace={workspace}
|
||||
mobilePanelId={mobilePanelId}
|
||||
onMobilePanelChange={(id) => {
|
||||
if (!id) setSelected(null);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user