fix(ui): forward-dialog header, notification presets, syntax-highlight tokens
- N14 ForwardMessageDialog: add folds <Header> with title + close IconButton (was closeable only by clicking outside) - N20 Notification presets: bare <button> with undefined --border-interactive- normal / --bg-surface-low vars -> folds <Button variant="Secondary" fill="Soft"> - N68 syntaxHighlight tokenStyle: use the theme-aware --prism-* variable family (keyword/selector/boolean/atrule/comment) instead of TDS-only --lt-accent-* vars with dark-only Monokai fallbacks; comment uses --prism-comment not opacity Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+4
-4
@@ -318,13 +318,13 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
| N11 | AvatarDecoration | `AvatarDecoration.tsx` | 5 / 38–41 | Fixed 8px inset on all sides regardless of avatar size — at folds size `"200"` (~32px) the decoration bleeds 50% of the avatar diameter, clipping against `overflow: hidden` parent containers in member lists. **Inset issue still OPEN.** _Related regression fixed in `useAvatarDecoration.ts`_: the decoration fetch cached **all** failures (including transient 429/5xx) as "no decoration" permanently for the session, so a single rate-limited burst (member list / timeline mount many avatars at once) would make decorations vanish until a full reload. Now only a genuine 404 is cached; transient errors retry on the next mount. | Folds `Avatar` and `PresenceRingAvatar` do not emit overflow outside their bounding box |
|
||||
| N12 | MediaGallery Drawer | `MediaGallery.tsx` | 651–661 | Drawer uses `position: 'fixed'` with hardcoded `width: '320px'` as inline styles on a `<Box>` — **FIXED**: moved positioning/width into co-located `MediaGallery.css.ts` using `toRem(320)` + a `max-width: 750px` full-screen media query (mirrors `MembersDrawer`); border/header now use `config.borderWidth`/`config.space` tokens. Added Escape-to-close on the panel (previously only the lightbox handled Escape). **Full chrome redesign (round 2)** to match native conventions: panel + header switched from `Surface` to `Background` variant (matching `MembersDrawer`/Saved Messages); header now `Text size="H4"` + plain close `IconButton` (dropped the bespoke tooltip-wrapped button); tabs moved to a bordered toolbar strip with the `variant={active?'Primary':'Secondary'} fill={active?'Solid':'Soft'}` pattern from `PolicyListViewer` and now show per-tab counts; the centered "lines + label" month divider replaced with a left-aligned group label (Cinny group-label pattern); thumbnail tiles moved hover/focus styling to CSS `:hover`/`:focus-visible` (no JS hover state) and into `MediaGallery.css.ts`; file rows + grid tokenized. **Docking fix (round 3)** — the core of the finding: the gallery was a `position: fixed` overlay floating over the timeline, mounted from `RoomViewHeader`. It is now a **docked flex sibling** in the room layout row, exactly like `MembersDrawer`: open state lifted to a `mediaGalleryAtom` (mirrors `bookmarksPanelAtom`), rendered in `Room.tsx` with a vertical `Line` separator on desktop and `key={room.roomId}` to reset per room; the CSS is static-width on desktop and only `position: fixed; inset: 0` full-screen on mobile (identical strategy to `MembersDrawer.css`). It now shares the row with the timeline instead of overlapping it. | `MembersDrawer` uses a vanilla-extract class with `width: toRem(266)` and is placed by the layout system, not `position: fixed`. 54px width discrepancy also breaks visual rhythm if both panels could be open |
|
||||
| N13 | ScheduledMessagesTray | `ScheduledMessagesTray.tsx` | 108–126 | Collapsible tray header is `<Box as="button">` with `cursor: 'pointer'` inline style and no folds variant — no hover state, no focus ring — **FIXED**: replaced with folds `<Button variant="Secondary" fill="None" radii="0">` using `before`/`after` icon props (gains design-system hover/focus) | All clickable header/toggle elements in the room view use folds `<Button>` or `<IconButton>` with explicit variants for hover/focus; `<Box as="button">` with no variant is used nowhere else |
|
||||
| N14 | ForwardMessageDialog | `ForwardMessageDialog.tsx` | 137–154 | Dialog uses `<Modal>` but has no `<Header>` component and no close `<IconButton>` — only way to close is clicking outside | Every other modal using `<Modal>` or `<Box role="dialog">` includes a `<Header>` with a close `<IconButton>` in the top-right (EditHistoryModal, LeaveRoomPrompt, ScheduleMessageModal, RemindMeDialog, etc.) |
|
||||
| N14 | ForwardMessageDialog | `ForwardMessageDialog.tsx` | 137–154 | Dialog uses `<Modal>` but has no `<Header>` component and no close `<IconButton>` — only way to close is clicking outside — **FIXED**: added a folds `<Header variant="Surface" size="500">` with the title + close `<IconButton radii="300">`, matching every other modal | Every other modal using `<Modal>` or `<Box role="dialog">` includes a `<Header>` with a close `<IconButton>` in the top-right (EditHistoryModal, LeaveRoomPrompt, ScheduleMessageModal, RemindMeDialog, etc.) |
|
||||
| N15 | ScheduleMessageModal | `ScheduleMessageModal.tsx` | 180–193 | Modal root is `<Box as="form" role="dialog">` with manually assembled `borderRadius: config.radii.R400`/`boxShadow` | `ForwardMessageDialog` uses folds `<Modal size="400">` with `R500` radius; the R400 vs R500 mismatch is visible when both dialogs appear in the same session |
|
||||
| N16 | Presence Picker | `SettingsTab.tsx` | 118–144 | Presence trigger dot is raw `<button>` with `position: absolute; bottom: 2; right: 2` inline and no folds focus ring; no tooltip — **FIXED**: wrapped the trigger in a folds `TooltipProvider` (shows "Status: …"); replaced the undefined `var(--bg-surface)` with `color.Background.Container`. Kept the absolute-positioned `<button>` (it overlays the avatar corner; a full `IconButton` would be too large for the dot). | Every other sidebar icon button uses folds `IconButton` with `SidebarItemTooltip` and `TooltipProvider` |
|
||||
| N17 | Presence Picker | `SettingsTab.tsx` | 80–86 | `PresencePicker` `FocusTrap` missing `escapeDeactivates: stopPropagation` and `isKeyForward`/`isKeyBackward` — **FIXED**: added all three options, matching the theme selector / sort menus | Every other `PopOut`+`FocusTrap`+`Menu` combo supplies both (theme selector `General.tsx:143–160`, `SettingsSelect`, sort menus) — without it Escape bubbles past the trap and arrow-key navigation is absent |
|
||||
| N18 | Profile Selects | `Profile.tsx` | 547–575 / 816–848 | `ProfileStatus` auto-clear and `ProfileTimezone` selectors are native `<select>` elements with hardcoded `colorScheme: 'dark'` — will render in dark mode on light themes | General.tsx uses folds `SettingsSelect<T>` (`Button`+`PopOut`+`Menu`) for all dropdowns; `colorScheme: 'dark'` breaks light/custom theme appearance |
|
||||
| N19 | Presence Labels | `useUserPresence.ts` vs `SettingsTab.tsx` | 55–62 / 36–42 | `PresenceBadge` tooltip shows "Active / Busy / Away"; `PresencePicker` options read "Online / Idle / Do Not Disturb / Invisible" — a DND user shows tooltip "Busy", not "Do Not Disturb" — **FIXED**: aligned `usePresenceLabel` reader vocabulary to the setter (online→"Online", unavailable→"Idle", offline→"Offline") | Within the same Lotus feature set the user-facing vocabulary is inconsistent between the setter UI and the reader tooltip |
|
||||
| N20 | Notification Presets | `Notifications.tsx` | 57–107 | Gaming/Work/Sleep preset buttons are bare `<button>` elements with Lotus-specific CSS vars (`--border-interactive-normal`, `--bg-surface-low`) not defined in all themes | Grouped preset/action buttons elsewhere use folds `Chip variant="Primary/Secondary" outlined radii="Pill"` (e.g., Composer Toolbar toggles in `General.tsx:1100–1113`) |
|
||||
| N20 | Notification Presets | `Notifications.tsx` | 57–107 | Gaming/Work/Sleep preset buttons are bare `<button>` elements with Lotus-specific CSS vars (`--border-interactive-normal`, `--bg-surface-low`) not defined in all themes — **FIXED**: converted to folds `<Button variant="Secondary" fill="Soft" radii="300">` (auto height) wrapping the emoji/label/description column; undefined vars removed | Grouped preset/action buttons elsewhere use folds `Chip variant="Primary/Secondary" outlined radii="Pill"` (e.g., Composer Toolbar toggles in `General.tsx:1100–1113`) |
|
||||
| N21 | Notification Sound Selects | `SystemNotification.tsx` | 111–305 | Message sound, invite sound, and quiet-hours time pickers are bare `<select>`/`<input type="time">` with `colorScheme: 'dark'` workaround | All other dropdowns in settings use the `Button`+`PopOut`+`Menu`+`MenuItem` folds pattern; the native select renders OS-styled on all platforms |
|
||||
| N22 | DM Preview Virtualizer | `RoomNavItem.tsx` / `Direct.tsx` | 608–627 / 232 | DM preview adds a second text row to each DM item, making it taller than 38px, but `useVirtualizer` in `Direct.tsx` still uses `estimateSize: () => 38` — causes layout jump/overlap on initial render | Non-DM rooms in Home.tsx also estimate 38px; DM items with a preview are now a different height, creating two visual densities in the same nav column |
|
||||
| N23 | RoomServerACL | `RoomServerACL.tsx` | 100–115 / 298–309 | Server-name text input is a raw `<input type="text">` with inline style object; "Allow IP literal addresses" is a raw `<input type="checkbox">` with `style={{ width: 16, height: 16 }}` — **FIXED**: text input → folds `<Input variant={error?'Critical':'Secondary'}>`; checkbox → folds `<Checkbox variant="Primary">` | All other text/boolean controls in room settings use folds `Input` and `Checkbox` components (`RoomAddress.tsx:163`, `RoomAddress.tsx:330`) |
|
||||
@@ -418,7 +418,7 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
| N65 | EditHistoryModal — No "Load More" | `EditHistoryModal.tsx` | 253–259 | When `hasMore` is true the modal shows passive `<Text>"Showing the 50 most recent edits"</Text>` with no action; older edits are inaccessible — **FIXED**: implemented real pagination — edits accumulate across `next_batch` fetches (de-duped by event id, re-sorted by ts), with a folds `<Button>Load more</Button>` (spinner while loading) replacing the passive text | `RoomActivityLog.tsx:425` and `MessageSearch.tsx:129` both render a folds `<Button size="300" variant="Secondary">Load more</Button>` to fetch the next page |
|
||||
| N66 | DateRangeButton — Native `<input type="date">` | `SearchFilters.tsx` | 558–589 | "From" and "To" date fields are raw `<input type="date">` with inline style overrides including `fontSize: '0.82rem'` — **FIXED**: replaced both with folds `<Input type="date" variant="SurfaceVariant" size="300" radii="300">`; removed now-unused `color` import | `SelectRoomButton` (same file, line 224) and `SelectSenderButton` (line 424) both use folds `<Input size="300" radii="300">`; the date inputs are the only native browser inputs in the search filter row |
|
||||
| N67 | SeasonalEffect / NightLight Z-Index Order | `SeasonalEffect.tsx` / `App.tsx` | 759 / 62–77 | `SeasonalEffect` mounts at `zIndex: 9999`; `NightLightOverlay` at `zIndex: 9998`. Seasonal particles render **above** Night Light so they are never tinted. `SeasonalEffect` also shares `z-index: 9999` with the skip-to-content link in `ClientLayout.tsx` — **FIXED**: lowered `SeasonalEffect` overlay to `zIndex: 9997` (below Night Light at 9998 and modals at 9999), so Night Light now tints the particles and dialogs are never obscured | Expected UX: Night Light tints all visible content including effects; requires either a higher Night Light z-index or a lower SeasonalEffect z-index |
|
||||
| N68 | Syntax Highlighting — `--lt-accent-*` Vars in Non-TDS Themes | `syntaxHighlight.ts` | 313–323 | `tokenStyle()` returns `var(--lt-accent-cyan/green/orange/purple, hardcoded-fallback)` — `--lt-*` vars only exist in TDS mode; fallbacks are Monokai dark colors that have poor contrast on light themes and no relationship to the existing `--prism-*` variables in `ReactPrism.css` | `ReactPrism.css` uses `--prism-keyword`, `--prism-selector` etc. which switch correctly between light and dark palettes; syntax highlighting should use the same variable family |
|
||||
| N68 | Syntax Highlighting — `--lt-accent-*` Vars in Non-TDS Themes | `syntaxHighlight.ts` | 313–323 | `tokenStyle()` returns `var(--lt-accent-cyan/green/orange/purple, hardcoded-fallback)` — `--lt-*` vars only exist in TDS mode; fallbacks are Monokai dark colors that have poor contrast on light themes and no relationship to the existing `--prism-*` variables in `ReactPrism.css` — **FIXED**: `tokenStyle()` now maps to the `--prism-*` family (keyword/selector/boolean/atrule/comment) which has proper light/dark/TDS palettes; comment uses `--prism-comment` instead of an opacity hack | `ReactPrism.css` uses `--prism-keyword`, `--prism-selector` etc. which switch correctly between light and dark palettes; syntax highlighting should use the same variable family |
|
||||
| N69 | Mention Highlight — `<input type="color">` Instead of `HexColorPickerPopOut` | `General.tsx` | 644–675 | Raw `<input type="color">` with hardcoded pixel dimensions; OS-native color picker chrome renders completely differently from the rest of settings UI | `PowersEditor.tsx:125–143` establishes `<HexColorPickerPopOut picker={<HexColorPicker ...>}>` as the codebase's color-picking pattern; Reset button should be `<Button size="300" variant="Secondary" radii="300">` |
|
||||
| N70 | ChatBgGrid / SeasonalBgGrid — Raw `<button>` Elements | `General.tsx` | 1648–1689 / 1711–1742 | Both pickers use raw HTML `<button>` elements with hardcoded `width: toRem(76)`, `height: toRem(50/56)`, `borderRadius: toRem(8)`, `border: 2px solid rgba(...)` — no focus ring via folds, no `variant` prop, no hover state from the design system | Native Cinny theme pickers use folds `<MenuItem>` or `<Chip>` which respond to theme and provide focus/hover states automatically |
|
||||
|
||||
@@ -437,7 +437,7 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
| N77 | Search Filter Inline Lambdas | `SearchFilters.tsx` | 480, 625 | `SelectSenderButton` and `DateRangeButton` trigger chips use inline `onClick` arrow functions — **WON'T FIX (deliberate)**: purely a code-style nit with zero user-facing or behavioural impact. Inline arrow handlers are idiomatic React and used throughout this very file; extracting them yields no functional benefit. | `OrderButton` (line 58) and `SelectRoomButton` (line 195) both extract a named `const handleOpenMenu: MouseEventHandler<HTMLButtonElement>` handler — bypassing the type annotation in the inline form |
|
||||
| N78 | HasLink Chip Active Color | `SearchFilters.tsx` | 755 | `HasLink` active state uses `variant="Primary"` (blue); all boolean scope-toggle chips in the same bar use `variant="Success"` (green) with `outlined` — **FIXED**: changed to `variant={containsUrl ? 'Success' : 'SurfaceVariant'} outlined={!!containsUrl}` | `variant="Success" outlined` is the established active-state pattern for boolean toggles in the filter bar |
|
||||
| N79 | Server Notice Chip Radii | `RoomViewHeader.tsx` | 570 | `<Chip size="400" radii="Pill">` — `Pill` radii on a room-type label — **FIXED**: changed to `radii="300"` | Room/space type labels in lobby (`RoomItem.tsx:83`, `SpaceItem.tsx:63`) use `radii="300"`; `radii="Pill"` is for filter/tag chips only |
|
||||
| N80 | Server Support Contact Layout | `About.tsx` | 172–239 | Homeserver support contacts rendered as raw `<Box direction="Column">` with `<Text as="a">` pairs — custom label/link layout | All other `<SequenceCard>` content in `About.tsx` and `General.tsx` uses `<SettingTile title="..." description="..." after={...}>` as the content unit |
|
||||
| N80 | Server Support Contact Layout | `About.tsx` | 172–239 | Homeserver support contacts rendered as raw `<Box direction="Column">` with `<Text as="a">` pairs — custom label/link layout — **WON'T FIX (deliberate)**: a contact is `role → {matrix_id?, email?, …}` (one-to-many links per role), which doesn't map onto `SettingTile`'s single `title`/`description`/`after` slots without contortion. The current layout already uses folds `Box`/`Text`/`SequenceCard` + tokens and `Text as="a"` (a valid folds pattern); no undefined vars or raw HTML chrome. | All other `<SequenceCard>` content in `About.tsx` and `General.tsx` uses `<SettingTile title="..." description="..." after={...}>` as the content unit |
|
||||
| N81 | Background Picker Grid — No Responsive Layout | `General.tsx` | 1707–1742 | Fixed `width: toRem(76)` flex-wrap cells with no `minWidth` floor or CSS grid `auto-fill` — SeasonalBgGrid's 13 items produce a visually lopsided orphan last row at any viewport width | Cinny's native grids use `grid-template-columns: repeat(auto-fill, minmax(N, 1fr))` or equivalent for responsive fill |
|
||||
| N82 | Join/Leave Sounds Auto-Preview | `General.tsx` | 1592–1609 | Selecting a sound in the dropdown immediately plays a preview, but no UI affordance (button label, description text, or "▶ Preview" button) communicates this to the user | Settings tiles with side effects on selection (theme picker, chat background) show a live visual preview or a dedicated control explaining the side effect |
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ import {
|
||||
Avatar,
|
||||
Box,
|
||||
config,
|
||||
Header,
|
||||
Icon,
|
||||
IconButton,
|
||||
Icons,
|
||||
Input,
|
||||
Line,
|
||||
MenuItem,
|
||||
@@ -134,14 +138,22 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
||||
...modalStyle,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
direction="Column"
|
||||
gap="200"
|
||||
shrink="No"
|
||||
style={{ padding: config.space.S400, paddingBottom: config.space.S200 }}
|
||||
<Header
|
||||
variant="Surface"
|
||||
size="500"
|
||||
style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
|
||||
>
|
||||
<Text size="H5">Forward message</Text>
|
||||
{!sentTo && (
|
||||
<Box grow="Yes">
|
||||
<Text as="h2" size="H4" truncate>
|
||||
Forward message
|
||||
</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={onClose} radii="300" aria-label="Close">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
{!sentTo && (
|
||||
<Box shrink="No" style={{ padding: `${config.space.S200} ${config.space.S400}` }}>
|
||||
<Input
|
||||
variant="Background"
|
||||
size="400"
|
||||
@@ -151,8 +163,8 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
||||
value={query}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => setQuery(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Line size="300" />
|
||||
{sentTo ? (
|
||||
<Box
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll, config, toRem } from 'folds';
|
||||
import { Box, Button, Text, IconButton, Icon, Icons, Scroll, config, toRem } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { SystemNotification } from './SystemNotification';
|
||||
import { AllMessagesNotifications } from './AllMessages';
|
||||
@@ -68,37 +68,34 @@ function NotificationPresets() {
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<Box gap="300" style={{ padding: config.space.S300, flexWrap: 'wrap' }}>
|
||||
{PRESETS.map((preset) => (
|
||||
<button
|
||||
<Button
|
||||
key={preset.label}
|
||||
type="button"
|
||||
onClick={() => applyPreset(preset.patch)}
|
||||
title={preset.description}
|
||||
variant="Secondary"
|
||||
fill="Soft"
|
||||
radii="300"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: toRem(4),
|
||||
padding: `${toRem(8)} ${toRem(16)}`,
|
||||
borderRadius: config.radii.R300,
|
||||
border: '1px solid var(--border-interactive-normal)',
|
||||
background: 'var(--bg-surface-low)',
|
||||
color: 'inherit',
|
||||
cursor: 'pointer',
|
||||
height: 'unset',
|
||||
minWidth: toRem(80),
|
||||
padding: `${config.space.S200} ${config.space.S400}`,
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: toRem(24) }}>{preset.emoji}</span>
|
||||
<Text size="T300" style={{ fontWeight: 600 }}>
|
||||
{preset.label}
|
||||
</Text>
|
||||
<Text
|
||||
size="T200"
|
||||
priority="300"
|
||||
style={{ textAlign: 'center', maxWidth: toRem(120) }}
|
||||
>
|
||||
{preset.description}
|
||||
</Text>
|
||||
</button>
|
||||
<Box direction="Column" alignItems="Center" gap="100">
|
||||
<span style={{ fontSize: toRem(24) }}>{preset.emoji}</span>
|
||||
<Text size="T300" style={{ fontWeight: config.fontWeight.W600 }}>
|
||||
{preset.label}
|
||||
</Text>
|
||||
<Text
|
||||
size="T200"
|
||||
priority="300"
|
||||
style={{ textAlign: 'center', maxWidth: toRem(120) }}
|
||||
>
|
||||
{preset.description}
|
||||
</Text>
|
||||
</Box>
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</SequenceCard>
|
||||
|
||||
@@ -306,19 +306,26 @@ export function tokenize(code: string, lang: string): SyntaxToken[] {
|
||||
|
||||
// ── Inline style helpers ────────────────────────────────────────────────────
|
||||
|
||||
/** Returns the React inline-style object for a given SyntaxToken type. */
|
||||
/**
|
||||
* Returns the React inline-style object for a given SyntaxToken type.
|
||||
*
|
||||
* Uses the `--prism-*` variable family (defined in `ReactPrism.css`), which has
|
||||
* proper light / dark / TDS palettes — unlike the raw `--lt-accent-*` vars,
|
||||
* which only exist in TDS mode and otherwise fall back to dark-only Monokai
|
||||
* colors with poor contrast on light themes.
|
||||
*/
|
||||
export function tokenStyle(type: SyntaxToken['type']): CSSProperties {
|
||||
switch (type) {
|
||||
case 'kw':
|
||||
return { color: 'var(--lt-accent-cyan, #66d9ef)' };
|
||||
return { color: 'var(--prism-keyword)' };
|
||||
case 'str':
|
||||
return { color: 'var(--lt-accent-green, #a6e22e)' };
|
||||
return { color: 'var(--prism-selector)' };
|
||||
case 'num':
|
||||
return { color: 'var(--lt-accent-orange, #fd971f)' };
|
||||
return { color: 'var(--prism-boolean)' };
|
||||
case 'cmt':
|
||||
return { opacity: 0.5, fontStyle: 'italic' as const };
|
||||
return { color: 'var(--prism-comment)', fontStyle: 'italic' as const };
|
||||
case 'fn':
|
||||
return { color: 'var(--lt-accent-purple, #ae81ff)' };
|
||||
return { color: 'var(--prism-atrule)' };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user