polish(water-log/settings): Apple HIG refinements to the Smartsheet surface
Deploy to route.crispygoat.com / deploy (push) Successful in 4m0s
Deploy to route.crispygoat.com / deploy (push) Successful in 4m0s
Layer HIG polish on top of the Field Almanac visual identity — no layout change, just quality. Per-element polish: - Inputs: focus-visible ring (4px at 15% opacity), subtle inner shadow, 44pt min tap target, motion-safe transition. - Selects: same focus ring + min height + transition. - Toggles: Apple spring curve (cubic-bezier 0.32,0.72,0,1), 26×46 track, 20px thumb, 200ms color, 200ms spring transform, focus ring, active:scale-0.97. - Buttons: focus-visible ring, active:scale-0.97 press state, 48pt primary CTA, 40pt secondary, refined hover with subtle lift shadow. - Status banner: inline SVG icon (filled circle + check or info-i), polite/assertive aria-live, slide-down entrance. - Test Connection result: HIG-style icon (filled green/red circle + stroke check/x), refined hierarchy. - Backfill result + error: same HIG pattern as status banner. - Modal: backdrop-blur-[6px], spring entrance (sheet-up 220ms), rounded-t-2xl on mobile (sheet-style) and rounded-2xl on sm+. - All animations: motion-safe: + a prefers-reduced-motion block in globals.css that disables them. Type system: kept the existing --font-display (Field Almanac serif identity) for headings; system-ui for body remains the fallback chain in the existing --font-sans token. New keyframes in globals.css: smartsheet-fade-in, smartsheet-sheet-up, smartsheet-slide-down. All short (180–220ms) to feel responsive. Also: button label 'Save Settings' → 'Save changes' (sentence case, matches HIG register) on the parent settings page and the Smartsheet card.
This commit is contained in:
@@ -261,13 +261,44 @@ export default function WaterLogSettingsPage() {
|
|||||||
<form onSubmit={handleSave} className="space-y-6">
|
<form onSubmit={handleSave} className="space-y-6">
|
||||||
{message && (
|
{message && (
|
||||||
<div
|
<div
|
||||||
className={`rounded-lg border px-4 py-3 text-sm font-medium ${
|
role={message.type === "error" ? "alert" : "status"}
|
||||||
|
aria-live={message.type === "error" ? "assertive" : "polite"}
|
||||||
|
className={`smartsheet-slide-down flex items-start gap-3 rounded-lg border px-4 py-3 text-[13.5px] font-medium
|
||||||
|
${
|
||||||
message.type === "success"
|
message.type === "success"
|
||||||
? "border-[#1a4d2e] bg-[#1a4d2e]/5 text-[#1a4d2e]"
|
? "border-[#1a4d2e]/25 bg-[#1a4d2e]/[0.05] text-[#143d24]"
|
||||||
: "border-[#a4452b] bg-[#a4452b]/5 text-[#a4452b]"
|
: "border-[#a4452b]/25 bg-[#a4452b]/[0.05] text-[#7a341f]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{message.text}
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={`mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-white ${
|
||||||
|
message.type === "success" ? "bg-[#1a4d2e]" : "bg-[#a4452b]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{message.type === "success" ? (
|
||||||
|
<svg viewBox="0 0 12 12" className="h-3 w-3">
|
||||||
|
<path
|
||||||
|
d="M2 6.5L5 9.5L10 3.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg viewBox="0 0 12 12" className="h-3 w-3">
|
||||||
|
<path
|
||||||
|
d="M6 3V6.5M6 8.5V9"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<p className="leading-snug">{message.text}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -317,9 +348,22 @@ export default function WaterLogSettingsPage() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleRegenerate}
|
onClick={handleRegenerate}
|
||||||
disabled={regenerating}
|
disabled={regenerating}
|
||||||
className="shrink-0 rounded-lg border border-[#1a4d2e] bg-[#1a4d2e] px-4 py-2 text-sm font-semibold text-white transition hover:bg-[#143d24] disabled:opacity-50"
|
className="inline-flex min-h-[40px] shrink-0 items-center justify-center rounded-lg border border-[#1a4d2e] bg-[#1a4d2e] px-4 py-2
|
||||||
|
text-[14px] font-semibold text-white
|
||||||
|
motion-safe:transition-[background-color,transform] motion-safe:duration-150 motion-safe:ease-out
|
||||||
|
hover:bg-[#143d24]
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/30
|
||||||
|
active:scale-[0.97] active:bg-[#0f2e1c]
|
||||||
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{regenerating ? "Generating…" : "Regenerate PIN"}
|
{regenerating ? (
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-white/30 border-t-white motion-reduce:animate-none" />
|
||||||
|
Generating…
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"Regenerate PIN"
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -393,7 +437,10 @@ export default function WaterLogSettingsPage() {
|
|||||||
dispatch({ type: "SET_ALERT_PHONE", value: e.target.value })
|
dispatch({ type: "SET_ALERT_PHONE", value: e.target.value })
|
||||||
}
|
}
|
||||||
placeholder="+13035551234"
|
placeholder="+13035551234"
|
||||||
className="mt-1 w-full rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5 text-base outline-none focus:border-[#1a4d2e] focus:ring-1 focus:ring-[#1a4d2e]"
|
className="mt-1 block min-h-[44px] w-full rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5
|
||||||
|
text-[14px] text-[#1d1d1f] placeholder:text-[#a3a5a0]
|
||||||
|
outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
|
||||||
|
focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
|
||||||
autoComplete="tel"
|
autoComplete="tel"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-[#8a8b88]">
|
<p className="mt-1 text-xs text-[#8a8b88]">
|
||||||
@@ -408,9 +455,23 @@ export default function WaterLogSettingsPage() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="w-full rounded-lg bg-[#1a4d2e] px-6 py-3.5 text-sm font-semibold uppercase tracking-wider text-white transition hover:bg-[#143d24] disabled:opacity-50"
|
className="inline-flex min-h-[48px] w-full items-center justify-center rounded-lg bg-[#1a4d2e] px-6 py-3
|
||||||
|
text-[14px] font-semibold tracking-tight text-white
|
||||||
|
shadow-[0_1px_0_rgba(0,0,0,0.06),inset_0_1px_0_rgba(255,255,255,0.06)]
|
||||||
|
motion-safe:transition-[background-color,transform,box-shadow] motion-safe:duration-150 motion-safe:ease-out
|
||||||
|
hover:bg-[#143d24] hover:shadow-[0_2px_4px_rgba(15,23,18,0.1),inset_0_1px_0_rgba(255,255,255,0.06)]
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/30 focus-visible:ring-offset-2 focus-visible:ring-offset-[#fdfaf2]
|
||||||
|
active:scale-[0.985] active:bg-[#0f2e1c]
|
||||||
|
disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saving ? "Saving…" : "Save Settings"}
|
{saving ? (
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white motion-reduce:animate-none" />
|
||||||
|
Saving…
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"Save changes"
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -450,10 +511,16 @@ function Card({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section className="rounded-xl border border-[#d4d9d3] bg-white p-5 shadow-sm">
|
<section className="rounded-xl border border-[#d4d9d3] bg-white/95 p-6 shadow-[0_1px_2px_rgba(15,23,18,0.04),0_2px_6px_-2px_rgba(15,23,18,0.06)]">
|
||||||
<header className="mb-4">
|
<header className="mb-5">
|
||||||
<h2 className="text-base font-semibold text-[#1d1d1f]">{title}</h2>
|
<h2 className="text-[15px] font-semibold tracking-tight text-[#1d1d1f]">
|
||||||
{subtitle && <p className="mt-0.5 text-xs text-[#5a5d5a]">{subtitle}</p>}
|
{title}
|
||||||
|
</h2>
|
||||||
|
{subtitle && (
|
||||||
|
<p className="mt-1 text-[13px] leading-snug text-[#5a5d5a]">
|
||||||
|
{subtitle}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
{children}
|
{children}
|
||||||
</section>
|
</section>
|
||||||
@@ -483,14 +550,17 @@ function ToggleRow({
|
|||||||
aria-checked={value}
|
aria-checked={value}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
onClick={() => onChange(!value)}
|
onClick={() => onChange(!value)}
|
||||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors ${
|
className={`group relative inline-flex h-[26px] w-[46px] shrink-0 items-center rounded-full
|
||||||
value ? "bg-[#1a4d2e]" : "bg-[#d4d9d3]"
|
motion-safe:transition-colors motion-safe:duration-200
|
||||||
}`}
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25 focus-visible:ring-offset-2 focus-visible:ring-offset-white
|
||||||
|
active:scale-[0.97] motion-safe:transition-transform motion-safe:ease-out
|
||||||
|
${value ? "bg-[#1a4d2e]" : "bg-[#d4d9d3]"}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${
|
className={`inline-block h-[20px] w-[20px] transform rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.2),0_1px_1px_rgba(0,0,0,0.06)]
|
||||||
value ? "translate-x-6" : "translate-x-1"
|
motion-safe:transition-transform motion-safe:duration-200
|
||||||
}`}
|
motion-safe:ease-[cubic-bezier(0.32,0.72,0,1)]
|
||||||
|
${value ? "translate-x-[22px]" : "translate-x-[3px]"}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1104,6 +1104,32 @@ select:-webkit-autofill:focus {
|
|||||||
.atelier-backdrop {
|
.atelier-backdrop {
|
||||||
animation: atelier-backdrop 140ms ease-out both;
|
animation: atelier-backdrop 140ms ease-out both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Smartsheet integration polish — Apple HIG-inspired micro-animations.
|
||||||
|
* Kept short (180–220ms) and single-property to feel responsive. The
|
||||||
|
* global prefers-reduced-motion block further down disables all of
|
||||||
|
* these for users who request reduced motion. */
|
||||||
|
@keyframes smartsheet-fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
@keyframes smartsheet-sheet-up {
|
||||||
|
from { opacity: 0; transform: translateY(8px) scale(0.985); }
|
||||||
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
|
}
|
||||||
|
@keyframes smartsheet-slide-down {
|
||||||
|
from { opacity: 0; transform: translateY(-4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.smartsheet-fade-in {
|
||||||
|
animation: smartsheet-fade-in 180ms ease-out both;
|
||||||
|
}
|
||||||
|
.smartsheet-sheet-up {
|
||||||
|
animation: smartsheet-sheet-up 220ms cubic-bezier(0.32, 0.72, 0, 1) both;
|
||||||
|
}
|
||||||
|
.smartsheet-slide-down {
|
||||||
|
animation: smartsheet-slide-down 180ms cubic-bezier(0.32, 0.72, 0, 1) both;
|
||||||
|
}
|
||||||
.atelier-stagger > * {
|
.atelier-stagger > * {
|
||||||
animation: atelier-stagger 180ms ease-out both;
|
animation: atelier-stagger 180ms ease-out both;
|
||||||
/* No more 80-440ms cumulative delays — items appear together, not as a parade. */
|
/* No more 80-440ms cumulative delays — items appear together, not as a parade. */
|
||||||
|
|||||||
@@ -402,13 +402,44 @@ export default function SmartsheetIntegrationCard({
|
|||||||
<form onSubmit={handleSave} className="space-y-6">
|
<form onSubmit={handleSave} className="space-y-6">
|
||||||
{state.message && (
|
{state.message && (
|
||||||
<div
|
<div
|
||||||
className={`rounded-lg border px-4 py-3 text-sm font-medium ${
|
role={state.message.type === "error" ? "alert" : "status"}
|
||||||
|
aria-live={state.message.type === "error" ? "assertive" : "polite"}
|
||||||
|
className={`smartsheet-slide-down flex items-start gap-3 rounded-lg border px-4 py-3 text-[13.5px]
|
||||||
|
${
|
||||||
state.message.type === "success"
|
state.message.type === "success"
|
||||||
? "border-[#1a4d2e] bg-[#1a4d2e]/5 text-[#1a4d2e]"
|
? "border-[#1a4d2e]/25 bg-[#1a4d2e]/[0.05] text-[#143d24]"
|
||||||
: "border-[#a4452b] bg-[#a4452b]/5 text-[#a4452b]"
|
: "border-[#a4452b]/25 bg-[#a4452b]/[0.05] text-[#7a341f]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{state.message.text}
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={`mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-white ${
|
||||||
|
state.message.type === "success" ? "bg-[#1a4d2e]" : "bg-[#a4452b]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{state.message.type === "success" ? (
|
||||||
|
<svg viewBox="0 0 12 12" className="h-3 w-3">
|
||||||
|
<path
|
||||||
|
d="M2 6.5L5 9.5L10 3.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg viewBox="0 0 12 12" className="h-3 w-3">
|
||||||
|
<path
|
||||||
|
d="M6 3V6.5M6 8.5V9"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<p className="font-medium leading-snug">{state.message.text}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -431,7 +462,12 @@ export default function SmartsheetIntegrationCard({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => dispatch({ type: "OPEN_SETUP_MODAL" })}
|
onClick={() => dispatch({ type: "OPEN_SETUP_MODAL" })}
|
||||||
className="rounded-md border border-[#d4d9d3] bg-white px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-[#5a5d5a] transition hover:border-[#1a4d2e] hover:text-[#1a4d2e]"
|
className="inline-flex min-h-[32px] items-center justify-center rounded-lg border border-[#d4d9d3] bg-white px-3.5 py-1.5
|
||||||
|
text-[12.5px] font-semibold text-[#5a5d5a]
|
||||||
|
motion-safe:transition-[background-color,border-color,color] motion-safe:duration-150
|
||||||
|
hover:border-[#1a4d2e] hover:bg-[#f4f1e8] hover:text-[#1a4d2e]
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/20
|
||||||
|
active:scale-[0.97] motion-safe:transition-transform motion-safe:ease-out"
|
||||||
>
|
>
|
||||||
Setup instructions
|
Setup instructions
|
||||||
</button>
|
</button>
|
||||||
@@ -453,7 +489,11 @@ export default function SmartsheetIntegrationCard({
|
|||||||
dispatch({ type: "SET_SHEET_ID", value: e.target.value })
|
dispatch({ type: "SET_SHEET_ID", value: e.target.value })
|
||||||
}
|
}
|
||||||
placeholder="https://app.smartsheet.com/sheets/AbCdEfGhIjKlMn or 123456789012345"
|
placeholder="https://app.smartsheet.com/sheets/AbCdEfGhIjKlMn or 123456789012345"
|
||||||
className="w-full rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5 font-mono text-sm outline-none focus:border-[#1a4d2e] focus:ring-1 focus:ring-[#1a4d2e]"
|
className="block min-h-[44px] w-full rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5
|
||||||
|
font-mono text-[14px] text-[#1d1d1f] placeholder:text-[#a3a5a0]
|
||||||
|
shadow-[inset_0_1px_0_rgba(15,23,18,0.02)]
|
||||||
|
outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
|
||||||
|
focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-[#8a8b88]">
|
<p className="text-xs text-[#8a8b88]">
|
||||||
@@ -484,13 +524,22 @@ export default function SmartsheetIntegrationCard({
|
|||||||
? `Leave blank to keep the saved token (${state.savedConfig.maskedToken ?? "••••"})`
|
? `Leave blank to keep the saved token (${state.savedConfig.maskedToken ?? "••••"})`
|
||||||
: "Paste your Smartsheet API token"
|
: "Paste your Smartsheet API token"
|
||||||
}
|
}
|
||||||
className="flex-1 rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5 font-mono text-sm outline-none focus:border-[#1a4d2e] focus:ring-1 focus:ring-[#1a4d2e]"
|
className="min-h-[44px] flex-1 rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5
|
||||||
|
font-mono text-[14px] text-[#1d1d1f] placeholder:text-[#a3a5a0]
|
||||||
|
shadow-[inset_0_1px_0_rgba(15,23,18,0.02)]
|
||||||
|
outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
|
||||||
|
focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => dispatch({ type: "TOGGLE_SHOW_TOKEN" })}
|
onClick={() => dispatch({ type: "TOGGLE_SHOW_TOKEN" })}
|
||||||
className="rounded-lg border border-[#d4d9d3] bg-white px-3 text-xs font-medium text-[#5a5d5a] hover:bg-[#f4f1e8]"
|
className="inline-flex min-h-[44px] items-center justify-center rounded-lg border border-[#d4d9d3] bg-white px-3
|
||||||
|
text-[13px] font-medium text-[#5a5d5a]
|
||||||
|
motion-safe:transition-colors motion-safe:duration-150
|
||||||
|
hover:bg-[#f4f1e8] hover:text-[#1d1d1f]
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/20
|
||||||
|
active:scale-[0.97] motion-safe:transition-transform motion-safe:ease-out"
|
||||||
aria-label={state.showToken ? "Hide token" : "Show token"}
|
aria-label={state.showToken ? "Hide token" : "Show token"}
|
||||||
>
|
>
|
||||||
{state.showToken ? "Hide" : "Show"}
|
{state.showToken ? "Hide" : "Show"}
|
||||||
@@ -519,9 +568,22 @@ export default function SmartsheetIntegrationCard({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleTest}
|
onClick={handleTest}
|
||||||
disabled={state.test.state === "loading"}
|
disabled={state.test.state === "loading"}
|
||||||
className="rounded-lg border border-[#1a4d2e] bg-white px-4 py-2 text-sm font-semibold text-[#1a4d2e] transition hover:bg-[#1a4d2e] hover:text-white disabled:opacity-50"
|
className="inline-flex min-h-[40px] items-center justify-center rounded-lg border border-[#1a4d2e] bg-white px-4 py-2
|
||||||
|
text-[14px] font-semibold text-[#1a4d2e]
|
||||||
|
motion-safe:transition-[background-color,color,transform] motion-safe:duration-150 motion-safe:ease-out
|
||||||
|
hover:bg-[#1a4d2e] hover:text-white
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25
|
||||||
|
active:scale-[0.97]
|
||||||
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{state.test.state === "loading" ? "Testing…" : "Test Connection"}
|
{state.test.state === "loading" ? (
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-current border-r-transparent motion-reduce:animate-none" />
|
||||||
|
Testing…
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"Test Connection"
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
<TestResultPanel test={state.test} />
|
<TestResultPanel test={state.test} />
|
||||||
</div>
|
</div>
|
||||||
@@ -535,11 +597,15 @@ export default function SmartsheetIntegrationCard({
|
|||||||
{(Object.keys(FREQUENCY_LABELS) as SmartsheetFrequency[]).map((f) => (
|
{(Object.keys(FREQUENCY_LABELS) as SmartsheetFrequency[]).map((f) => (
|
||||||
<label
|
<label
|
||||||
key={f}
|
key={f}
|
||||||
className={`flex cursor-pointer items-start gap-3 rounded-lg border px-4 py-3 transition ${
|
className={`flex cursor-pointer items-start gap-3 rounded-lg border px-4 py-3
|
||||||
|
motion-safe:transition-[border-color,background-color] motion-safe:duration-150
|
||||||
|
${
|
||||||
state.frequency === f
|
state.frequency === f
|
||||||
? "border-[#1a4d2e] bg-[#1a4d2e]/5"
|
? "border-[#1a4d2e] bg-[#1a4d2e]/[0.04]"
|
||||||
: "border-[#d4d9d3] bg-white hover:border-[#1a4d2e]"
|
: "border-[#d4d9d3] bg-white hover:border-[#a8b4a4] hover:bg-[#fcfaf3]"
|
||||||
}`}
|
}
|
||||||
|
focus-within:ring-4 focus-within:ring-[#1a4d2e]/15 focus-within:border-[#1a4d2e]
|
||||||
|
active:scale-[0.997] motion-safe:transition-transform motion-safe:ease-out`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
@@ -549,9 +615,9 @@ export default function SmartsheetIntegrationCard({
|
|||||||
onChange={() =>
|
onChange={() =>
|
||||||
dispatch({ type: "SET_FREQUENCY", value: f })
|
dispatch({ type: "SET_FREQUENCY", value: f })
|
||||||
}
|
}
|
||||||
className="mt-0.5 accent-[#1a4d2e]"
|
className="mt-0.5 h-4 w-4 cursor-pointer accent-[#1a4d2e]"
|
||||||
/>
|
/>
|
||||||
<span className="flex-1 text-sm font-medium text-[#1d1d1f]">
|
<span className="flex-1 text-[14px] font-medium text-[#1d1d1f]">
|
||||||
{FREQUENCY_LABELS[f]}
|
{FREQUENCY_LABELS[f]}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -608,7 +674,10 @@ export default function SmartsheetIntegrationCard({
|
|||||||
value: e.target.value,
|
value: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="flex-1 rounded-lg border border-[#d4d9d3] bg-white px-3 py-2 text-sm outline-none focus:border-[#1a4d2e] focus:ring-1 focus:ring-[#1a4d2e]"
|
className="min-h-[40px] flex-1 rounded-lg border border-[#d4d9d3] bg-white px-3 py-2
|
||||||
|
text-[14px] text-[#1d1d1f]
|
||||||
|
outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
|
||||||
|
focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
|
||||||
>
|
>
|
||||||
<option value="">
|
<option value="">
|
||||||
— {required ? "Select a column" : "Not mapped"} —
|
— {required ? "Select a column" : "Not mapped"} —
|
||||||
@@ -635,7 +704,10 @@ export default function SmartsheetIntegrationCard({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder="Column ID (numeric)"
|
placeholder="Column ID (numeric)"
|
||||||
className="flex-1 rounded-lg border border-[#d4d9d3] bg-white px-3 py-2 font-mono text-sm outline-none focus:border-[#1a4d2e] focus:ring-1 focus:ring-[#1a4d2e]"
|
className="min-h-[40px] flex-1 rounded-lg border border-[#d4d9d3] bg-white px-3 py-2
|
||||||
|
font-mono text-[14px] text-[#1d1d1f] placeholder:text-[#a3a5a0]
|
||||||
|
outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
|
||||||
|
focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -700,9 +772,23 @@ export default function SmartsheetIntegrationCard({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={state.saving || noTokenYet}
|
disabled={state.saving || noTokenYet}
|
||||||
className="w-full rounded-lg bg-[#1a4d2e] px-6 py-3.5 text-sm font-semibold uppercase tracking-wider text-white transition hover:bg-[#143d24] disabled:opacity-50"
|
className="inline-flex min-h-[48px] w-full items-center justify-center rounded-lg bg-[#1a4d2e] px-6 py-3
|
||||||
|
text-[14px] font-semibold tracking-tight text-white
|
||||||
|
shadow-[0_1px_0_rgba(0,0,0,0.06),inset_0_1px_0_rgba(255,255,255,0.06)]
|
||||||
|
motion-safe:transition-[background-color,transform,box-shadow] motion-safe:duration-150 motion-safe:ease-out
|
||||||
|
hover:bg-[#143d24] hover:shadow-[0_2px_4px_rgba(15,23,18,0.1),inset_0_1px_0_rgba(255,255,255,0.06)]
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/30 focus-visible:ring-offset-2 focus-visible:ring-offset-[#fdfaf2]
|
||||||
|
active:scale-[0.985] active:bg-[#0f2e1c]
|
||||||
|
disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-[#1a4d2e]"
|
||||||
>
|
>
|
||||||
{state.saving ? "Saving…" : "Save Settings"}
|
{state.saving ? (
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white motion-reduce:animate-none" />
|
||||||
|
Saving…
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"Save changes"
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{noTokenYet && (
|
{noTokenYet && (
|
||||||
@@ -734,11 +820,17 @@ function Card({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section className="rounded-xl border border-[#d4d9d3] bg-white p-5 shadow-sm">
|
<section className="rounded-xl border border-[#d4d9d3] bg-white/95 p-6 shadow-[0_1px_2px_rgba(15,23,18,0.04),0_2px_6px_-2px_rgba(15,23,18,0.06)] motion-safe:transition-shadow">
|
||||||
<header className="mb-4 flex items-start justify-between gap-3">
|
<header className="mb-5 flex items-start justify-between gap-4">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h2 className="text-base font-semibold text-[#1d1d1f]">{title}</h2>
|
<h2 className="text-[15px] font-semibold tracking-tight text-[#1d1d1f]">
|
||||||
{subtitle && <p className="mt-0.5 text-xs text-[#5a5d5a]">{subtitle}</p>}
|
{title}
|
||||||
|
</h2>
|
||||||
|
{subtitle && (
|
||||||
|
<p className="mt-1 text-[13px] leading-snug text-[#5a5d5a]">
|
||||||
|
{subtitle}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{action && <div className="shrink-0">{action}</div>}
|
{action && <div className="shrink-0">{action}</div>}
|
||||||
</header>
|
</header>
|
||||||
@@ -759,11 +851,15 @@ function ToggleRow({
|
|||||||
onChange: (v: boolean) => void;
|
onChange: (v: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-4 py-1">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-medium text-[#1d1d1f]">{label}</p>
|
<p className="text-[14px] font-medium leading-snug text-[#1d1d1f]">
|
||||||
|
{label}
|
||||||
|
</p>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="mt-0.5 text-xs text-[#5a5d5a]">{description}</p>
|
<p className="mt-0.5 text-[12.5px] leading-snug text-[#5a5d5a]">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -772,14 +868,17 @@ function ToggleRow({
|
|||||||
aria-checked={value}
|
aria-checked={value}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
onClick={() => onChange(!value)}
|
onClick={() => onChange(!value)}
|
||||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors ${
|
className={`group relative inline-flex h-[26px] w-[46px] shrink-0 items-center rounded-full
|
||||||
value ? "bg-[#1a4d2e]" : "bg-[#d4d9d3]"
|
motion-safe:transition-colors motion-safe:duration-200
|
||||||
}`}
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25 focus-visible:ring-offset-2 focus-visible:ring-offset-white
|
||||||
|
active:scale-[0.97] motion-safe:transition-transform motion-safe:ease-out
|
||||||
|
${value ? "bg-[#1a4d2e]" : "bg-[#d4d9d3]"}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${
|
className={`inline-block h-[20px] w-[20px] transform rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.2),0_1px_1px_rgba(0,0,0,0.06)]
|
||||||
value ? "translate-x-6" : "translate-x-1"
|
motion-safe:transition-transform motion-safe:duration-200
|
||||||
}`}
|
motion-safe:ease-[cubic-bezier(0.32,0.72,0,1)]
|
||||||
|
${value ? "translate-x-[22px]" : "translate-x-[3px]"}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -789,20 +888,48 @@ function ToggleRow({
|
|||||||
function TestResultPanel({ test }: { test: TestState }) {
|
function TestResultPanel({ test }: { test: TestState }) {
|
||||||
if (test.state === "idle") return null;
|
if (test.state === "idle") return null;
|
||||||
if (test.state === "loading") {
|
if (test.state === "loading") {
|
||||||
return <span className="text-sm text-[#5a5d5a]">Testing…</span>;
|
return (
|
||||||
|
<span className="inline-flex items-center gap-2 text-[13px] text-[#5a5d5a]">
|
||||||
|
<span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-[#d4d9d3] border-t-[#1a4d2e] motion-reduce:animate-none" />
|
||||||
|
Testing connection…
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (test.state === "ok") {
|
if (test.state === "ok") {
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 rounded-lg border border-[#1a4d2e] bg-[#1a4d2e]/5 px-3 py-2 text-xs">
|
<div className="flex flex-1 items-start gap-2 rounded-lg border border-[#1a4d2e]/25 bg-[#1a4d2e]/[0.04] px-3 py-2 text-[13px]">
|
||||||
<p className="font-semibold text-[#1a4d2e]">
|
<span className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#1a4d2e] text-white">
|
||||||
✓ Connected to “{test.sheetName}” ({test.columnCount} columns)
|
<svg viewBox="0 0 12 12" className="h-2.5 w-2.5" aria-hidden>
|
||||||
|
<path
|
||||||
|
d="M2 6.5L5 9.5L10 3.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<p className="text-[#1a4d2e]">
|
||||||
|
Connected to <span className="font-semibold">“{test.sheetName}”</span>{" "}
|
||||||
|
<span className="text-[#3a6649]">— {test.columnCount} columns loaded</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 rounded-lg border border-[#a4452b] bg-[#a4452b]/5 px-3 py-2 text-xs">
|
<div className="flex flex-1 items-start gap-2 rounded-lg border border-[#a4452b]/25 bg-[#a4452b]/[0.04] px-3 py-2 text-[13px]">
|
||||||
<p className="font-semibold text-[#a4452b]">✗ {test.message}</p>
|
<span className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#a4452b] text-white">
|
||||||
|
<svg viewBox="0 0 12 12" className="h-2.5 w-2.5" aria-hidden>
|
||||||
|
<path
|
||||||
|
d="M3 3L9 9M9 3L3 9"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<p className="text-[#a4452b]">{test.message}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -848,7 +975,11 @@ function BackfillCard({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onRangeChange(e.target.value as "all" | "30d" | "7d")
|
onRangeChange(e.target.value as "all" | "30d" | "7d")
|
||||||
}
|
}
|
||||||
className="rounded-lg border border-[#d4d9d3] bg-white px-3 py-2 text-sm outline-none focus:border-[#1a4d2e] focus:ring-1 focus:ring-[#1a4d2e]"
|
className="min-h-[40px] rounded-lg border border-[#d4d9d3] bg-white px-3 py-2
|
||||||
|
text-[14px] text-[#1d1d1f]
|
||||||
|
outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
|
||||||
|
focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15
|
||||||
|
disabled:opacity-50"
|
||||||
disabled={state.running}
|
disabled={state.running}
|
||||||
>
|
>
|
||||||
<option value="all">All time</option>
|
<option value="all">All time</option>
|
||||||
@@ -859,9 +990,22 @@ function BackfillCard({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onRun}
|
onClick={onRun}
|
||||||
disabled={state.running}
|
disabled={state.running}
|
||||||
className="rounded-lg border border-[#1a4d2e] bg-white px-4 py-2 text-sm font-semibold text-[#1a4d2e] transition hover:bg-[#1a4d2e] hover:text-white disabled:opacity-50"
|
className="inline-flex min-h-[40px] items-center justify-center rounded-lg border border-[#1a4d2e] bg-white px-4 py-2
|
||||||
|
text-[14px] font-semibold text-[#1a4d2e]
|
||||||
|
motion-safe:transition-[background-color,color,transform] motion-safe:duration-150 motion-safe:ease-out
|
||||||
|
hover:bg-[#1a4d2e] hover:text-white
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25
|
||||||
|
active:scale-[0.97]
|
||||||
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{state.running ? "Running backfill…" : "Run backfill"}
|
{state.running ? (
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-current border-r-transparent motion-reduce:animate-none" />
|
||||||
|
Running backfill…
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"Run backfill"
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -873,15 +1017,48 @@ function BackfillCard({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{showError && (
|
{showError && (
|
||||||
<div className="rounded-lg border border-[#a4452b] bg-[#a4452b]/5 px-3 py-2 text-xs">
|
<div
|
||||||
<p className="font-semibold text-[#a4452b]">✗ {result.error}</p>
|
role="alert"
|
||||||
|
aria-live="assertive"
|
||||||
|
className="flex items-start gap-2.5 rounded-lg border border-[#a4452b]/25 bg-[#a4452b]/[0.05] px-3 py-2.5 text-[13px]"
|
||||||
|
>
|
||||||
|
<span className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#a4452b] text-white">
|
||||||
|
<svg viewBox="0 0 12 12" className="h-2.5 w-2.5">
|
||||||
|
<path
|
||||||
|
d="M3 3L9 9M9 3L3 9"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<p className="font-medium leading-snug text-[#7a341f]">
|
||||||
|
{result.error}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showResult && result && (
|
{showResult && result && (
|
||||||
<div className="rounded-lg border border-[#1a4d2e] bg-[#1a4d2e]/5 px-3 py-2 text-xs">
|
<div
|
||||||
<p className="font-semibold text-[#1a4d2e]">
|
role="status"
|
||||||
✓ Found {result.considered}{" "}
|
aria-live="polite"
|
||||||
|
className="flex items-start gap-2.5 rounded-lg border border-[#1a4d2e]/25 bg-[#1a4d2e]/[0.04] px-3 py-2.5 text-[13px]"
|
||||||
|
>
|
||||||
|
<span className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#1a4d2e] text-white">
|
||||||
|
<svg viewBox="0 0 12 12" className="h-2.5 w-2.5">
|
||||||
|
<path
|
||||||
|
d="M2 6.5L5 9.5L10 3.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<div className="text-[#143d24]">
|
||||||
|
<p className="font-medium leading-snug">
|
||||||
|
Found {result.considered}{" "}
|
||||||
{result.considered === 1 ? "entry" : "entries"} matching the
|
{result.considered === 1 ? "entry" : "entries"} matching the
|
||||||
range
|
range
|
||||||
{result.considered > 0 && (
|
{result.considered > 0 && (
|
||||||
@@ -891,20 +1068,19 @@ function BackfillCard({
|
|||||||
queued).
|
queued).
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
.
|
|
||||||
</p>
|
</p>
|
||||||
{(result.drained.synced +
|
{(result.drained.synced +
|
||||||
result.drained.skipped +
|
result.drained.skipped +
|
||||||
result.drained.failed >
|
result.drained.failed >
|
||||||
0 || result.drained.remaining > 0) && (
|
0 || result.drained.remaining > 0) && (
|
||||||
<p className="mt-1 text-[#1d1d1f]">
|
<p className="mt-1 leading-snug text-[#1a4d2e]/85">
|
||||||
Inline drain: {result.drained.synced} synced /{" "}
|
Inline drain: {result.drained.synced} synced /{" "}
|
||||||
{result.drained.skipped} skipped / {result.drained.failed}{" "}
|
{result.drained.skipped} skipped / {result.drained.failed}{" "}
|
||||||
failed.
|
failed.
|
||||||
{result.drained.remaining > 0 && (
|
{result.drained.remaining > 0 && (
|
||||||
<>
|
<>
|
||||||
{" "}
|
{" "}
|
||||||
<strong className="text-[#1a4d2e]">
|
<strong className="font-semibold text-[#1a4d2e]">
|
||||||
{result.drained.remaining} still queued
|
{result.drained.remaining} still queued
|
||||||
</strong>{" "}
|
</strong>{" "}
|
||||||
— they'll drain on the next cron tick (within 15
|
— they'll drain on the next cron tick (within 15
|
||||||
@@ -918,6 +1094,7 @@ function BackfillCard({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -998,7 +1175,8 @@ const FIELD_SCHEMA: Array<{
|
|||||||
function SetupInstructionsModal({ onClose }: { onClose: () => void }) {
|
function SetupInstructionsModal({ onClose }: { onClose: () => void }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
|
className="smartsheet-fade-in fixed inset-0 z-50 flex items-end justify-center bg-black/40 p-0 backdrop-blur-[6px]
|
||||||
|
sm:items-center sm:p-4"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="smartsheet-setup-title"
|
aria-labelledby="smartsheet-setup-title"
|
||||||
@@ -1007,16 +1185,21 @@ function SetupInstructionsModal({ onClose }: { onClose: () => void }) {
|
|||||||
if (e.target === e.currentTarget) onClose();
|
if (e.target === e.currentTarget) onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative max-h-[90vh] w-full max-w-2xl overflow-y-auto rounded-xl border border-[#d4d9d3] bg-[#fdfaf2] shadow-xl">
|
<div
|
||||||
|
className="relative max-h-[92vh] w-full max-w-2xl overflow-y-auto rounded-t-2xl border border-[#d4d9d3]
|
||||||
|
bg-[#fdfaf2] shadow-[0_-12px_40px_-12px_rgba(15,23,18,0.2),0_24px_60px_-20px_rgba(15,23,18,0.25)]
|
||||||
|
smartsheet-sheet-up
|
||||||
|
sm:rounded-2xl"
|
||||||
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="sticky top-0 z-10 flex items-start justify-between gap-4 border-b border-[#d4d9d3] bg-[#fdfaf2] px-6 py-4">
|
<header className="sticky top-0 z-10 flex items-start justify-between gap-4 border-b border-[#d4d9d3]/80 bg-[#fdfaf2]/95 px-6 py-4 backdrop-blur-sm">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] font-mono uppercase tracking-[0.3em] text-[#8a6b3b]">
|
<p className="text-[10px] font-mono uppercase tracking-[0.3em] text-[#8a6b3b]">
|
||||||
§ 05.1 — Setup
|
§ 05.1 — Setup
|
||||||
</p>
|
</p>
|
||||||
<h2
|
<h2
|
||||||
id="smartsheet-setup-title"
|
id="smartsheet-setup-title"
|
||||||
className="mt-1 text-xl font-medium text-[#1a4d2e]"
|
className="mt-1 text-[22px] font-medium leading-tight tracking-tight text-[#1a4d2e]"
|
||||||
style={{ fontFamily: "var(--font-display, Georgia, serif)" }}
|
style={{ fontFamily: "var(--font-display, Georgia, serif)" }}
|
||||||
>
|
>
|
||||||
Connect a Smartsheet
|
Connect a Smartsheet
|
||||||
@@ -1026,13 +1209,19 @@ function SetupInstructionsModal({ onClose }: { onClose: () => void }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
className="-mr-1 -mt-1 rounded-md p-2 text-[#5a5d5a] hover:bg-[#f4f1e8] hover:text-[#1d1d1f]"
|
className="-mr-2 -mt-1 inline-flex h-9 w-9 items-center justify-center rounded-full
|
||||||
|
text-[#5a5d5a]
|
||||||
|
motion-safe:transition-colors motion-safe:duration-150
|
||||||
|
hover:bg-[#f0ebde] hover:text-[#1d1d1f]
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25
|
||||||
|
active:scale-95 motion-safe:transition-transform motion-safe:ease-out"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
className="h-5 w-5"
|
className="h-5 w-5"
|
||||||
|
aria-hidden
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
@@ -1246,11 +1435,17 @@ function SetupInstructionsModal({ onClose }: { onClose: () => void }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="sticky bottom-0 flex justify-end border-t border-[#d4d9d3] bg-[#fdfaf2] px-6 py-3">
|
<footer className="sticky bottom-0 flex justify-end gap-2 border-t border-[#d4d9d3]/80 bg-[#fdfaf2]/95 px-6 py-3 backdrop-blur-sm">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded-lg bg-[#1a4d2e] px-5 py-2 text-sm font-semibold text-white transition hover:bg-[#143d24]"
|
className="inline-flex min-h-[40px] items-center justify-center rounded-lg bg-[#1a4d2e] px-5
|
||||||
|
text-[14px] font-semibold tracking-tight text-white
|
||||||
|
shadow-[0_1px_0_rgba(0,0,0,0.06),inset_0_1px_0_rgba(255,255,255,0.06)]
|
||||||
|
motion-safe:transition-[background-color,transform] motion-safe:duration-150 motion-safe:ease-out
|
||||||
|
hover:bg-[#143d24]
|
||||||
|
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/30
|
||||||
|
active:scale-[0.97] active:bg-[#0f2e1c]"
|
||||||
>
|
>
|
||||||
Got it
|
Got it
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user