mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
[codex] Fix settings dialog open jank (#254)
This commit is contained in:
@@ -20,6 +20,7 @@ import { useHomeFeedNotifications } from "@/features/notifications/hooks";
|
||||
import { usePresenceSession } from "@/features/presence/hooks";
|
||||
import { useProfileQuery } from "@/features/profile/hooks";
|
||||
import type { SettingsSection } from "@/features/settings/ui/SettingsPanels";
|
||||
import { SettingsScreen } from "@/features/settings/ui/SettingsScreen";
|
||||
import { AppSidebar } from "@/features/sidebar/ui/AppSidebar";
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { useIdentityQuery } from "@/shared/api/hooks";
|
||||
@@ -44,10 +45,6 @@ const ChannelScreen = React.lazy(async () => {
|
||||
const module = await import("@/features/channels/ui/ChannelScreen");
|
||||
return { default: module.ChannelScreen };
|
||||
});
|
||||
const SettingsScreen = React.lazy(async () => {
|
||||
const module = await import("@/features/settings/ui/SettingsScreen");
|
||||
return { default: module.SettingsScreen };
|
||||
});
|
||||
const WorkflowsScreen = React.lazy(async () => {
|
||||
const module = await import("@/features/workflows/ui/WorkflowsScreen");
|
||||
return { default: module.WorkflowsScreen };
|
||||
@@ -472,33 +469,29 @@ export function AppShell() {
|
||||
/>
|
||||
|
||||
{settingsOpen ? (
|
||||
<React.Suspense
|
||||
fallback={<ViewLoadingFallback label="Loading settings..." />}
|
||||
>
|
||||
<SettingsScreen
|
||||
currentPubkey={identityQuery.data?.pubkey}
|
||||
fallbackDisplayName={identityQuery.data?.displayName}
|
||||
isUpdatingDesktopNotifications={
|
||||
notificationSettings.isUpdatingDesktopEnabled
|
||||
}
|
||||
notificationErrorMessage={notificationSettings.errorMessage}
|
||||
notificationPermission={notificationSettings.permission}
|
||||
notificationSettings={notificationSettings.settings}
|
||||
onClose={handleCloseSettings}
|
||||
onSectionChange={setSettingsSection}
|
||||
onSetDesktopNotificationsEnabled={
|
||||
notificationSettings.setDesktopEnabled
|
||||
}
|
||||
onSetHomeBadgeEnabled={notificationSettings.setHomeBadgeEnabled}
|
||||
onSetMentionNotificationsEnabled={
|
||||
notificationSettings.setMentionsEnabled
|
||||
}
|
||||
onSetNeedsActionNotificationsEnabled={
|
||||
notificationSettings.setNeedsActionEnabled
|
||||
}
|
||||
section={settingsSection}
|
||||
/>
|
||||
</React.Suspense>
|
||||
<SettingsScreen
|
||||
currentPubkey={identityQuery.data?.pubkey}
|
||||
fallbackDisplayName={identityQuery.data?.displayName}
|
||||
isUpdatingDesktopNotifications={
|
||||
notificationSettings.isUpdatingDesktopEnabled
|
||||
}
|
||||
notificationErrorMessage={notificationSettings.errorMessage}
|
||||
notificationPermission={notificationSettings.permission}
|
||||
notificationSettings={notificationSettings.settings}
|
||||
onClose={handleCloseSettings}
|
||||
onSectionChange={setSettingsSection}
|
||||
onSetDesktopNotificationsEnabled={
|
||||
notificationSettings.setDesktopEnabled
|
||||
}
|
||||
onSetHomeBadgeEnabled={notificationSettings.setHomeBadgeEnabled}
|
||||
onSetMentionNotificationsEnabled={
|
||||
notificationSettings.setMentionsEnabled
|
||||
}
|
||||
onSetNeedsActionNotificationsEnabled={
|
||||
notificationSettings.setNeedsActionEnabled
|
||||
}
|
||||
section={settingsSection}
|
||||
/>
|
||||
) : null}
|
||||
</SidebarProvider>
|
||||
</ChannelNavigationProvider>
|
||||
|
||||
@@ -133,8 +133,10 @@ export function ProfilePopover({
|
||||
className={MENU_ITEM_CLASS}
|
||||
data-testid="profile-popover-settings"
|
||||
onClick={() => {
|
||||
onOpenSettings();
|
||||
onOpenChange(false);
|
||||
window.requestAnimationFrame(() => {
|
||||
onOpenSettings();
|
||||
});
|
||||
}}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
import type { DesktopNotificationPermissionState } from "@/features/notifications/hooks";
|
||||
import type { NotificationSettings } from "@/features/notifications/hooks";
|
||||
import type { SettingsSection } from "@/features/settings/ui/SettingsPanels";
|
||||
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
|
||||
|
||||
const SettingsView = React.lazy(async () => {
|
||||
const module = await import("@/features/settings/ui/SettingsView");
|
||||
return { default: module.SettingsView };
|
||||
});
|
||||
import { SettingsView } from "@/features/settings/ui/SettingsView";
|
||||
|
||||
type SettingsScreenProps = {
|
||||
currentPubkey?: string;
|
||||
@@ -42,26 +35,22 @@ export function SettingsScreen({
|
||||
section,
|
||||
}: SettingsScreenProps) {
|
||||
return (
|
||||
<React.Suspense
|
||||
fallback={<ViewLoadingFallback label="Loading settings..." />}
|
||||
>
|
||||
<SettingsView
|
||||
currentPubkey={currentPubkey}
|
||||
fallbackDisplayName={fallbackDisplayName}
|
||||
isUpdatingDesktopNotifications={isUpdatingDesktopNotifications}
|
||||
notificationErrorMessage={notificationErrorMessage}
|
||||
notificationPermission={notificationPermission}
|
||||
notificationSettings={notificationSettings}
|
||||
onClose={onClose}
|
||||
onSectionChange={onSectionChange}
|
||||
onSetDesktopNotificationsEnabled={onSetDesktopNotificationsEnabled}
|
||||
onSetHomeBadgeEnabled={onSetHomeBadgeEnabled}
|
||||
onSetMentionNotificationsEnabled={onSetMentionNotificationsEnabled}
|
||||
onSetNeedsActionNotificationsEnabled={
|
||||
onSetNeedsActionNotificationsEnabled
|
||||
}
|
||||
section={section}
|
||||
/>
|
||||
</React.Suspense>
|
||||
<SettingsView
|
||||
currentPubkey={currentPubkey}
|
||||
fallbackDisplayName={fallbackDisplayName}
|
||||
isUpdatingDesktopNotifications={isUpdatingDesktopNotifications}
|
||||
notificationErrorMessage={notificationErrorMessage}
|
||||
notificationPermission={notificationPermission}
|
||||
notificationSettings={notificationSettings}
|
||||
onClose={onClose}
|
||||
onSectionChange={onSectionChange}
|
||||
onSetDesktopNotificationsEnabled={onSetDesktopNotificationsEnabled}
|
||||
onSetHomeBadgeEnabled={onSetHomeBadgeEnabled}
|
||||
onSetMentionNotificationsEnabled={onSetMentionNotificationsEnabled}
|
||||
onSetNeedsActionNotificationsEnabled={
|
||||
onSetNeedsActionNotificationsEnabled
|
||||
}
|
||||
section={section}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,13 +22,11 @@ type SettingsViewProps = SettingsPanelProps & {
|
||||
|
||||
function SettingsSectionButton({
|
||||
active,
|
||||
index,
|
||||
isLoaded,
|
||||
onSelect,
|
||||
section,
|
||||
}: {
|
||||
active: boolean;
|
||||
index: number;
|
||||
isLoaded: boolean;
|
||||
onSelect: (section: SettingsSection) => void;
|
||||
section: (typeof settingsSections)[number];
|
||||
@@ -39,7 +37,7 @@ function SettingsSectionButton({
|
||||
<button
|
||||
aria-pressed={active}
|
||||
className={cn(
|
||||
"group inline-flex min-w-fit items-center gap-2 rounded-lg border px-3 py-2 text-sm font-medium whitespace-nowrap motion-safe:transition-all motion-safe:duration-300 motion-safe:ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
"group inline-flex min-w-fit items-center gap-2 rounded-lg border px-3 py-2 text-sm font-medium whitespace-nowrap motion-safe:transition-all motion-safe:duration-200 motion-safe:ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
active
|
||||
? "border-border bg-background text-foreground shadow-sm"
|
||||
: "border-transparent bg-transparent text-muted-foreground hover:bg-background/70 hover:text-foreground",
|
||||
@@ -47,7 +45,6 @@ function SettingsSectionButton({
|
||||
)}
|
||||
data-testid={`settings-nav-${section.value}`}
|
||||
onClick={() => onSelect(section.value)}
|
||||
style={{ transitionDelay: isLoaded ? "0ms" : `${index * 40 + 300}ms` }}
|
||||
type="button"
|
||||
>
|
||||
<Icon
|
||||
@@ -78,14 +75,12 @@ export function SettingsView({
|
||||
onSetNeedsActionNotificationsEnabled,
|
||||
section,
|
||||
}: SettingsViewProps) {
|
||||
// Entrance animation state
|
||||
const [isLoaded, setIsLoaded] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoaded(true), 50);
|
||||
return () => clearTimeout(timer);
|
||||
const frameId = window.requestAnimationFrame(() => setIsLoaded(true));
|
||||
return () => window.cancelAnimationFrame(frameId);
|
||||
}, []);
|
||||
|
||||
// Escape key to close
|
||||
React.useEffect(() => {
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape" && !event.defaultPrevented) {
|
||||
@@ -101,32 +96,29 @@ export function SettingsView({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 flex items-center justify-center motion-safe:transition-opacity motion-safe:duration-300",
|
||||
"fixed inset-0 z-50 flex items-center justify-center motion-safe:transition-opacity motion-safe:duration-200",
|
||||
isLoaded ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
data-testid="settings-overlay"
|
||||
>
|
||||
{/* Backdrop — click to close */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 bg-background/80 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Modal container */}
|
||||
{/* biome-ignore lint/a11y/useKeyWithClickEvents: Click stops propagation to backdrop; keyboard dismiss handled by Escape key */}
|
||||
{/* biome-ignore lint/a11y/useKeyWithClickEvents: Click stops propagation to backdrop; keyboard dismiss handled by Escape key. */}
|
||||
<div
|
||||
aria-labelledby="settings-title"
|
||||
aria-modal="true"
|
||||
className={cn(
|
||||
"relative mx-auto flex h-[min(600px,calc(100vh-8rem))] w-[calc(100%-4rem)] max-w-3xl flex-col overflow-hidden rounded-xl border border-border bg-background shadow-lg motion-safe:transition-all motion-safe:duration-500 motion-safe:ease-out",
|
||||
"relative mx-auto flex h-[min(600px,calc(100vh-8rem))] w-[calc(100%-4rem)] max-w-3xl flex-col overflow-hidden rounded-xl border border-border bg-background shadow-lg motion-safe:transition-all motion-safe:duration-200 motion-safe:ease-out",
|
||||
isLoaded ? "opacity-100 scale-100" : "opacity-0 scale-95",
|
||||
)}
|
||||
data-testid="settings-view"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
role="dialog"
|
||||
>
|
||||
{/* Header with title and close button */}
|
||||
<header
|
||||
className="flex shrink-0 items-center justify-between border-b border-border px-4 py-3"
|
||||
data-tauri-drag-region
|
||||
@@ -149,12 +141,10 @@ export function SettingsView({
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{/* Two-column layout */}
|
||||
<div className="grid min-h-0 flex-1 grid-rows-[auto_minmax(0,1fr)] overflow-hidden lg:grid-cols-[220px_minmax(0,1fr)] lg:grid-rows-1">
|
||||
{/* Sidebar nav */}
|
||||
<aside
|
||||
className={cn(
|
||||
"border-b border-border/70 bg-muted/20 motion-safe:transition-all motion-safe:duration-700 motion-safe:ease-out lg:border-b-0 lg:border-r",
|
||||
"border-b border-border/70 bg-muted/20 motion-safe:transition-all motion-safe:duration-200 motion-safe:ease-out lg:border-b-0 lg:border-r",
|
||||
isLoaded
|
||||
? "opacity-100 translate-x-0"
|
||||
: "opacity-0 -translate-x-2",
|
||||
@@ -164,10 +154,9 @@ export function SettingsView({
|
||||
aria-label="Settings sections"
|
||||
className="flex gap-1 overflow-x-auto px-3 py-3 lg:flex-col lg:overflow-y-auto lg:pt-1"
|
||||
>
|
||||
{settingsSections.map((entry, index) => (
|
||||
{settingsSections.map((entry) => (
|
||||
<SettingsSectionButton
|
||||
active={entry.value === section}
|
||||
index={index}
|
||||
isLoaded={isLoaded}
|
||||
key={entry.value}
|
||||
onSelect={onSectionChange}
|
||||
@@ -177,7 +166,6 @@ export function SettingsView({
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{/* Content area */}
|
||||
<section className="min-h-0 overflow-y-auto px-4 py-4 sm:px-6">
|
||||
<div
|
||||
className="mx-auto flex w-full max-w-4xl flex-col gap-4"
|
||||
|
||||
Reference in New Issue
Block a user