[eric] ui: true path-drawing icon animations (POC), sidebar-toggle divider sweeps + the plus strokes redraw on hover

This commit is contained in:
ciregenz
2026-06-10 22:59:00 -07:00
parent 998de41da3
commit 170b6f1334
2 changed files with 56 additions and 10 deletions
@@ -23,7 +23,8 @@ import { LayoutGrid } from 'lucide-react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Settings as LucideSettings } from 'lucide-react';
import { Palette } from 'lucide-react';
import { PanelLeft, ArrowLeft, ArrowRight, Plus } from 'lucide-react';
import { ArrowLeft, ArrowRight } from 'lucide-react';
import { AnimatedPanelLeft, AnimatedPlus } from './animatedIcons';
import RestartAltIcon from '@mui/icons-material/RestartAlt';
import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt';
import CloseIcon from '@mui/icons-material/Close';
@@ -392,12 +393,10 @@ const AppShell: React.FC = () => {
color: c.text.tertiary,
p: 0.5,
borderRadius: 1,
'& svg': { transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)' },
'&:hover': { color: c.text.secondary, bgcolor: `${c.text.tertiary}14` },
'&:hover svg': { transform: 'translateX(-1.5px) scale(1.08)' },
}}
>
<PanelLeft size={18} />
<AnimatedPanelLeft size={18} />
</IconButton>
</Tooltip>
<Tooltip title="Back">
@@ -680,12 +679,10 @@ const AppShell: React.FC = () => {
p: 0.25,
mr: 0.25,
borderRadius: 1,
'& svg': { transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)' },
'&:hover': { color: c.accent.primary, bgcolor: `${c.accent.primary}14` },
'&:hover svg': { transform: 'rotate(90deg)' },
}}
>
<Plus size={15} />
<AnimatedPlus size={15} />
</IconButton>
</Tooltip>
{dashboardList.length > 0 && (
@@ -939,12 +936,10 @@ const AppShell: React.FC = () => {
p: 0.25,
mr: 0.25,
borderRadius: 1,
'& svg': { transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)' },
'&:hover': { color: c.accent.primary, bgcolor: `${c.accent.primary}14` },
'&:hover svg': { transform: 'rotate(90deg)' },
}}
>
<Plus size={15} />
<AnimatedPlus size={15} />
</IconButton>
</Tooltip>
{appsList.length > 0 && (
@@ -0,0 +1,51 @@
import { motion } from 'framer-motion';
// True path-drawing sidebar glyphs: the SVG strokes themselves animate on hover
// (not just a transform), then settle. Always fully visible at rest, so a hover
// that's interrupted mid-flight never leaves a half-drawn icon. Geometry matches
// the lucide line-icons they replace so the static look is unchanged.
const SPRING = { type: 'spring', stiffness: 380, damping: 20 } as const;
type Props = { size?: number };
const svgBase = {
fill: 'none',
stroke: 'currentColor',
strokeWidth: 2,
strokeLinecap: 'round' as const,
strokeLinejoin: 'round' as const,
viewBox: '0 0 24 24',
};
// Panel toggle: the inner divider sweeps left, like the panel collapsing.
export function AnimatedPanelLeft({ size = 18 }: Props) {
return (
<motion.svg width={size} height={size} {...svgBase} initial="rest" animate="rest" whileHover="hover">
<rect x="3" y="3" width="18" height="18" rx="2" />
<motion.line
x1="9" y1="3" x2="9" y2="21"
variants={{ rest: { x: 0 }, hover: { x: -2.5 } }}
transition={SPRING}
/>
</motion.svg>
);
}
// Plus: both strokes redraw from nothing, the classic "new" flourish.
export function AnimatedPlus({ size = 16 }: Props) {
return (
<motion.svg width={size} height={size} {...svgBase} initial="rest" animate="rest" whileHover="hover">
<motion.line
x1="12" y1="5" x2="12" y2="19"
variants={{ rest: { pathLength: 1 }, hover: { pathLength: [0, 1] } }}
transition={{ duration: 0.34, ease: 'easeOut' }}
/>
<motion.line
x1="5" y1="12" x2="19" y2="12"
variants={{ rest: { pathLength: 1 }, hover: { pathLength: [0, 1] } }}
transition={{ duration: 0.34, ease: 'easeOut', delay: 0.06 }}
/>
</motion.svg>
);
}