Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions components/grid-wallet-demo/src/app/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,24 @@ body {
[data-theme] flips, so the whole backdrop changes in lockstep (no stagger).
`--dot-grid-dot` is the resting dot; `--dot-grid-dot-peak` is the ripple crest. */
:root {
/* Light: surface-secondary is the sunken stage gray (#F0F0EE), like
grid-visualizer's dot panels. Dark overrides to gray-925 below — the WebGL
dots' original backdrop; surface-secondary's #111111 reads too dark. */
--dot-grid-bg: var(--surface-secondary);
/* Light: surface-primary (#F8F8F7) — the stage sits a step ABOVE the
chrome-gray side panels (surface-secondary), matching the raised cards.
Dark overrides below mirror it: gray-950 stage over #111111 chrome. */
--dot-grid-bg: var(--surface-primary);
--dot-grid-dot: #deded9;
--dot-grid-dot-peak: #b8b8b3;
}

/* 3-col layout: the light stage steps down a hair so it separates from the
raised cards flanking it. Keyed to html's data-layout attribute (set
pre-paint by layout.tsx, kept live by page.tsx) so the color rides the
same clock as the arrangement flip. Light-only — the dark stage is
gray-950 in both arrangements. The JS canvas re-reads the token on resize
(StageGL's ResizeObserver), which every arrangement flip triggers. */
html:not([data-theme='dark']):not([data-layout='stacked']) {
--dot-grid-bg: #f4f4f3;
}

:root {
--p-accent: #11a967;
--p-accent-press: #0e8f57;
Expand All @@ -176,9 +186,11 @@ body {
}

[data-theme='dark'] {
--dot-grid-bg: var(--color-gray-925);
--dot-grid-dot: #333330;
--dot-grid-dot-peak: #4d4d47;
/* gray-950 (#1A1A1A) — a step darker than the chrome-gray side panels'
raised cards, mirroring the light stage sitting under the panel surface. */
--dot-grid-bg: var(--color-gray-950);
--dot-grid-dot: #2d2d2a;
--dot-grid-dot-peak: #474741;
--p-bg: #000000;
--p-screen: #000000;
--p-surface: #141414;
Expand Down
13 changes: 12 additions & 1 deletion components/grid-wallet-demo/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import { GeistSans } from 'geist/font/sans';
import { easingVarsStylesheet } from '@/lib/easing';
import { CONFIGURE_COL_PX, LAYOUT_WIDE_PX } from '@/lib/layout';
import './globals.scss';

const TITLE = 'Grid Global Accounts — Live demo';
Expand Down Expand Up @@ -44,7 +45,14 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<link rel="preload" href="/fonts/SuisseIntl-Book.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
<link rel="preload" href="/fonts/SuisseIntl-Medium.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
<link rel="preload" href="/fonts/SuisseIntlMono-Regular-WebXL.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
{/* Set data-embed and an initial data-theme before paint to avoid a flash. */}
{/* Boot attributes, set before first paint to avoid flashes — an
effect is too late (the SSR HTML paints long before hydration):
- data-embed / data-theme from the URL (embed) or stored pref
- data-layout (stacked ⇄ 3-col) from the viewport width
- --api-col-default from the embed's ?nav param (the live docs
sidebar width), so the wide layout's code column paints at its
real default — sidebar + configure column — instead of the
expanded-sidebar assumption and re-fitting after hydration. */}
<script
dangerouslySetInnerHTML={{
__html: `(function(){try{
Expand All @@ -58,6 +66,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
t=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';
}
document.documentElement.setAttribute('data-theme',t);
document.documentElement.setAttribute('data-layout',window.innerWidth<${LAYOUT_WIDE_PX}?'stacked':'wide');
var nav=parseFloat(p.get('nav'));
if(isFinite(nav)&&nav>=0){document.documentElement.style.setProperty('--api-col-default',(Math.round(nav)+${CONFIGURE_COL_PX})+'px');}
}catch(e){}})();`,
}}
/>
Expand Down
64 changes: 45 additions & 19 deletions components/grid-wallet-demo/src/app/page.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,56 @@
display: flex;
flex-direction: column;
overflow: hidden;
/* Pre-hydration width: --api-col-default is set before first paint (from
the embed's ?nav param, see layout.tsx) so the wide layout boots at the
REAL default instead of re-fitting once React measures. The 680px
fallback is the standalone default — keep in sync with API_DEFAULT_WIDTH
in useColumnResize. React's inline width takes over after hydration. */
width: var(--api-col-default, 680px);
}

/* Laptop: configure left, app over API on the right. Floored at the mobile
breakpoint so these rules never leak into the mobile single-column layout. */
@media (min-width: ($breakpoint-layout-mobile + 1px)) and (max-width: ($breakpoint-layout-wide - 1px)) {
.stackCol {
flex-direction: column;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
/* Side-by-side: nav-driven width changes (the default following a docs
sidebar collapse/expand) opt INTO the ease via data-easing — set in the
same commit as the width change, never by default. An always-on transition
would race the stacked ⇄ 3-col media flip and hold the column at its full
stacked width inside the fresh row layout for a frame or two, crushing the
app column to zero (the phone blinks out). Drags always track 1:1. */
@media (min-width: $breakpoint-layout-wide) {
.apiCol[data-easing]:not([data-resizing]) {
transition: width 240ms cubic-bezier(0.25, 0.1, 0.25, 1);
}
}

.appCol {
flex: none;
width: 100% !important;
min-height: calc(100% - var(--panel-header-height, 52px));
overflow: visible;
}
/* Laptop: configure left, app over API on the right. Keyed to html's
data-layout attribute (set pre-paint by the layout.tsx inline script, kept
live by page.tsx) instead of a media query, so the arrangement and the
attribute-keyed chrome colors flip on one clock. Floored at the mobile
breakpoint so these rules never leak into the mobile single-column layout
(whose media rules set the same properties at lower specificity). */
@media (min-width: ($breakpoint-layout-mobile + 1px)) {
:global(html[data-layout='stacked']) {
.stackCol {
flex-direction: column;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
}

.apiCol {
flex: none;
width: 100% !important;
min-height: 0;
overflow: visible;
.appCol {
flex: none;
width: 100% !important;
/* Peek the API panel's header PLUS a strip of its body under the phone —
enough to read as content waiting below, not just a bar. */
min-height: calc(100% - var(--panel-header-height, 52px) - 64px);
overflow: visible;
}

.apiCol {
flex: none;
width: 100% !important;
min-height: 0;
overflow: visible;
}
}
}

Expand Down
31 changes: 27 additions & 4 deletions components/grid-wallet-demo/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import clsx from 'clsx';
import { IconArrowRight } from '@central-icons-react/round-outlined-radius-3-stroke-1.5/IconArrowRight';
import { IconArrowLeft } from '@central-icons-react/round-outlined-radius-3-stroke-1.5/IconArrowLeft';
Expand All @@ -11,6 +11,7 @@ import { ColumnResizeHandle } from '@/components/ColumnResizeHandle/ColumnResize
import { ThemeSync } from '@/components/ThemeSync';
import { useColumnResize } from '@/hooks/useColumnResize';
import { useWalletDemoLogic } from '@/hooks/useWalletDemoLogic';
import { LAYOUT_WIDE_PX } from '@/lib/layout';
import type { ActionId } from '@/data/actions';
import styles from './page.module.scss';

Expand All @@ -33,7 +34,21 @@ function withViewTransition(update: () => void) {

export default function Page() {
const logic = useWalletDemoLogic();
const { layoutRef, apiColRef, apiWidth, onResizeStart } = useColumnResize();
const { layoutRef, apiColRef, apiWidth, resizing, onResizeStart } = useColumnResize();

// Stacked ⇄ 3-col as a data-layout attribute on <html> so the arrangement,
// the panel chrome colors, and the API header all flip on one clock. The
// inline script in layout.tsx sets it BEFORE first paint (an effect here
// would flash the SSR wide default on stacked viewports while JS loads);
// this listener only keeps it live across resizes.
useLayoutEffect(() => {
const mql = window.matchMedia(`(max-width: ${LAYOUT_WIDE_PX - 1}px)`);
const apply = () =>
document.documentElement.setAttribute('data-layout', mql.matches ? 'stacked' : 'wide');
apply();
mql.addEventListener('change', apply);
return () => mql.removeEventListener('change', apply);
}, []);

// Mobile only (<=767): the layout collapses to one view at a time. On desktop
// this stays 'configure' forever (the switch is gated to the mobile viewport),
Expand Down Expand Up @@ -93,7 +108,11 @@ export default function Page() {
}, [mobileView]);

return (
<main ref={layoutRef} className={styles.layout} data-mobile-view={mobileView}>
<main
ref={layoutRef}
className={styles.layout}
data-mobile-view={mobileView}
>
<ThemeSync />
<div className={styles.configCol}>
<ConfigurePanel
Expand Down Expand Up @@ -153,7 +172,11 @@ export default function Page() {
<div
ref={apiColRef}
className={styles.apiCol}
style={{ width: apiWidth, flex: '0 0 auto' }}
data-resizing={resizing || undefined}
// No inline width until the post-hydration measure — the stylesheet's
// var(--api-col-default) (seeded pre-paint from the embed's ?nav
// param) styles the column so the first paint is already correct.
style={{ width: apiWidth ?? undefined }}
>
<ApiPanel entries={logic.entries} />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
display: flex;
flex-direction: column;
width: 100%;
padding: 48px;
/* Sides match the Configure panel's content padding. */
padding: 48px $layout-laptop-content-padding;
padding-top: calc(var(--spacing-md) + 12px);

// Mobile: match the Configure panel's 24px side padding.
Expand Down Expand Up @@ -101,16 +102,19 @@
corner-shape: var(--corner-shape);
--badge-height: 20px;
--row-padding: calc((48px - var(--badge-height)) / 2);
/* Single source for the card surface — the endpoint fade + tab indicator
must match it exactly, and the wide layout re-tints all three at once. */
--call-card-bg: var(--surface-base);
display: flex;
flex-direction: column;
width: 100%;
background: var(--surface-base);
background: var(--call-card-bg);
border: 0.5px solid var(--border-tertiary);
border-radius: var(--corner-radius-md);
overflow: clip;

:global([data-theme='dark']) & {
background: var(--color-gray-925);
--call-card-bg: var(--color-gray-925);
}

&:hover,
Expand Down Expand Up @@ -189,11 +193,7 @@
pointer-events: none;
opacity: 0;
transition: opacity 150ms ease;
background: linear-gradient(to right, transparent, var(--surface-base) 52%);

:global([data-theme='dark']) & {
background: linear-gradient(to right, transparent, var(--color-gray-925) 52%);
}
background: linear-gradient(to right, transparent, var(--call-card-bg) 52%);
}

.endpointCopyBtn {
Expand Down Expand Up @@ -253,12 +253,8 @@
box-sizing: border-box;
border-left: var(--stroke-xs) solid var(--border-tertiary);
border-right: var(--stroke-xs) solid var(--border-tertiary);
background: var(--surface-base);
background: var(--call-card-bg);
pointer-events: none;

:global([data-theme='dark']) & {
background: var(--color-gray-925);
}
}

.tabGroupLeadingActive .tabIndicator {
Expand Down Expand Up @@ -371,6 +367,19 @@
}
}

/* Side-by-side the panel wears the docs-chrome gray, so the cards step down
with it: pure white (light) / gray-925 (dark) read too hot on the chrome —
one notch softer keeps the same card-to-panel delta as the stacked layout. */
@media (min-width: $breakpoint-layout-wide) {
.callCard {
--call-card-bg: var(--surface-primary);

:global([data-theme='dark']) & {
--call-card-bg: var(--color-gray-950);
}
}
}

@include layout-laptop {
.list {
align-items: center;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
flex-direction: column;
background: var(--surface-primary);
overflow: hidden;
/* Chrome gray only side-by-side — glide the color across the crossing. */
transition: background-color 240ms cubic-bezier(0.19, 1, 0.22, 1);

:global([data-theme='dark']) & {
background: var(--color-gray-950);
Expand Down Expand Up @@ -57,36 +59,38 @@
}
}

@media (max-width: ($breakpoint-layout-wide - 1px)) {
.panel {
overflow: visible;
}

.body {
overflow: visible;
}
/* Keyed to html's data-layout attribute (set pre-paint by layout.tsx, kept
live by page.tsx) rather than the media query, so the panel's structure
and chrome flip on the same clock as the arrangement. The attribute is
'stacked' on laptop AND mobile, matching the old max-width media block. */
:global(html[data-layout='stacked']) .panel {
overflow: visible;
}

.headerStacked {
border-top: var(--stroke-xs) solid var(--border-primary);
}
:global(html[data-layout='stacked']) .body {
overflow: visible;
}

@media (min-width: $breakpoint-layout-wide) {
.panel {
overflow-y: auto;
overscroll-behavior: contain;
}
:global(html[data-layout='stacked']) .headerStacked {
border-top: var(--stroke-xs) solid var(--border-primary);
}

.body {
overflow: visible;
}
/* Side-by-side. */
:global(html:not([data-layout='stacked'])) .panel {
overflow-y: auto;
overscroll-behavior: contain;
/* The calls column wears the docs-chrome gray. surface-secondary flips
with the theme, and this selector outweighs the base dark override. */
background: var(--surface-secondary);
}

.headerStacked {
border-left: var(--stroke-xs) solid var(--border-primary);
}
:global(html:not([data-layout='stacked'])) .body {
overflow: visible;
}

/* Side-by-side: calls are always in view, so no "new" signifier. */
.newPill {
display: none;
}
/* Side-by-side: the calls ARE the column — no header bar needed (it only
earns its keep stacked, where it's the peek target + new-call signifier).
The resize handle draws the full-height column divider. */
:global(html:not([data-layout='stacked'])) .headerStacked {
display: none;
}
Loading
Loading