mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 02:45:39 -04:00
Optimize frontend: memoization, modern React hooks, reduced render overhead
- useTimeAgo: replace per-instance intervals with shared useSyncExternalStore ticker - useTiltEffect: gate to fine-pointer devices, skip motion graphs on touch - Carousel: bake WheelGesturesPlugin into primitive, remove from all consumers - TitleSeasons: use memoized Set + precomputed progress map instead of Array.includes - useSearch: stabilize results identity with useMemo - CommandPalette: hoist shortcut grouping to module scope, stabilize effect deps - StarRating: hoist static spring transition to module constant - Settings toggles: use useOptimistic + useTransition for auto-rollback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,15 @@ import {
|
||||
} from "@/lib/atoms/command-palette";
|
||||
import { SHORTCUT_DESCRIPTIONS } from "@/lib/constants/shortcuts";
|
||||
|
||||
const groupedShortcuts: Record<
|
||||
string,
|
||||
{ description: string; keys: readonly string[] }[]
|
||||
> = {};
|
||||
for (const entry of SHORTCUT_DESCRIPTIONS) {
|
||||
if (!groupedShortcuts[entry.scope]) groupedShortcuts[entry.scope] = [];
|
||||
groupedShortcuts[entry.scope].push(entry);
|
||||
}
|
||||
|
||||
type SearchResult = ReturnType<typeof useSearch>["results"][number];
|
||||
|
||||
export function CommandPalette() {
|
||||
@@ -102,7 +111,7 @@ export function CommandPalette() {
|
||||
return () => {
|
||||
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
|
||||
};
|
||||
}, [debouncedQuery, results, setRecentSearches]);
|
||||
}, [debouncedQuery, results.length, setRecentSearches]);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(result: SearchResult) => {
|
||||
@@ -134,16 +143,6 @@ export function CommandPalette() {
|
||||
|
||||
const hasQuery = query.trim().length > 0;
|
||||
|
||||
// Group shortcuts by scope for help dialog
|
||||
const grouped: Record<
|
||||
string,
|
||||
{ description: string; keys: readonly string[] }[]
|
||||
> = {};
|
||||
for (const entry of SHORTCUT_DESCRIPTIONS) {
|
||||
if (!grouped[entry.scope]) grouped[entry.scope] = [];
|
||||
grouped[entry.scope].push(entry);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={commandPaletteOpen} onOpenChange={setCommandPaletteOpen}>
|
||||
@@ -362,7 +361,7 @@ export function CommandPalette() {
|
||||
<DialogTitle>Keyboard Shortcuts</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-5 py-2">
|
||||
{Object.entries(grouped).map(([scope, items]) => (
|
||||
{Object.entries(groupedShortcuts).map(([scope, items]) => (
|
||||
<div key={scope} className="space-y-2">
|
||||
<h3 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{scope}
|
||||
|
||||
@@ -4,10 +4,13 @@ import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
|
||||
import useEmblaCarousel, {
|
||||
type UseEmblaCarouselType,
|
||||
} from "embla-carousel-react";
|
||||
import { WheelGesturesPlugin } from "embla-carousel-wheel-gestures";
|
||||
import * as React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const defaultPlugins = [WheelGesturesPlugin({ forceWheelAxis: "x" })];
|
||||
|
||||
type CarouselApi = UseEmblaCarouselType[1];
|
||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
||||
type CarouselOptions = UseCarouselParameters[0];
|
||||
@@ -55,7 +58,7 @@ function Carousel({
|
||||
...opts,
|
||||
axis: orientation === "horizontal" ? "x" : "y",
|
||||
},
|
||||
plugins,
|
||||
plugins ?? defaultPlugins,
|
||||
);
|
||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
||||
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
||||
|
||||
Reference in New Issue
Block a user