Skip to content
82 changes: 49 additions & 33 deletions src/components/AppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
import { cn } from '@/lib/utils'
import { useIsMobile } from '@/hooks/use-mobile'
import { Outlet, Link, useLocation, useNavigate } from 'react-router'
import {
CanAccess,
Expand Down Expand Up @@ -443,7 +444,7 @@ function AppSidebar() {
</SidebarContent>

<SidebarFooter>
{/* Footer links — hidden when collapsed */}
{/* Footer links + version — hidden when collapsed */}
{!collapsed && (
<div className="border-t px-3 pt-3 pb-1 flex flex-col gap-0.5">
{FOOTER_LINKS.map(({ label, href }) => (
Expand All @@ -455,18 +456,13 @@ function AppSidebar() {
{label}
</Link>
))}
</div>
)}

<SupportPanelTrigger collapsed={collapsed} />

{!collapsed && (
<div className="px-3 pb-2">
<span className="text-xs text-muted-foreground/60">
<span className="text-xs text-muted-foreground/60 pt-1">
v{pkg.version}
</span>
</div>
)}

<SupportPanelTrigger collapsed={collapsed} />
</SidebarFooter>
</Sidebar>
)
Expand Down Expand Up @@ -594,6 +590,7 @@ function getBrowser(): string {

function SupportPanel() {
const { isOpen, close } = useContext(SupportPanelContext)
const isMobile = useIsMobile()
const { data: user } = useGetIdentity<{ name: string; email: string }>()
const location = useLocation()

Expand Down Expand Up @@ -722,23 +719,12 @@ function SupportPanel() {

const pageUrl = location.pathname

return (
const panelBody = (
<div
ref={outerRef}
className={cn(
'relative shrink-0 sticky top-0 h-svh overflow-hidden border-l bg-background transition-[width] duration-200 ease-in-out',
!isOpen && 'w-0 border-l-0'
)}
style={isOpen ? { width } : undefined}
aria-hidden={!isOpen}
ref={innerRef}
className="flex h-full w-full flex-col"
style={isMobile ? undefined : { width }}
>
{/* Drag handle */}
<div
onMouseDown={onMouseDown}
className="absolute left-0 top-0 h-full w-1.5 cursor-col-resize hover:bg-primary/20 transition-colors z-10"
/>

<div ref={innerRef} className="flex h-full flex-col" style={{ width }}>
{/* Panel header */}
<div className="flex h-14 shrink-0 items-center justify-between border-b px-4">
<div className="flex items-center gap-2">
Expand Down Expand Up @@ -1017,6 +1003,36 @@ function SupportPanel() {
)}
</div>
</div>
)

if (isMobile) {
if (!isOpen) return null

return (
<div
className="fixed inset-x-0 top-14 z-40 h-[calc(100svh-3.5rem)] border-l bg-background"
aria-hidden={!isOpen}
>
{panelBody}
</div>
)
}

return (
<div
ref={outerRef}
className={cn(
'relative shrink-0 sticky top-0 h-svh overflow-hidden border-l bg-background transition-[width] duration-200 ease-in-out',
!isOpen && 'w-0 border-l-0'
)}
style={isOpen ? { width } : undefined}
aria-hidden={!isOpen}
>
<div
onMouseDown={onMouseDown}
className="absolute left-0 top-0 h-full w-1.5 cursor-col-resize hover:bg-primary/20 transition-colors z-10"
/>
{panelBody}
</div>
)
}
Expand Down Expand Up @@ -1146,14 +1162,14 @@ function ShellHeader() {
<div className="min-w-0 shrink overflow-hidden">
<HeaderBreadcrumb />
</div>
{/* Search bar — hidden on mobile, visible sm+ */}
<div className="hidden sm:block shrink-0 max-w-sm w-full sm:ml-3">
{/* Search bar — hidden on mobile, visible tablet+ */}
<div className="hidden tablet:block shrink-0 max-w-sm w-full tablet:ml-3">
<SearchBar />
</div>
<div className="ml-auto flex items-center gap-1 shrink-0">
{/* Mobile search icon */}
<button
className="sm:hidden flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
className="tablet:hidden flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
onClick={openSearch}
aria-label="Search"
>
Expand All @@ -1162,12 +1178,11 @@ function ShellHeader() {
<ReportBugButton />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 px-2 sm:px-2.5 gap-1.5 font-semibold cursor-pointer">
{/* Avatar on mobile, full name on sm+ */}
<span className="flex sm:hidden size-7 rounded bg-primary items-center justify-center text-primary-foreground text-xs font-bold shrink-0">
<Button variant="ghost" className="h-9 px-2 mobile-lg:px-2.5 gap-1.5 font-semibold cursor-pointer">
<span className="flex mobile-lg:hidden size-7 rounded bg-primary items-center justify-center text-primary-foreground text-xs font-bold shrink-0">
{initials}
</span>
<span className="hidden sm:inline">{user?.name || 'User'}</span>
<span className="hidden mobile-lg:inline">{user?.name || 'User'}</span>
<ChevronDown className="size-3.5 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
Expand Down Expand Up @@ -1246,7 +1261,6 @@ function SidebarAutoCollapse() {
function AppShellInner({ children }: { children?: React.ReactNode }) {
const { open: sidebarOpen, setOpen: setSidebarOpen } = useSidebar()
const [panelOpen, setPanelOpen] = useState(false)
// Remember whether the sidebar was open when the panel was triggered
const sidebarWasOpen = useRef(false)

const openPanel = () => {
Expand All @@ -1261,7 +1275,9 @@ function AppShellInner({ children }: { children?: React.ReactNode }) {
}

return (
<SupportPanelContext.Provider value={{ isOpen: panelOpen, open: openPanel, close: closePanel }}>
<SupportPanelContext.Provider
value={{ isOpen: panelOpen, open: openPanel, close: closePanel }}
>
<SidebarAutoCollapse />
<AppSidebar />
<AppContent className="min-w-0">
Expand Down
13 changes: 12 additions & 1 deletion src/components/OcotilloPageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react'
import { Box, Skeleton, SxProps, Theme, Typography } from '@mui/material'
import { cn } from '@/lib/utils'

/** Shared CardHeader layout for Ocotillo list and show pages. */
export const ocotilloCardHeaderProps: { sx: SxProps<Theme> } = {
Expand All @@ -9,11 +10,13 @@ export const ocotilloCardHeaderProps: { sx: SxProps<Theme> } = {
gap: { xs: 1.5, md: 3 },
'.MuiCardHeader-content': {
alignSelf: 'flex-start',
minWidth: 0,
},
'.MuiCardHeader-action': {
alignSelf: { xs: 'flex-end', md: 'flex-start' },
mr: 0,
pt: { xs: 0.5, md: 1 },
maxWidth: '100%',
},
},
}
Expand Down Expand Up @@ -69,6 +72,14 @@ export function OcotilloHeaderButtons({
className?: string
}) {
return (
<div className={className ?? 'flex items-center gap-1.5'}>{children}</div>
<div
className={cn(
'flex max-w-full flex-wrap items-center justify-end gap-1.5',
'[&_button]:shrink-0',
className
)}
>
{children}
</div>
)
}
55 changes: 33 additions & 22 deletions src/components/card/CoreWellInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import { Box, Paper, Skeleton, Typography } from '@mui/material'
import { CORE_WELL_INFO_STATS_MIN_PX } from '@/constants/breakpoints'
import { IWell } from '@/interfaces/ocotillo'

const statCellSx = (index: number) => ({
px: 2,
py: 1.5,
borderColor: 'divider',
borderTop: index > 0 ? '1px solid' : 'none',
borderLeft: 'none',
[`@container (min-width: ${CORE_WELL_INFO_STATS_MIN_PX}px)`]: {
borderTop: 'none',
borderLeft: index > 0 ? '1px solid' : 'none',
},
})

const statsGridSx = {
display: 'grid',
gridTemplateColumns: '1fr',
[`@container (min-width: ${CORE_WELL_INFO_STATS_MIN_PX}px)`]: {
gridTemplateColumns: 'repeat(3, 1fr)',
},
} as const

export const CoreWellInfoCard = ({ well }: { well?: IWell }) => {
if (!well) {
return <LoadingBar />
Expand Down Expand Up @@ -34,18 +55,13 @@ export const CoreWellInfoCard = ({ well }: { well?: IWell }) => {
]

return (
<Paper elevation={2} sx={{ borderRadius: 2, overflow: 'hidden' }}>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
<Paper
elevation={2}
sx={{ borderRadius: 2, overflow: 'hidden', containerType: 'inline-size' }}
>
<Box sx={statsGridSx}>
{stats.map((stat, i) => (
<Box
key={stat.label}
sx={{
px: 2,
py: 1.5,
borderLeft: i > 0 ? '1px solid' : 'none',
borderColor: 'divider',
}}
>
<Box key={stat.label} sx={statCellSx(i)}>
<Typography
variant="caption"
color="text.secondary"
Expand All @@ -68,18 +84,13 @@ export const CoreWellInfoCard = ({ well }: { well?: IWell }) => {
}

const LoadingBar = () => (
<Paper elevation={2} sx={{ borderRadius: 2, overflow: 'hidden' }}>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
<Paper
elevation={2}
sx={{ borderRadius: 2, overflow: 'hidden', containerType: 'inline-size' }}
>
<Box sx={statsGridSx}>
{Array.from({ length: 3 }).map((_, i) => (
<Box
key={i}
sx={{
px: 2,
py: 1.5,
borderLeft: i > 0 ? '1px solid' : 'none',
borderColor: 'divider',
}}
>
<Box key={i} sx={statCellSx(i)}>
<Skeleton width="60%" height={14} sx={{ mb: 0.5 }} />
<Skeleton width="80%" height={18} />
</Box>
Expand Down
49 changes: 25 additions & 24 deletions src/components/editing/EditPanelLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,50 +57,51 @@ export function EditPanelLayout({
useEditPanelWidth(resizeEnabled)

if (pinPanel === 'sticky') {
const showPanelShell = open || !isMobile

return (
<div className={cn('flex', className)}>
<div className={cn('min-w-0 flex-1', isMobile && open && 'hidden')}>
{children}
</div>

{isMobile ? (
open ? (
<div
className={cn(
'fixed inset-x-0 top-14 z-30 w-full bg-background',
PANEL_VIEWPORT_HEIGHT
)}
>
{panel}
</div>
) : null
) : (
{showPanelShell ? (
<div
className={cn(
'relative shrink-0',
!isResizing && 'transition-[width] duration-200 ease-in-out',
!open && 'w-0 overflow-hidden'
isMobile
? cn(
'fixed inset-x-0 top-14 z-30 w-full bg-background',
PANEL_VIEWPORT_HEIGHT
)
: cn(
'relative shrink-0',
!isResizing &&
'transition-[width] duration-200 ease-in-out',
!open && 'w-0 overflow-hidden'
)
)}
style={open ? { width: panelWidth } : undefined}
style={!isMobile && open ? { width: panelWidth } : undefined}
aria-hidden={!open}
>
{!isMobile && open && resizeEnabled ? (
<EditPanelResizeHandle
panelWidth={panelWidth}
onResizeStart={handleResizeStart}
/>
) : null}
{open ? (
<div
className={cn(
'relative sticky top-0 w-full',
'relative w-full',
!isMobile && 'sticky top-0',
PANEL_VIEWPORT_HEIGHT
)}
>
{resizeEnabled ? (
<EditPanelResizeHandle
panelWidth={panelWidth}
onResizeStart={handleResizeStart}
/>
) : null}
{panel}
</div>
) : null}
</div>
)}
) : null}
</div>
)
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/ui/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ function Sidebar({

return (
<div
className="group peer hidden text-sidebar-foreground md:block"
className="group peer hidden text-sidebar-foreground tablet:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
Expand All @@ -210,7 +210,7 @@ function Sidebar({
data-slot="sidebar-container"
data-side={side}
className={cn(
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-100 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-100 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] tablet:flex",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
Expand Down Expand Up @@ -287,7 +287,7 @@ function AppContent({ className, ...props }: React.ComponentProps<"main">) {
<main
data-slot="app-content"
className={cn(
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
"relative flex w-full flex-1 flex-col bg-background tablet:peer-data-[variant=inset]:m-2 tablet:peer-data-[variant=inset]:ml-0 tablet:peer-data-[variant=inset]:rounded-xl tablet:peer-data-[variant=inset]:shadow-sm tablet:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className
)}
{...props}
Expand Down
Loading
Loading