update ui update and fix layout issue

This commit is contained in:
2026-08-06 13:21:10 +05:30
parent 92322bff16
commit e5f8144fb3
251 changed files with 9041 additions and 2119 deletions

View 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;
}