Files
Desktop/ts/axo/AxoScrollArea.dom.stories.tsx
2025-10-29 10:21:15 -07:00

275 lines
8.3 KiB
TypeScript

// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReactNode } from 'react';
import React, { useMemo } from 'react';
import type { Meta } from '@storybook/react';
import { AxoScrollArea } from './AxoScrollArea.dom.js';
import { tw } from './tw.dom.js';
import { getScrollbarGutters } from './_internal/scrollbars.dom.js';
import { AxoSymbol } from './AxoSymbol.dom.js';
export default {
title: 'Axo/AxoScrollArea',
} satisfies Meta;
function Box(props: { children: ReactNode }) {
return (
<div
className={tw(
'flex items-center justify-center rounded-2xl bg-color-fill-primary p-10 type-title-large font-semibold text-label-primary-on-color'
)}
>
{props.children}
</div>
);
}
function MaybeMask(props: { mask?: boolean; children: ReactNode }) {
if (props.mask) {
return <AxoScrollArea.Mask>{props.children}</AxoScrollArea.Mask>;
}
return <>{props.children}</>;
}
function VerticalTemplate(props: {
items: number;
centered?: boolean;
fit?: boolean;
hints?: boolean;
mask?: boolean;
}) {
const paddingInline = useMemo(() => {
return getScrollbarGutters('thin', 'custom').vertical;
}, []);
return (
<div className={tw('w-64 rounded-2xl bg-background-secondary')}>
<h1
className={tw('pt-3 pb-2 type-title-large')}
style={{ paddingInline }}
>
Header
</h1>
<div className={tw(props.fit || 'h-100')}>
<AxoScrollArea.Root
scrollbarWidth="thin"
maxHeight={props.fit ? 400 : undefined}
>
{props.hints && <AxoScrollArea.Hint edge="top" />}
{props.hints && <AxoScrollArea.Hint edge="bottom" />}
<MaybeMask mask={props.mask}>
<AxoScrollArea.Viewport>
<AxoScrollArea.Content>
<div
className={tw(
'flex flex-col gap-2',
props.centered && 'min-h-full justify-center'
)}
>
{Array.from({ length: props.items }, (_, index) => {
return <Box key={index}>{index + 1}</Box>;
})}
</div>
</AxoScrollArea.Content>
</AxoScrollArea.Viewport>
</MaybeMask>
</AxoScrollArea.Root>
</div>
<p className={tw('pt-2 pb-3 type-title-large')} style={{ paddingInline }}>
Footer
</p>
</div>
);
}
function VerticalVariants(props: { mask?: boolean; hints?: boolean }) {
return (
<div className={tw('flex w-fit flex-row gap-2')}>
<VerticalTemplate {...props} items={10} />
<VerticalTemplate {...props} items={10} centered />
<VerticalTemplate {...props} items={10} fit />
<VerticalTemplate {...props} items={2} />
<VerticalTemplate {...props} items={2} centered />
<VerticalTemplate {...props} items={2} fit />
<VerticalTemplate {...props} items={0} />
<VerticalTemplate {...props} items={0} centered />
<VerticalTemplate {...props} items={0} fit />
</div>
);
}
export function Vertical(): JSX.Element {
return <VerticalVariants />;
}
export function VerticalWithHints(): JSX.Element {
return <VerticalVariants hints />;
}
export function VerticalWithMask(): JSX.Element {
return <VerticalVariants mask />;
}
function HorizontalTemplate(props: {
items: number;
centered?: boolean;
fit?: boolean;
hints?: boolean;
mask?: boolean;
}) {
return (
<div
className={tw(
'flex h-32 w-fit flex-row rounded-2xl bg-background-secondary'
)}
>
<div className={tw('flex flex-col justify-center p-4')}>
<AxoSymbol.Icon label={null} symbol="arrow-[start]" size={24} />
</div>
<div className={tw(props.fit || 'w-100')}>
<AxoScrollArea.Root
orientation="horizontal"
scrollbarWidth="thin"
maxWidth={props.fit ? 400 : undefined}
>
{props.hints && <AxoScrollArea.Hint edge="inline-start" />}
{props.hints && <AxoScrollArea.Hint edge="inline-end" />}
<MaybeMask mask={props.mask}>
<AxoScrollArea.Viewport>
<AxoScrollArea.Content>
<div
className={tw(
'flex h-full flex-row items-stretch gap-2',
props.centered && 'justify-center-safe'
)}
>
{Array.from({ length: props.items }, (_, index) => {
return <Box key={index}>{index + 1}</Box>;
})}
</div>
</AxoScrollArea.Content>
</AxoScrollArea.Viewport>
</MaybeMask>
</AxoScrollArea.Root>
</div>
<div className={tw('flex flex-col justify-center p-4')}>
<AxoSymbol.Icon label={null} symbol="arrow-[end]" size={24} />
</div>
</div>
);
}
function HorizontalVariants(props: { mask?: boolean; hints?: boolean }) {
return (
<div className={tw('flex flex-col gap-2')}>
<HorizontalTemplate {...props} items={10} />
<HorizontalTemplate {...props} items={10} centered />
<HorizontalTemplate {...props} items={10} fit />
<HorizontalTemplate {...props} items={2} />
<HorizontalTemplate {...props} items={2} centered />
<HorizontalTemplate {...props} items={2} fit />
<HorizontalTemplate {...props} items={0} />
</div>
);
}
export function Horizontal(): JSX.Element {
return <HorizontalVariants />;
}
export function HorizontalWithHints(): JSX.Element {
return <HorizontalVariants hints />;
}
export function HorizontalWithMask(): JSX.Element {
return <HorizontalVariants mask />;
}
function BothTemplate(props: {
cols: number;
rows: number;
centered?: boolean;
fit?: boolean;
hints?: boolean;
mask?: boolean;
}) {
return (
<div
className={tw(
props.fit || 'size-100',
'rounded-lg bg-background-secondary'
)}
>
<AxoScrollArea.Root
orientation="both"
scrollbarWidth="thin"
scrollbarGutter="stable-both-edges"
maxWidth={props.fit ? 400 : undefined}
maxHeight={props.fit ? 400 : undefined}
>
{props.hints && <AxoScrollArea.Hint edge="top" />}
{props.hints && <AxoScrollArea.Hint edge="bottom" />}
{props.hints && <AxoScrollArea.Hint edge="inline-start" />}
{props.hints && <AxoScrollArea.Hint edge="inline-end" />}
<MaybeMask mask={props.mask}>
<AxoScrollArea.Viewport>
<AxoScrollArea.Content>
<div
className={tw(
'flex flex-col gap-2',
props.centered && 'min-h-full justify-center'
)}
>
{Array.from({ length: props.rows }, (_, row) => {
return (
<div
key={row}
className={tw(
'flex flex-row gap-2',
props.centered && 'justify-center'
)}
>
{Array.from({ length: props.cols }, (_2, col) => {
return <Box key={col}>{col + 1}</Box>;
})}
</div>
);
})}
</div>
</AxoScrollArea.Content>
</AxoScrollArea.Viewport>
</MaybeMask>
</AxoScrollArea.Root>
</div>
);
}
function BothVariants(props: { mask?: boolean; hints?: boolean }) {
return (
<div className={tw('flex flex-col items-start justify-start gap-2')}>
<BothTemplate {...props} cols={10} rows={10} />
<BothTemplate {...props} cols={10} rows={10} centered />
<BothTemplate {...props} cols={10} rows={10} fit />
<BothTemplate {...props} cols={2} rows={2} />
<BothTemplate {...props} cols={2} rows={2} centered />
<BothTemplate {...props} cols={2} rows={2} fit />
<BothTemplate {...props} cols={0} rows={0} />
<BothTemplate {...props} cols={0} rows={0} centered />
<BothTemplate {...props} cols={0} rows={0} fit />
</div>
);
}
export function Both(): JSX.Element {
return <BothVariants />;
}
export function BothWithHints(): JSX.Element {
return <BothVariants hints />;
}
export function BothWithMask(): JSX.Element {
return <BothVariants mask />;
}