hidden shit, and online status bars, search settings
This commit is contained in:
18
src/app/components/setting-tile/SettingTile.css.ts
Normal file
18
src/app/components/setting-tile/SettingTile.css.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { keyframes, style } from '@vanilla-extract/css';
|
||||
import { color, config } from 'folds';
|
||||
|
||||
const focusPulse = keyframes({
|
||||
'0%': {
|
||||
outlineColor: color.Primary.Main,
|
||||
},
|
||||
'100%': {
|
||||
outlineColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
export const SettingTileFocus = style({
|
||||
borderRadius: config.radii.R400,
|
||||
outline: `${config.borderWidth.B300} solid ${color.Primary.Main}`,
|
||||
outlineOffset: config.space.S100,
|
||||
animation: `${focusPulse} 1.2s ease-out forwards`,
|
||||
});
|
||||
@@ -1,6 +1,9 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
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;
|
||||
@@ -8,10 +11,65 @@ type SettingTileProps = {
|
||||
before?: ReactNode;
|
||||
after?: ReactNode;
|
||||
children?: ReactNode;
|
||||
/** Override auto-generated scroll anchor. */
|
||||
anchorId?: string;
|
||||
};
|
||||
export function SettingTile({ title, description, before, after, children }: SettingTileProps) {
|
||||
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 alignItems="Center" gap="300">
|
||||
<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 && (
|
||||
|
||||
32
src/app/components/setting-tile/SettingsFocus.tsx
Normal file
32
src/app/components/setting-tile/SettingsFocus.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export type SettingsFocus = {
|
||||
/** Stable anchor id for a setting tile (from settingAnchorId). */
|
||||
anchorId?: string;
|
||||
setAnchorId: (anchorId: string | undefined) => void;
|
||||
};
|
||||
|
||||
const SettingsFocusContext = createContext<SettingsFocus | null>(null);
|
||||
|
||||
export const SettingsFocusProvider = SettingsFocusContext.Provider;
|
||||
|
||||
export const useSettingsFocus = (): SettingsFocus => {
|
||||
const ctx = useContext(SettingsFocusContext);
|
||||
if (!ctx) {
|
||||
return {
|
||||
anchorId: undefined,
|
||||
setAnchorId: () => undefined,
|
||||
};
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
|
||||
/** Stable DOM id for a settings tile title. */
|
||||
export function settingAnchorId(title: string): string {
|
||||
const slug = title
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
return `settings-item-${slug || 'untitled'}`;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './SettingTile';
|
||||
export * from './SettingsFocus';
|
||||
|
||||
Reference in New Issue
Block a user