freezmo/components/icons.tsx
Martin Dvorak 8557b04621 Navigation restructure: five-tab bottom bar, no phone drawer
Drawer removed (phones only; desktop rail untouched)
- MobileSidebar, sidebarOpen/useSidebarOpen, MenuIcon and the drawer's
  back-gesture layer are gone; the shared nav content now serves only
  the desktop rail (dead mobile branches deleted). No drawer-specific
  CSS existed; the three drawer strings (header.openMenu,
  sidebar.closeMenu, common.menu) and library.importAria are removed.
- The header drops the hamburger: logo + name on the left, the single
  theme chip on the right.

Five-tab bottom bar
- Library / Progress / New deck (prominent centre +) / Exam / Settings,
  in that order; Progress and Exam reuse the existing /stats and /exam
  screens, Settings keeps opening the dialog. Sliding pill, icon bounce,
  press feedback, re-tap-to-top and safe-area padding kept; every item
  is >=44px (72px wide at 360px).
- New short tab.* keys (EN + CS) so all five labels stay on one line at
  360px — 'Zkouškový režim' became 'Zkouška'. No ellipsis anywhere.
- The bar is mounted once in AppHeader so library, progress, exam and
  share get it; the wizards keep their own bottom action bar and the
  tab bar steps aside there, exactly as before.

Re-homed drawer destinations
- Smart Review card loses the Exam link (it is a tab now); Review due
  and Mistakes stay.
- New deck screen (and /homework) get a Study deck / Make my homework
  segmented switch; the library's homework empty-state button stays.
- The phone import icon becomes a ... button opening a sheet with
  Import JSON and New folder (back-gesture and Escape close it); folder
  chips still appear only once at least one folder exists, and folder
  creation stays in the New deck options step.

Verified: mobile suite 134 checks (no hamburger/drawer, tab order and
44px targets, label matrix at 360/390 x EN/CS x dark/light x reduced
motion, per-tab routing, deep links, Back from every tab, the ... sheet
with folder creation) plus regression 74/74, wizard 83/83, declutter
116/116, animation 8/8, native 35/35, i18n 25/25, phase1 38/38, phase2
43/43, phase3 20/20, freeze-loading 6/6. Key parity 740/740. Lint +
build clean. SW freezmo-v28.
2026-10-10 16:53:55 +00:00

327 lines
8.8 KiB
TypeScript

"use client";
/**
* Small inline SVG icons used for the app chrome so that critical controls
* don't depend on a system emoji font being installed.
*/
type IconProps = { className?: string; strokeWidth?: number };
const base = "size-5";
function Svg({
children,
className,
filled,
}: {
children: React.ReactNode;
className?: string;
filled?: boolean;
}) {
return (
<svg
viewBox="0 0 24 24"
className={className ?? base}
fill={filled ? "currentColor" : "none"}
stroke={filled ? "none" : "currentColor"}
strokeWidth={1.8}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
{children}
</svg>
);
}
export function IceCubeIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 3.2 19 7v10l-7 3.8L5 17V7l7-3.8Z" />
<path d="M5 7l7 3.8L19 7M12 10.8V21" />
</Svg>
);
}
export function SnowflakeIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 2v20M4.5 6.5l15 11M19.5 6.5l-15 11" />
<path d="M12 6l-2.2-2.2M12 6l2.2-2.2M12 18l-2.2 2.2M12 18l2.2 2.2M7 9.2 4.8 8.6M7 14.8l-2.2.6M17 9.2l2.2-.6M17 14.8l2.2.6" />
</Svg>
);
}
export function GearIcon({ className }: IconProps) {
return (
<Svg className={className}>
<circle cx="12" cy="12" r="3.2" />
<path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.03 1.56V21a2 2 0 1 1-4 0v-.11A1.7 1.7 0 0 0 8.9 19.3a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.7 15a1.7 1.7 0 0 0-1.56-1.03H3a2 2 0 1 1 0-4h.11A1.7 1.7 0 0 0 4.7 8.9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.7a1.7 1.7 0 0 0 1.03-1.56V3a2 2 0 1 1 4 0v.11A1.7 1.7 0 0 0 15.1 4.7a1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9v.03A1.7 1.7 0 0 0 21 10.06H21a2 2 0 1 1 0 4h-.11A1.7 1.7 0 0 0 19.4 15Z" />
</Svg>
);
}
export function SunIcon({ className }: IconProps) {
return (
<Svg className={className}>
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</Svg>
);
}
export function MoonIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M21 12.8A8.5 8.5 0 1 1 11.2 3a6.6 6.6 0 0 0 9.8 9.8Z" />
</Svg>
);
}
export function BoltIcon({ className }: IconProps) {
return (
<Svg className={className} filled>
<path d="M13.5 2 4 13.2h5.4L9.5 22 19 10.8h-5.4L13.5 2Z" />
</Svg>
);
}
export function FlameIcon({ className }: IconProps) {
return (
<Svg className={className} filled>
<path d="M12 2s4.5 4.2 4.5 8.2c0 1.2-.5 2.2-1.2 2.9.1-1.6-.5-3.3-1.8-4.6.3 2.6-1 4-2.2 5.1-1 .9-1.8 1.8-1.8 3.2 0 1 .4 1.9 1.1 2.6-2.9-.3-5.1-2.6-5.1-5.5C5.5 9.7 12 8 12 2Z" />
<path d="M12 22c2.2 0 4-1.6 4-3.7 0-1.6-.8-2.4-1.7-3.3-.6-.6-1.3-1.3-1.6-2.3-.8.8-1.4 2-1.4 3.2 0 .9-.4 1.4-1 1.8-.5.4-1.1.9-1.1 1.9C9.2 21.1 10.4 22 12 22Z" />
</Svg>
);
}
export function SparklesIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 3l1.6 4.4L18 9l-4.4 1.6L12 15l-1.6-4.4L6 9l4.4-1.6L12 3Z" />
<path d="M18.5 15.5l.8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8.8-2.2Z" />
</Svg>
);
}
export function LayersIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 3.5 20 8l-8 4.5L4 8l8-4.5Z" />
<path d="m4.6 12.4 7.4 4.2 7.4-4.2M4.6 16.4l7.4 4.2 7.4-4.2" />
</Svg>
);
}
export function FolderIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M3 7.5A1.5 1.5 0 0 1 4.5 6h4l2 2.5h9A1.5 1.5 0 0 1 21 10v8a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 18V7.5Z" />
</Svg>
);
}
export function CameraIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M4 8.5A1.5 1.5 0 0 1 5.5 7h1.9l1.1-1.8A1 1 0 0 1 9.4 5h5.2a1 1 0 0 1 .9.5L16.6 7h1.9A1.5 1.5 0 0 1 20 8.5v9A1.5 1.5 0 0 1 18.5 19h-13A1.5 1.5 0 0 1 4 17.5v-9Z" />
<circle cx="12" cy="12.8" r="3.2" />
</Svg>
);
}
export function PlayIcon({ className }: IconProps) {
return (
<Svg className={className} filled>
<path d="M8 5.5v13l11-6.5-11-6.5Z" />
</Svg>
);
}
export function DownloadIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 3v12M7.5 10.5 12 15l4.5-4.5M4 20h16" />
</Svg>
);
}
export function UploadIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 16V4M7.5 8.5 12 4l4.5 4.5M4 20h16" />
</Svg>
);
}
export function HomeIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M4 10.5 12 4l8 6.5V19a1 1 0 0 1-1 1h-4v-5H9v5H5a1 1 0 0 1-1-1v-8.5Z" />
</Svg>
);
}
export function CheckCircleIcon({ className }: IconProps) {
return (
<Svg className={className}>
<circle cx="12" cy="12" r="9" />
<path d="m8.2 12.4 2.6 2.6 5-5.4" />
</Svg>
);
}
export function AlertTriangleIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 4.5 21 19.5H3L12 4.5Z" />
<path d="M12 10v4.2M12 17.2v.1" />
</Svg>
);
}
export function XCircleIcon({ className }: IconProps) {
return (
<Svg className={className}>
<circle cx="12" cy="12" r="9" />
<path d="m9 9 6 6M15 9l-6 6" />
</Svg>
);
}
export function PlusIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 5v14M5 12h14" />
</Svg>
);
}
export function ShareIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M12 3v12M8.2 6.8 12 3l3.8 3.8" />
<path d="M6 12H5a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1h-1" />
</Svg>
);
}
export function ChevronLeftIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M14.5 5.5 8 12l6.5 6.5" />
</Svg>
);
}
export function ChevronRightIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="m9.5 5.5 6.5 6.5-6.5 6.5" />
</Svg>
);
}
export function RefreshIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M20 12a8 8 0 1 1-2.4-5.7M20 4v4h-4" />
</Svg>
);
}
export function EyeIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z" />
<circle cx="12" cy="12" r="3" />
</Svg>
);
}
export function EyeOffIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="m3 3 18 18" />
<path d="M10.6 5.7c.5-.1.9-.2 1.4-.2 6 0 9.5 6.5 9.5 6.5a17.5 17.5 0 0 1-2.6 3.5" />
<path d="M6.6 6.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.4 3.8-1" />
<path d="M9.9 9.9a3 3 0 0 0 4.2 4.2" />
</Svg>
);
}
export function QuestionIcon({ className }: IconProps) {
return (
<Svg className={className}>
<circle cx="12" cy="12" r="9" />
<path d="M9.7 9.4a2.5 2.5 0 0 1 4.9.7c0 1.7-2.5 2-2.5 3.5" />
<path d="M12 17h.01" />
</Svg>
);
}
export function XIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="m6 6 12 12M18 6 6 18" />
</Svg>
);
}
export function ExternalLinkIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="M14 4h6v6" />
<path d="m20 4-8.5 8.5" />
<path d="M18 13.5V19a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5.5" />
</Svg>
);
}
/* --- Install-sheet chips ------------------------------------------------ */
export function CompassIcon({ className }: IconProps) {
return (
<Svg className={className}>
<circle cx="12" cy="12" r="9" />
<path d="m15.6 8.4-2.3 5.3-5.3 2.3 2.3-5.3 5.3-2.3Z" />
</Svg>
);
}
export function EllipsisIcon({ className }: IconProps) {
return (
<Svg className={className}>
<circle cx="5.5" cy="12" r="1.4" fill="currentColor" stroke="none" />
<circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none" />
<circle cx="18.5" cy="12" r="1.4" fill="currentColor" stroke="none" />
</Svg>
);
}
export function ChevronDownIcon({ className }: IconProps) {
return (
<Svg className={className}>
<path d="m6 9.5 6 6 6-6" />
</Svg>
);
}
export function AddToHomeIcon({ className }: IconProps) {
return (
<Svg className={className}>
<rect x="4" y="4" width="16" height="16" rx="4.5" />
<path d="M12 8.8v6.4M8.8 12h6.4" />
</Svg>
);
}
export function ChromeIcon({ className }: IconProps) {
return (
<Svg className={className}>
<circle cx="12" cy="12" r="9" />
<circle cx="12" cy="12" r="3.4" />
<path d="M12 8.6V3M9.05 13.7 4.2 16.5M14.95 13.7l4.85 2.8" />
</Svg>
);
}