feat(mobile): add responsive mobile support to dashboard (#19)
- Site header: compact search (icon-only on small screens), hide keyboard shortcut and feedback button text on mobile - Project detail: stack urgency columns and phases/tasks grid on mobile, show week agenda below content instead of hiding it, wrap client/PM row on narrow viewports - Schedule view: stack header title and tabs, collapse toolbar button labels to icons on mobile - File browser: reduce search input width on small screens - Dashboard home: tighter padding on small screens Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d4fcaa00bb
commit
06fcc5eacb
@ -84,8 +84,8 @@ export default async function Page() {
|
|||||||
const data = await getRepoData()
|
const data = await getRepoData()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 items-start justify-center p-6 md:p-12">
|
<div className="flex flex-1 items-start justify-center p-4 sm:p-6 md:p-12">
|
||||||
<div className="w-full max-w-6xl py-8">
|
<div className="w-full max-w-6xl py-4 sm:py-8">
|
||||||
<div className="mb-10 text-center">
|
<div className="mb-10 text-center">
|
||||||
<span
|
<span
|
||||||
className="mx-auto mb-3 block size-12 bg-foreground"
|
className="mx-auto mb-3 block size-12 bg-foreground"
|
||||||
|
|||||||
@ -132,8 +132,8 @@ export default async function ProjectSummaryPage({
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 min-h-0 overflow-hidden">
|
<div className="flex flex-col lg:flex-row flex-1 min-h-0 overflow-hidden">
|
||||||
<div className="flex-1 overflow-y-auto p-6">
|
<div className="flex-1 overflow-y-auto p-4 md:p-6">
|
||||||
{/* header */}
|
{/* header */}
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
@ -158,7 +158,7 @@ export default async function ProjectSummaryPage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* client / pm row */}
|
{/* client / pm row */}
|
||||||
<div className="flex gap-8 mb-6">
|
<div className="flex flex-wrap gap-4 sm:gap-8 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium uppercase text-muted-foreground mb-2">
|
<p className="text-xs font-medium uppercase text-muted-foreground mb-2">
|
||||||
Client
|
Client
|
||||||
@ -197,7 +197,7 @@ export default async function ProjectSummaryPage({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto self-end">
|
<div className="sm:ml-auto self-end w-full sm:w-auto">
|
||||||
<Link
|
<Link
|
||||||
href={`/dashboard/projects/${id}/schedule`}
|
href={`/dashboard/projects/${id}/schedule`}
|
||||||
className="text-sm text-primary hover:underline flex items-center gap-1.5"
|
className="text-sm text-primary hover:underline flex items-center gap-1.5"
|
||||||
@ -230,7 +230,7 @@ export default async function ProjectSummaryPage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* urgency columns */}
|
{/* urgency columns */}
|
||||||
<div className="grid grid-cols-3 gap-px rounded-lg border overflow-hidden mb-6">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-px rounded-lg border overflow-hidden mb-6">
|
||||||
<div className="p-4 bg-background">
|
<div className="p-4 bg-background">
|
||||||
<p className="text-xs font-medium uppercase text-muted-foreground mb-3">
|
<p className="text-xs font-medium uppercase text-muted-foreground mb-3">
|
||||||
Past Due
|
Past Due
|
||||||
@ -261,7 +261,7 @@ export default async function ProjectSummaryPage({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 bg-background border-x">
|
<div className="p-4 bg-background border-t sm:border-t-0 sm:border-x">
|
||||||
<p className="text-xs font-medium uppercase text-muted-foreground mb-3">
|
<p className="text-xs font-medium uppercase text-muted-foreground mb-3">
|
||||||
Due Today
|
Due Today
|
||||||
</p>
|
</p>
|
||||||
@ -279,7 +279,7 @@ export default async function ProjectSummaryPage({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 bg-background">
|
<div className="p-4 bg-background border-t sm:border-t-0">
|
||||||
<p className="text-xs font-medium uppercase text-muted-foreground mb-3">
|
<p className="text-xs font-medium uppercase text-muted-foreground mb-3">
|
||||||
Upcoming Milestones
|
Upcoming Milestones
|
||||||
</p>
|
</p>
|
||||||
@ -306,7 +306,7 @@ export default async function ProjectSummaryPage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* two-column: phases + active tasks */}
|
{/* two-column: phases + active tasks */}
|
||||||
<div className="grid grid-cols-2 gap-6 mb-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-6">
|
||||||
{/* phase breakdown */}
|
{/* phase breakdown */}
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xs font-medium uppercase text-muted-foreground mb-3">
|
<h2 className="text-xs font-medium uppercase text-muted-foreground mb-3">
|
||||||
@ -421,7 +421,7 @@ export default async function ProjectSummaryPage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* right sidebar: week agenda */}
|
{/* right sidebar: week agenda */}
|
||||||
<div className="w-72 border-l overflow-y-auto p-4 shrink-0 hidden lg:block">
|
<div className="w-full lg:w-72 border-t lg:border-t-0 lg:border-l overflow-y-auto p-4 shrink-0">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-xs font-medium uppercase text-muted-foreground">
|
<h2 className="text-xs font-medium uppercase text-muted-foreground">
|
||||||
This Week
|
This Week
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export function FileToolbar({
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className={`relative transition-all duration-200 ${
|
className={`relative transition-all duration-200 ${
|
||||||
searchFocused ? "w-64" : "w-44"
|
searchFocused ? "w-48 sm:w-64" : "w-32 sm:w-44"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<IconSearch
|
<IconSearch
|
||||||
|
|||||||
@ -25,8 +25,8 @@ export function ScheduleToolbar({ onNewItem }: ScheduleToolbarProps) {
|
|||||||
const [offlineMode, setOfflineMode] = useState(false)
|
const [offlineMode, setOfflineMode] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between py-1.5 border-b mb-2">
|
<div className="flex flex-wrap items-center justify-between gap-2 py-1.5 border-b mb-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-1 sm:gap-3">
|
||||||
<Button variant="ghost" size="icon" className="size-8">
|
<Button variant="ghost" size="icon" className="size-8">
|
||||||
<IconSettings className="size-4" />
|
<IconSettings className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -39,15 +39,15 @@ export function ScheduleToolbar({ onNewItem }: ScheduleToolbarProps) {
|
|||||||
onCheckedChange={setOfflineMode}
|
onCheckedChange={setOfflineMode}
|
||||||
className="scale-75"
|
className="scale-75"
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground hidden sm:inline">
|
||||||
Schedule Offline
|
Schedule Offline
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="text-xs">
|
<Button variant="ghost" size="sm" className="text-xs h-8">
|
||||||
<IconDots className="size-4 mr-1" />
|
<IconDots className="size-4 sm:mr-1" />
|
||||||
More Actions
|
<span className="hidden sm:inline">More Actions</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
@ -56,14 +56,14 @@ export function ScheduleToolbar({ onNewItem }: ScheduleToolbarProps) {
|
|||||||
<DropdownMenuItem>Print</DropdownMenuItem>
|
<DropdownMenuItem>Print</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Button variant="ghost" size="sm" className="text-xs">
|
<Button variant="ghost" size="sm" className="text-xs h-8">
|
||||||
<IconFilter className="size-4 mr-1" />
|
<IconFilter className="size-4 sm:mr-1" />
|
||||||
Filter
|
<span className="hidden sm:inline">Filter</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button size="sm" onClick={onNewItem}>
|
<Button size="sm" onClick={onNewItem}>
|
||||||
<IconPlus className="size-4 mr-1" />
|
<IconPlus className="size-4 sm:mr-1" />
|
||||||
New Schedule Item
|
<span className="hidden sm:inline">New Schedule Item</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -36,8 +36,8 @@ export function ScheduleView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-1 min-h-0">
|
<div className="flex flex-col flex-1 min-h-0">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-2">
|
||||||
<h1 className="text-lg font-semibold">
|
<h1 className="text-lg font-semibold truncate">
|
||||||
{projectName} - Schedule
|
{projectName} - Schedule
|
||||||
</h1>
|
</h1>
|
||||||
<Tabs
|
<Tabs
|
||||||
@ -48,7 +48,7 @@ export function ScheduleView({
|
|||||||
<TabsTrigger value="schedule">Schedule</TabsTrigger>
|
<TabsTrigger value="schedule">Schedule</TabsTrigger>
|
||||||
<TabsTrigger value="baseline">Baseline</TabsTrigger>
|
<TabsTrigger value="baseline">Baseline</TabsTrigger>
|
||||||
<TabsTrigger value="exceptions">
|
<TabsTrigger value="exceptions">
|
||||||
Workday Exceptions
|
<span className="hidden sm:inline">Workday </span>Exceptions
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@ -33,11 +33,11 @@ export function SiteHeader() {
|
|||||||
const [accountOpen, setAccountOpen] = React.useState(false)
|
const [accountOpen, setAccountOpen] = React.useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex h-14 shrink-0 items-center gap-2 border-b px-4">
|
<header className="flex h-14 shrink-0 items-center gap-2 border-b px-2 md:px-4">
|
||||||
<SidebarTrigger className="-ml-1" />
|
<SidebarTrigger className="-ml-1" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="relative mx-auto w-full max-w-md cursor-pointer"
|
className="relative mx-auto hidden min-[480px]:block w-full max-w-md cursor-pointer"
|
||||||
onClick={openCommand}
|
onClick={openCommand}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@ -50,17 +50,27 @@ export function SiteHeader() {
|
|||||||
<span className="text-muted-foreground flex-1">
|
<span className="text-muted-foreground flex-1">
|
||||||
Search...
|
Search...
|
||||||
</span>
|
</span>
|
||||||
<kbd className="bg-muted text-muted-foreground pointer-events-none ml-2 inline-flex h-5 items-center gap-0.5 rounded border px-1.5 font-mono text-xs">
|
<kbd className="bg-muted text-muted-foreground pointer-events-none ml-2 hidden sm:inline-flex h-5 items-center gap-0.5 rounded border px-1.5 font-mono text-xs">
|
||||||
<span className="text-xs">⌘</span>K
|
<span className="text-xs">⌘</span>K
|
||||||
</kbd>
|
</kbd>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile search button */}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="size-8 min-[480px]:hidden ml-auto"
|
||||||
|
onClick={openCommand}
|
||||||
|
>
|
||||||
|
<IconSearch className="size-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-muted-foreground text-xs"
|
className="text-muted-foreground text-xs hidden sm:inline-flex"
|
||||||
onClick={openFeedback}
|
onClick={openFeedback}
|
||||||
>
|
>
|
||||||
Feedback
|
Feedback
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user