update ui update and fix layout issue
This commit is contained in:
125
src/shared/hooks/useBreakpoint.ts
Normal file
125
src/shared/hooks/useBreakpoint.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
'use client';
|
||||
|
||||
import {useSyncExternalStore} from 'react';
|
||||
|
||||
/**
|
||||
* Shell breakpoints.
|
||||
*
|
||||
* These are LAYOUT decisions — they choose whether Loyaly AI is an inline
|
||||
* panel or a sheet, whether a table renders as rows or cards. Those are React
|
||||
* tree differences that CSS cannot express.
|
||||
*
|
||||
* Anything that CAN be done in CSS should be: use Grid's responsive `columns`
|
||||
* or a Tailwind breakpoint instead of reaching for this hook. Every consumer
|
||||
* here costs a client-side re-render on resize.
|
||||
*/
|
||||
export type Breakpoint =
|
||||
| 'mobile'
|
||||
| 'tablet'
|
||||
| 'laptop'
|
||||
| 'desktop'
|
||||
| 'ultrawide';
|
||||
|
||||
/**
|
||||
* Ordered widest-first — `read()` returns the first match.
|
||||
*
|
||||
* mobile <640 phones
|
||||
* tablet 640–1024
|
||||
* laptop 1024–1440
|
||||
* desktop 1440–1920
|
||||
* ultrawide >1920 content gets capped rather than stretched
|
||||
*/
|
||||
export const BREAKPOINT_MIN = {
|
||||
ultrawide: 1920,
|
||||
desktop: 1440,
|
||||
laptop: 1024,
|
||||
tablet: 640,
|
||||
mobile: 0,
|
||||
} as const;
|
||||
|
||||
const QUERIES: [Breakpoint, string][] = [
|
||||
['ultrawide', `(min-width: ${BREAKPOINT_MIN.ultrawide}px)`],
|
||||
['desktop', `(min-width: ${BREAKPOINT_MIN.desktop}px)`],
|
||||
['laptop', `(min-width: ${BREAKPOINT_MIN.laptop}px)`],
|
||||
['tablet', `(min-width: ${BREAKPOINT_MIN.tablet}px)`],
|
||||
];
|
||||
|
||||
function read(): Breakpoint {
|
||||
for (const [name, q] of QUERIES) {
|
||||
if (window.matchMedia(q).matches) return name;
|
||||
}
|
||||
return 'mobile';
|
||||
}
|
||||
|
||||
function subscribe(onChange: () => void) {
|
||||
const mqls = QUERIES.map(([, q]) => window.matchMedia(q));
|
||||
mqls.forEach((m) => m.addEventListener('change', onChange));
|
||||
return () => mqls.forEach((m) => m.removeEventListener('change', onChange));
|
||||
}
|
||||
|
||||
/**
|
||||
* SSR resolves to 'desktop'. The shell is flex-based, so when a narrow client
|
||||
* corrects on the first frame the only visible change is the Loyaly AI panel
|
||||
* dropping out — no reflow of the content column.
|
||||
*/
|
||||
export function useBreakpoint(): Breakpoint {
|
||||
return useSyncExternalStore(subscribe, read, () => 'desktop');
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the side nav renders as an inline rail. Below this AppShell moves
|
||||
* it into the drawer, which is also the point where the workspace shell has to
|
||||
* put the top bar back at the shell root — see (workspace)/layout.tsx.
|
||||
*
|
||||
* Must stay in sync with AppShell's own `mobileNav.breakpoint`, which is
|
||||
* 'sm' (640px) — the same line as the mobile/tablet boundary.
|
||||
*/
|
||||
export function isSideNavInline(bp: Breakpoint): boolean {
|
||||
return bp !== 'mobile';
|
||||
}
|
||||
|
||||
/**
|
||||
* True when Loyaly AI renders as a third column.
|
||||
*
|
||||
* Tablet is excluded deliberately: at 640–1024 a 320px panel leaves under
|
||||
* 400px of workspace once the rail is out, which is narrower than a single
|
||||
* chart needs. There it becomes a sheet, same as on mobile.
|
||||
*/
|
||||
export function isPanelInline(bp: Breakpoint): boolean {
|
||||
return bp === 'laptop' || bp === 'desktop' || bp === 'ultrawide';
|
||||
}
|
||||
|
||||
/** Loyaly AI panel width. Laptop trades panel width for workspace. */
|
||||
export function assistantWidth(bp: Breakpoint): number {
|
||||
if (bp === 'ultrawide') return 420;
|
||||
if (bp === 'desktop') return 380;
|
||||
return 320;
|
||||
}
|
||||
|
||||
/**
|
||||
* Columns for a four-up metric row.
|
||||
*
|
||||
* Four KPIs must lay out 4-up, 2×2, or stacked — never 3+1, which reads as a
|
||||
* broken grid. Grid's `repeat: 'fit'` cannot express that: at a ~800px
|
||||
* workspace it yields exactly three tracks whatever minWidth you choose, and
|
||||
* the fourth card drops to an orphan row. So the count is computed.
|
||||
*/
|
||||
export function metricColumns(bp: Breakpoint, isPanelOpen: boolean): number {
|
||||
if (bp === 'mobile') return 1;
|
||||
if (bp === 'tablet') return 2;
|
||||
if (bp === 'laptop') return 2;
|
||||
// Desktop and ultrawide fit four unless Loyaly AI is taking 380–420px.
|
||||
return isPanelInline(bp) && isPanelOpen ? 2 : 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Max content width above `desktop`.
|
||||
*
|
||||
* On a 2560px monitor an uncapped workspace stretches a 30-day line chart
|
||||
* across ~1900px, which flattens every trend it is supposed to show, and runs
|
||||
* body text past the ~90ch where reading breaks down. Capping and centring
|
||||
* costs nothing below the cap.
|
||||
*/
|
||||
export function contentMaxWidth(bp: Breakpoint): number | undefined {
|
||||
return bp === 'ultrawide' ? 1600 : undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user