Something went wrong. Try again.
[mirror] A local-first desktop markdown writer with grammar checking, at proto integration, and more github.com/stormlightlabs/writer/releases
atproto codemirror markdown tauri rust react
Something went wrong. Try again.
3.1 kB · 95 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596import { NO_MOTION_TRANSITION, STATUS } from "$constants";import { useSkipAnimation } from "$hooks/useMotion";import { CheckIcon, SaveIcon } from "$icons";import type { SaveStatus } from "$types";import { AnimatePresence, motion } from "motion/react";import { useMemo } from "react";
function Status({ status, compact }: { status: SaveStatus; compact: boolean }) { switch (status) { case "Saving": { return ( <div className="flex items-center gap-1.5 text-xs text-accent-cyan p-2.5 bg-layer-01"> <SaveIcon size="sm" /> {compact ? null : <span>Saving...</span>} </div> ); } case "Saved": { return ( <div className="flex items-center gap-1.5 text-xs text-accent-green p-2.5 bg-layer-01"> <CheckIcon size="sm" /> <SaveIcon size="sm" /> {compact ? null : <span>Saved</span>} </div> ); } case "Dirty": { return ( <div className="flex items-center gap-1.5 text-xs text-accent-yellow p-2.5 bg-layer-01"> <SaveIcon size="sm" /> {compact ? null : <span>Unsaved</span>} </div> ); } case "Error": { return ( <div className="flex items-center gap-1.5 text-xs text-support-error p-2.5 bg-layer-01 rounded border border-stroke-subtle"> <SaveIcon size="sm" /> {compact ? null : <span>Error</span>} </div> ); } default: { return ( <div className="flex items-center gap-1.5 text-xs text-text-placeholder p-2.5 bg-layer-01 rounded border border-stroke-subtle"> {compact ? null : <span>Ready</span>} </div> ); } }}
function Indicator({ status, compact, animationProps }: { status: SaveStatus; compact: boolean; animationProps: any }) { return ( <AnimatePresence mode="wait"> <motion.div key={status} {...animationProps}> <Status status={status} compact={compact} /> </motion.div> </AnimatePresence> );}
type SaveStatusIndicatorProps = { status: SaveStatus; compact?: boolean; onClick?: () => void; disabled?: boolean; title?: string;};
export function SaveStatusIndicator( { status, compact = false, onClick, disabled = false, title }: SaveStatusIndicatorProps,) { const skipAnimation = useSkipAnimation(); const animationProps = useMemo(() => { const transition = skipAnimation ? NO_MOTION_TRANSITION : STATUS.TRANSITION; return { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition }; }, [skipAnimation]);
if (onClick) { return ( <button type="button" onClick={onClick} disabled={disabled} title={title} className="rounded-md px-2.5 py-1.5 opacity-85 disabled:cursor-not-allowed disabled:opacity-50" aria-label={title}> <Indicator status={status} compact={compact} animationProps={animationProps} /> </button> ); }
return <Indicator status={status} compact={compact} animationProps={animationProps} />;}