220 lines
6.1 KiB
TypeScript
220 lines
6.1 KiB
TypeScript
import { style } from '@vanilla-extract/css';
|
||
import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
|
||
import { DefaultReset, color, config, toRem } from 'folds';
|
||
|
||
export const PageNavResizable = style({
|
||
position: 'relative',
|
||
flexShrink: 0,
|
||
flexGrow: 0,
|
||
});
|
||
|
||
export const PageNavResizeHandle = style({
|
||
position: 'absolute',
|
||
top: 0,
|
||
bottom: 0,
|
||
// Native default — sits across the (original) `<Line>` separator
|
||
// between page-nav and content. Web shifts the handle into the
|
||
// horseshoe void via an inline style override in `ResizablePageNav`.
|
||
right: -3,
|
||
width: 7,
|
||
cursor: 'col-resize',
|
||
zIndex: 1,
|
||
background: 'transparent',
|
||
touchAction: 'none',
|
||
outline: 'none',
|
||
selectors: {
|
||
'&::before': {
|
||
content: '""',
|
||
position: 'absolute',
|
||
top: '50%',
|
||
left: '50%',
|
||
width: 2,
|
||
height: toRem(36),
|
||
transform: 'translate(-50%, -50%)',
|
||
borderRadius: 1,
|
||
backgroundColor: color.Surface.OnContainer,
|
||
opacity: 0,
|
||
transition:
|
||
'opacity 140ms ease, height 140ms ease, width 140ms ease, background-color 140ms ease',
|
||
},
|
||
'&:hover::before, &:focus-visible::before': {
|
||
opacity: 0.25,
|
||
},
|
||
'&:focus-visible::before': {
|
||
backgroundColor: color.Primary.Main,
|
||
opacity: 0.45,
|
||
},
|
||
'&[data-dragging="true"]::before': {
|
||
opacity: 0.55,
|
||
height: toRem(48),
|
||
backgroundColor: color.Primary.Main,
|
||
},
|
||
// Limit feedback: when the user drags past a clamp the width stops
|
||
// moving and the indicator deforms to signal it. Min = spring crushed
|
||
// against a wall (slight squish, thicker). Max = rubber band stretched
|
||
// (taller, full opacity). Activates only during drag so the resting
|
||
// state stays calm.
|
||
'&[data-dragging="true"][data-at-min="true"]::before': {
|
||
height: toRem(28),
|
||
width: 3,
|
||
opacity: 0.85,
|
||
},
|
||
'&[data-dragging="true"][data-at-max="true"]::before': {
|
||
height: toRem(76),
|
||
width: 2,
|
||
opacity: 0.9,
|
||
},
|
||
},
|
||
});
|
||
|
||
export const PageNav = recipe({
|
||
variants: {
|
||
size: {
|
||
'500': {
|
||
width: toRem(320),
|
||
},
|
||
'400': {
|
||
width: toRem(256),
|
||
},
|
||
'300': {
|
||
width: toRem(222),
|
||
},
|
||
// Used by the Settings nav — ~1.43× the regular 300 (~317px =
|
||
// 1.3 × 1.1 over 222px). Settings labels are long
|
||
// ("Notifications", "Emojis & Stickers", "Developer Tools") and
|
||
// the 222px column truncated them; the wider column also gives
|
||
// the nested-horseshoe void gap on the right room to breathe.
|
||
'350': {
|
||
width: toRem(317),
|
||
},
|
||
},
|
||
},
|
||
defaultVariants: {
|
||
size: '400',
|
||
},
|
||
});
|
||
export type PageNavVariants = RecipeVariants<typeof PageNav>;
|
||
|
||
// Web-only horseshoe shell wrapping every page-nav's inner column.
|
||
// Previously carved rounded TR / BR corners against a void backdrop;
|
||
// the rounding was reverted while keeping the 12px void gap between
|
||
// page-nav and chat panel, so this class now just enforces an opaque
|
||
// Background bg + clips overflow. The bg keeps the page-nav header /
|
||
// footer painted even on routes (Bots, Channels) that don't paint
|
||
// anything of their own; `overflow:hidden` is defensive against any
|
||
// child that might bleed past the inner column. Applied conditionally
|
||
// in `PageNav` / `ResizablePageNav` below; native skips it entirely.
|
||
export const PageNavInnerWebHorseshoe = style({
|
||
overflow: 'hidden',
|
||
backgroundColor: color.Background.Container,
|
||
});
|
||
|
||
export const PageNavHeader = recipe({
|
||
base: {
|
||
padding: `0 ${config.space.S200} 0 ${config.space.S300}`,
|
||
flexShrink: 0,
|
||
selectors: {
|
||
'button&': {
|
||
cursor: 'pointer',
|
||
},
|
||
'button&[aria-pressed=true]': {
|
||
backgroundColor: color.Background.ContainerActive,
|
||
},
|
||
'button&:hover, button&:focus-visible': {
|
||
backgroundColor: color.Background.ContainerHover,
|
||
},
|
||
'button&:active': {
|
||
backgroundColor: color.Background.ContainerActive,
|
||
},
|
||
},
|
||
},
|
||
|
||
variants: {
|
||
outlined: {
|
||
true: {
|
||
borderBottomWidth: 1,
|
||
},
|
||
},
|
||
},
|
||
defaultVariants: {
|
||
outlined: true,
|
||
},
|
||
});
|
||
export type PageNavHeaderVariants = RecipeVariants<typeof PageNavHeader>;
|
||
|
||
export const PageNavContent = style({
|
||
// No `min-height: 100%`. It used to force this padded wrapper to at least
|
||
// the scroll viewport's height, but the div is transparent (the curtain on
|
||
// native / the PageNav inner column on web paints the background) so the
|
||
// fill was invisible — its only real effect was a ~1px scroll overflow:
|
||
// `100%` resolves against the fractional flex-parent height and rounds UP
|
||
// while the viewport's clientHeight rounds DOWN, leaving the list
|
||
// permanently scrollable by a hair even when it fits. That hair is the
|
||
// "useless" overscroll on native. Letting the wrapper size to its content
|
||
// means a short list has no scroll range at all (paired with the
|
||
// `overflow-y: auto` override on the Scroll in Page.tsx).
|
||
padding: config.space.S200,
|
||
paddingRight: 0,
|
||
paddingBottom: config.space.S700,
|
||
});
|
||
|
||
export const PageHeader = recipe({
|
||
base: {
|
||
paddingLeft: config.space.S400,
|
||
paddingRight: config.space.S200,
|
||
},
|
||
variants: {
|
||
balance: {
|
||
true: {
|
||
paddingLeft: config.space.S200,
|
||
},
|
||
},
|
||
outlined: {
|
||
true: {
|
||
borderBottomWidth: config.borderWidth.B300,
|
||
},
|
||
},
|
||
},
|
||
defaultVariants: {
|
||
outlined: true,
|
||
},
|
||
});
|
||
export type PageHeaderVariants = RecipeVariants<typeof PageHeader>;
|
||
|
||
export const PageContent = style([
|
||
DefaultReset,
|
||
{
|
||
paddingTop: config.space.S400,
|
||
paddingLeft: config.space.S400,
|
||
paddingRight: 0,
|
||
paddingBottom: toRem(100),
|
||
},
|
||
]);
|
||
|
||
export const PageHeroEmpty = style([
|
||
DefaultReset,
|
||
{
|
||
padding: config.space.S400,
|
||
borderRadius: config.radii.R400,
|
||
minHeight: toRem(450),
|
||
},
|
||
]);
|
||
|
||
export const PageHeroSection = style([
|
||
DefaultReset,
|
||
{
|
||
padding: '40px 0',
|
||
maxWidth: toRem(466),
|
||
width: '100%',
|
||
margin: 'auto',
|
||
},
|
||
]);
|
||
|
||
export const PageContentCenter = style([
|
||
DefaultReset,
|
||
{
|
||
maxWidth: toRem(964),
|
||
width: '100%',
|
||
margin: 'auto',
|
||
},
|
||
]);
|