2026-09-18 19:19:54 -04:00
|
|
|
import React, { ReactNode, useEffect, useId, useRef } from 'react';
|
2025-02-10 16:49:47 +11:00
|
|
|
import { Box, Text } from 'folds';
|
|
|
|
|
import { BreakWord } from '../../styles/Text.css';
|
|
|
|
|
|
|
|
|
|
type SettingTileProps = {
|
|
|
|
|
title?: ReactNode;
|
|
|
|
|
description?: ReactNode;
|
|
|
|
|
before?: ReactNode;
|
|
|
|
|
after?: ReactNode;
|
|
|
|
|
children?: ReactNode;
|
|
|
|
|
};
|
|
|
|
|
export function SettingTile({ title, description, before, after, children }: SettingTileProps) {
|
2026-09-18 19:19:54 -04:00
|
|
|
const titleId = useId();
|
|
|
|
|
const afterRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
// [Gitea #185] The control in `after` is almost always an icon-only Switch
|
|
|
|
|
// (or a bare select/input) whose only visible label is this tile's title.
|
|
|
|
|
// Point it at the title so screen readers announce "Show Notifications,
|
|
|
|
|
// switch, on" instead of "switch, on". Explicit labels are left alone.
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!title || !afterRef.current) return;
|
|
|
|
|
afterRef.current
|
|
|
|
|
.querySelectorAll<HTMLElement>('[role="switch"], input, select, textarea, [role="combobox"]')
|
|
|
|
|
.forEach((el) => {
|
|
|
|
|
if (el.hasAttribute('aria-label') || el.hasAttribute('aria-labelledby')) return;
|
|
|
|
|
if (
|
|
|
|
|
el.tagName === 'INPUT' &&
|
|
|
|
|
el.id &&
|
|
|
|
|
afterRef.current?.querySelector(`label[for="${el.id}"]`)
|
|
|
|
|
)
|
|
|
|
|
return;
|
|
|
|
|
el.setAttribute('aria-labelledby', titleId);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-02-10 16:49:47 +11:00
|
|
|
return (
|
|
|
|
|
<Box alignItems="Center" gap="300">
|
|
|
|
|
{before && <Box shrink="No">{before}</Box>}
|
|
|
|
|
<Box grow="Yes" direction="Column" gap="100">
|
|
|
|
|
{title && (
|
2026-09-18 19:19:54 -04:00
|
|
|
<Text id={titleId} className={BreakWord} size="T300">
|
2025-02-10 16:49:47 +11:00
|
|
|
{title}
|
|
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
{description && (
|
|
|
|
|
<Text className={BreakWord} size="T200" priority="300">
|
|
|
|
|
{description}
|
|
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
{children}
|
|
|
|
|
</Box>
|
2026-09-18 19:19:54 -04:00
|
|
|
{after && (
|
|
|
|
|
<Box shrink="No" ref={afterRef}>
|
|
|
|
|
{after}
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
2025-02-10 16:49:47 +11:00
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|