feat: typography overhaul, custom scrollbars, import/export, settings UI
Includes changes from prior sessions: Epilogue + Space Mono fonts, OverlayScrollbars integration, markdown editor fixes, settings dialog, import/export buttons, and various UI refinements.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useRef, useCallback } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { springs, scaleIn, microInteraction } from "@/lib/motion";
|
||||
import { springs, microInteraction } from "@/lib/motion";
|
||||
import {
|
||||
Sun, Moon, Monitor, RotateCcw,
|
||||
} from "lucide-react";
|
||||
@@ -94,45 +94,68 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
|
||||
const setDensity = useAppStore((s) => s.setDensity);
|
||||
const setDefaultColumnWidth = useAppStore((s) => s.setDefaultColumnWidth);
|
||||
|
||||
const roRef = useRef<ResizeObserver | null>(null);
|
||||
const [height, setHeight] = useState<number | "auto">("auto");
|
||||
|
||||
// Callback ref: sets up ResizeObserver when dialog content mounts in portal
|
||||
const contentRef = useCallback((node: HTMLDivElement | null) => {
|
||||
if (roRef.current) {
|
||||
roRef.current.disconnect();
|
||||
roRef.current = null;
|
||||
}
|
||||
if (node) {
|
||||
const measure = () => setHeight(node.getBoundingClientRect().height);
|
||||
measure();
|
||||
roRef.current = new ResizeObserver(measure);
|
||||
roRef.current.observe(node);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="bg-pylon-surface sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="font-heading text-pylon-text">
|
||||
Settings
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-pylon-text-secondary">
|
||||
Configure your OpenPylon preferences.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent className="bg-pylon-surface sm:max-w-lg overflow-hidden p-0">
|
||||
<motion.div
|
||||
animate={{ height: typeof height === "number" && height > 0 ? height : "auto" }}
|
||||
initial={false}
|
||||
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div ref={contentRef} className="flex flex-col gap-4 p-6">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="font-heading text-pylon-text">
|
||||
Settings
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-pylon-text-secondary">
|
||||
Configure your OpenPylon preferences.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="flex gap-1 border-b border-border pb-2">
|
||||
{TABS.map((t) => (
|
||||
<Button
|
||||
key={t.value}
|
||||
variant={tab === t.value ? "secondary" : "ghost"}
|
||||
size="sm"
|
||||
onClick={() => setTab(t.value)}
|
||||
className="font-mono text-xs"
|
||||
>
|
||||
{t.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{/* Tab bar */}
|
||||
<div className="flex gap-1 border-b border-border pb-2">
|
||||
{TABS.map((t) => (
|
||||
<Button
|
||||
key={t.value}
|
||||
variant={tab === t.value ? "secondary" : "ghost"}
|
||||
size="sm"
|
||||
onClick={() => setTab(t.value)}
|
||||
className="font-mono text-xs"
|
||||
>
|
||||
{t.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab content */}
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={tab}
|
||||
className="flex flex-col gap-5 pt-1"
|
||||
variants={scaleIn}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
transition={springs.snappy}
|
||||
>
|
||||
{tab === "appearance" && (
|
||||
{/* Tab content — entire dialog height animates between tabs */}
|
||||
<AnimatePresence mode="popLayout" initial={false}>
|
||||
<motion.div
|
||||
key={tab}
|
||||
className="flex flex-col gap-5"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
>
|
||||
{tab === "appearance" && (
|
||||
<>
|
||||
{/* Theme */}
|
||||
<div>
|
||||
@@ -292,8 +315,10 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user