Files
cinny/src/app/components/setting-tile/SettingTile.tsx
T
Lotus CIandClaude Opus 5.5 53ac87befe
CI / Build & Quality Checks (pull_request) Successful in 2m0s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 6s
CI / Playwright smoke (e2e) (pull_request) Successful in 12m45s
fix(ui): receipt pill, insights counts, settings dividers, decoration preview (#269-#273)
#269 read-receipt pill sits at the message row's right edge, not where
     the text ends (outside the shrink-to-fit content column; grows in
     Compact). Bubble layout unchanged.
#270 Most Active Members: the bar's % is of its own track, so every row
     shows its count.
#271 Noise Suppression description at T200 like the rest.
#272 decoration preview shows your own avatar under the decoration; the
     empty state is a visible dashed circle.
#273 grouped settings get a divider between tiles (SettingTileGroup, via
     globalStyle; SettingTile roots carry data-setting-tile), and on phones
     the Sync tile's buttons sit under its description.

Fixes #269
Fixes #270
Fixes #271
Fixes #272
Fixes #273

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 15:06:20 -04:00

63 lines
2.1 KiB
TypeScript

import React, { ReactNode, useEffect, useId, useRef } from 'react';
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) {
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);
});
});
return (
// data-setting-tile: lets a group card draw dividers between tiles only (#273).
<Box alignItems="Center" gap="300" data-setting-tile="">
{before && <Box shrink="No">{before}</Box>}
<Box grow="Yes" direction="Column" gap="100">
{title && (
<Text id={titleId} className={BreakWord} size="T300">
{title}
</Text>
)}
{description && (
// [Gitea #291] A <div>, not a <p>: many tiles pass blocks (badges,
// <Box>, <Text>) as the description, which is invalid inside a <p>.
<Text as="div" className={BreakWord} size="T200" priority="300">
{description}
</Text>
)}
{children}
</Box>
{after && (
<Box shrink="No" ref={afterRef}>
{after}
</Box>
)}
</Box>
);
}