feat(planner): restyle Export + SaveToJournal buttons on tokens

Bring the two slotted topbar actions onto the design system so the bar
reads as one cohesive surface:

- SaveToJournalButton: primary Button primitive; "saved" uses the
  accent, error uses a new --color-danger token, and the return link
  gets secondary token styling.
- ExportButton: token split-button (secondary look, chevron icon,
  single divider) and a token dropdown menu (raised surface, soft
  shadow, muted descriptions).
- New --color-danger token (#a03c3c, the no-go hue at full strength)
  for error text.

Behavior and i18n unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-07-16 00:05:23 +02:00
parent 2bc6afcf4f
commit 1d3ba956cb
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
3 changed files with 32 additions and 31 deletions

View file

@ -52,45 +52,45 @@ export function ExportButton({ yjs }: { yjs: YjsState }) {
const hasMultipleDays = hasDayBreaks(yjs);
const item =
"block w-full px-3 py-1.5 text-left transition-colors hover:bg-bg-subtle";
const split =
"h-7 border border-border bg-bg-raised text-sm font-medium text-text-hi transition-colors hover:bg-bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent";
return (
<div ref={ref} className="relative z-[1001]">
<div className="flex">
<button
onClick={handleExportRoute}
className="rounded-l bg-gray-100 px-3 py-1 text-sm text-gray-700 hover:bg-gray-200"
className={`${split} rounded-l-md border-r-0 px-3`}
>
{t("exportGpx")}
</button>
<button
onClick={(e) => { e.stopPropagation(); setOpen((v) => !v); }}
className="rounded-r border-l border-gray-300 bg-gray-100 px-2.5 py-1 text-sm text-gray-700 hover:bg-gray-200"
aria-label={t("exportGpx")}
aria-expanded={open}
className={`${split} rounded-r-md px-2 text-text-md`}
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<polyline points="6 9 12 15 18 9" />
</svg>
</button>
</div>
{open && (
<div className="absolute left-0 top-full z-50 mt-1 w-56 rounded border border-gray-200 bg-white py-1 shadow-lg">
<button
onClick={handleExportRoute}
className="block w-full px-3 py-1.5 text-left hover:bg-gray-100"
>
<span className="text-sm text-gray-700">{t("exportRoute")}</span>
<span className="block text-xs text-gray-400">{t("exportRouteDesc")}</span>
<div className="absolute left-0 top-full z-50 mt-1 w-56 rounded-md border border-border bg-bg-raised py-1 shadow-md">
<button onClick={handleExportRoute} className={item}>
<span className="text-sm text-text-hi">{t("exportRoute")}</span>
<span className="block text-xs text-text-lo">{t("exportRouteDesc")}</span>
</button>
<button
onClick={handleExportPlan}
className="block w-full px-3 py-1.5 text-left hover:bg-gray-100"
>
<span className="text-sm text-gray-700">{t("exportPlan")}</span>
<span className="block text-xs text-gray-400">{t("exportPlanDesc")}</span>
<button onClick={handleExportPlan} className={item}>
<span className="text-sm text-text-hi">{t("exportPlan")}</span>
<span className="block text-xs text-text-lo">{t("exportPlanDesc")}</span>
</button>
{hasMultipleDays && (
<button
onClick={handleExportDays}
className="block w-full px-3 py-1.5 text-left hover:bg-gray-100"
>
<span className="text-sm text-gray-700">{t("exportDays")}</span>
<span className="block text-xs text-gray-400">{t("exportDaysDesc")}</span>
<button onClick={handleExportDays} className={item}>
<span className="text-sm text-text-hi">{t("exportDays")}</span>
<span className="block text-xs text-text-lo">{t("exportDaysDesc")}</span>
</button>
)}
</div>

View file

@ -1,5 +1,6 @@
import { useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@trails-cool/ui";
import { computeSurfaceBreakdown } from "@trails-cool/map-core";
import type { YjsState } from "~/lib/use-yjs";
import { buildPlanGpx } from "~/lib/gpx-export";
@ -64,17 +65,16 @@ export function SaveToJournalButton({ yjs, sessionId, returnUrl }: SaveToJournal
return (
<div className="flex items-center gap-2">
<button
onClick={handleSave}
disabled={saving}
className="rounded bg-green-600 px-3 py-1 text-sm text-white hover:bg-green-700 disabled:opacity-50"
>
<Button variant="primary" size="sm" onClick={handleSave} disabled={saving}>
{saving ? t("saving") : t("saveToJournal")}
</button>
{saved && <span className="text-xs text-green-600">{t("saved")}</span>}
{error && <span className="text-xs text-red-600">{error}</span>}
</Button>
{saved && <span className="text-xs text-accent">{t("saved")}</span>}
{error && <span className="text-xs text-danger">{error}</span>}
{saved && returnUrl && (
<a href={returnUrl} className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-700 hover:bg-gray-200">
<a
href={returnUrl}
className="inline-flex h-7 items-center rounded-md border border-border bg-bg-raised px-2.5 text-xs font-medium text-text-hi transition-colors hover:bg-bg-subtle"
>
{t("returnToJournal")}
</a>
)}