diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx index 5eadca46..4b267630 100644 --- a/frontend/src/app/components/Layout/AppShell.tsx +++ b/frontend/src/app/components/Layout/AppShell.tsx @@ -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)' }, }} > - + @@ -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)' }, }} > - + {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)' }, }} > - + {appsList.length > 0 && ( diff --git a/frontend/src/app/components/Layout/animatedIcons.tsx b/frontend/src/app/components/Layout/animatedIcons.tsx new file mode 100644 index 00000000..4cafedfd --- /dev/null +++ b/frontend/src/app/components/Layout/animatedIcons.tsx @@ -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 ( + + + + + ); +} + +// Plus: both strokes redraw from nothing, the classic "new" flourish. +export function AnimatedPlus({ size = 16 }: Props) { + return ( + + + + + ); +}