91 lines
2.4 KiB
TypeScript
91 lines
2.4 KiB
TypeScript
import React, { ReactNode, useEffect, useRef, useState } from 'react';
|
|
import { Box, Text } from 'folds';
|
|
import classNames from 'classnames';
|
|
import { BreakWord } from '../../styles/Text.css';
|
|
import { settingAnchorId, useSettingsFocus } from './SettingsFocus';
|
|
import { SettingTileFocus } from './SettingTile.css';
|
|
|
|
type SettingTileProps = {
|
|
title?: ReactNode;
|
|
description?: ReactNode;
|
|
before?: ReactNode;
|
|
after?: ReactNode;
|
|
children?: ReactNode;
|
|
/** Override auto-generated scroll anchor. */
|
|
anchorId?: string;
|
|
};
|
|
export function SettingTile({
|
|
title,
|
|
description,
|
|
before,
|
|
after,
|
|
children,
|
|
anchorId: anchorIdProp,
|
|
}: SettingTileProps) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const { anchorId: focusAnchorId, setAnchorId } = useSettingsFocus();
|
|
const [highlighted, setHighlighted] = useState(false);
|
|
|
|
const autoAnchorId = typeof title === 'string' ? settingAnchorId(title) : undefined;
|
|
const anchorId = anchorIdProp ?? autoAnchorId;
|
|
|
|
useEffect(() => {
|
|
if (!anchorId || !focusAnchorId || anchorId !== focusAnchorId) return;
|
|
let cancelled = false;
|
|
let attempts = 0;
|
|
|
|
const run = () => {
|
|
if (cancelled) return;
|
|
const node = ref.current;
|
|
if (node) {
|
|
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
setHighlighted(true);
|
|
setAnchorId(undefined);
|
|
return;
|
|
}
|
|
if (attempts < 40) {
|
|
attempts += 1;
|
|
window.requestAnimationFrame(run);
|
|
}
|
|
};
|
|
|
|
const timer = window.setTimeout(run, 50);
|
|
return () => {
|
|
cancelled = true;
|
|
window.clearTimeout(timer);
|
|
};
|
|
}, [anchorId, focusAnchorId, setAnchorId]);
|
|
|
|
useEffect(() => {
|
|
if (!highlighted) return;
|
|
const timer = window.setTimeout(() => setHighlighted(false), 1400);
|
|
return () => window.clearTimeout(timer);
|
|
}, [highlighted]);
|
|
|
|
return (
|
|
<Box
|
|
ref={ref}
|
|
id={anchorId}
|
|
alignItems="Center"
|
|
gap="300"
|
|
className={classNames(highlighted && SettingTileFocus)}
|
|
>
|
|
{before && <Box shrink="No">{before}</Box>}
|
|
<Box grow="Yes" direction="Column" gap="100">
|
|
{title && (
|
|
<Text className={BreakWord} size="T300">
|
|
{title}
|
|
</Text>
|
|
)}
|
|
{description && (
|
|
<Text className={BreakWord} size="T200" priority="300">
|
|
{description}
|
|
</Text>
|
|
)}
|
|
{children}
|
|
</Box>
|
|
{after && <Box shrink="No">{after}</Box>}
|
|
</Box>
|
|
);
|
|
}
|