mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-19 17:58:48 +00:00
275 lines
8.3 KiB
TypeScript
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 />;
|
|
}
|