Fix interface fidelity: invalid Tailwind classes + restore lost HomeScreen
Side-by-side comparison surfaced that several restyle classes silently generated NO CSS (confirmed against the compiled .next CSS) — invisible to tests because unit tests assert class strings, not computed styles, and visual baselines were captured from the broken build. Root causes fixed: - rounded-radius-button (51) / rounded-radius-squircle (3) generated nothing → every pill/button/chip was border-radius:0. Tailwind v4 maps --radius-button to 'rounded-button', not 'rounded-radius-button'. Replaced with rounded-full / rounded-[17px]. - duration-normal/fast/slow (58) generated nothing → transitions had easing but 0 duration (snapped). Replaced with the token-arbitrary form duration-(--duration-normal). (Also fixed an over-replace that double-wrapped var(--duration-normal) inside animate-[…] values.) - HomeScreen.tsx was the OLD emoji version — the R3 rewrite was never committed. Restored: brand Chrome/Slack squircle tiles, coral GradientGlyph icons, 56px tiles with press-scale, and the frosted glass dock (bg-white/[0.07] backdrop-blur-[40px] saturate-150). Targeted gaps from the feedback: - raised surface --color-surface-warm-muted #1f1f1f → #1a1a1a (composer, chips, tabs) - welcome chips: max-w-[422px] gap-3 → centered 2×2 grid - composer input + rail nav labels → 14px - DeviceSizeToggle: Full/Tablet/Phone text → lucide monitor/tablet/ smartphone icons (aria-label keeps the radio names) - SW cache bumped v1→v2 so redeployed clients purge the stale bundle - LeftRail drawer: ref-during-render → prev-state pattern (lint) Compiled CSS now emits .rounded-full, transition-duration:var(--duration- normal), border-radius:17px, and shadow-dock-capsule. Button/Avatar unit tests updated to the real class names. 83 unit tests green. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
889c65685e
commit
0383d8fd77
@@ -3,8 +3,8 @@
|
|||||||
// /api/** -> NEVER touched (approvals + SSE must be live)
|
// /api/** -> NEVER touched (approvals + SSE must be live)
|
||||||
// /_next/static/** -> cache-first (content-hashed, immutable)
|
// /_next/static/** -> cache-first (content-hashed, immutable)
|
||||||
// navigations -> network-first, cache fallback for offline shell
|
// navigations -> network-first, cache fallback for offline shell
|
||||||
const STATIC_CACHE = "tc-static-v1";
|
const STATIC_CACHE = "tc-static-v2";
|
||||||
const PAGE_CACHE = "tc-pages-v1";
|
const PAGE_CACHE = "tc-pages-v2";
|
||||||
|
|
||||||
self.addEventListener("install", () => {
|
self.addEventListener("install", () => {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default async function CreditsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="mailto:[email protected]"
|
href="mailto:[email protected]"
|
||||||
className="rounded-radius-button border border-border px-4 py-2 text-sm text-foreground transition-colors duration-normal ease-app hover:bg-hover-bg"
|
className="rounded-full border border-border px-4 py-2 text-sm text-foreground transition-colors duration-(--duration-normal) ease-app hover:bg-hover-bg"
|
||||||
>
|
>
|
||||||
Talk to sales
|
Talk to sales
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
--color-card: #0a0a0a;
|
--color-card: #0a0a0a;
|
||||||
--color-popover: #0a0a0a;
|
--color-popover: #0a0a0a;
|
||||||
--color-surface-warm: #141414; /* raised tier 1 */
|
--color-surface-warm: #141414; /* raised tier 1 */
|
||||||
--color-surface-warm-muted: #1f1f1f; /* raised tier 2 */
|
--color-surface-warm-muted: #1a1a1a; /* raised tier 2 */
|
||||||
--color-subtle: #141414;
|
--color-subtle: #141414;
|
||||||
--color-hover-bg: #171717; /* generic hover fill */
|
--color-hover-bg: #171717; /* generic hover fill */
|
||||||
--color-divider-subtle: #1a1a1a;
|
--color-divider-subtle: #1a1a1a;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function ChatHeader({
|
|||||||
aria-label="Sessions"
|
aria-label="Sessions"
|
||||||
aria-pressed={sessions}
|
aria-pressed={sessions}
|
||||||
onClick={() => setParams({ sessions: !sessions })}
|
onClick={() => setParams({ sessions: !sessions })}
|
||||||
className={`inline-flex size-9 items-center justify-center rounded-radius-button transition-colors duration-normal ease-app ${
|
className={`inline-flex size-9 items-center justify-center rounded-full transition-colors duration-(--duration-normal) ease-app ${
|
||||||
sessions
|
sessions
|
||||||
? "bg-surface-warm-muted text-foreground"
|
? "bg-surface-warm-muted text-foreground"
|
||||||
: "text-muted-foreground hover:bg-hover-bg hover:text-foreground"
|
: "text-muted-foreground hover:bg-hover-bg hover:text-foreground"
|
||||||
@@ -52,7 +52,7 @@ export function ChatHeader({
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label="New"
|
aria-label="New"
|
||||||
onClick={onNewSession}
|
onClick={onNewSession}
|
||||||
className="inline-flex size-9 items-center justify-center rounded-radius-button text-muted-foreground transition-colors duration-normal ease-app hover:bg-hover-bg hover:text-foreground"
|
className="inline-flex size-9 items-center justify-center rounded-full text-muted-foreground transition-colors duration-(--duration-normal) ease-app hover:bg-hover-bg hover:text-foreground"
|
||||||
>
|
>
|
||||||
<SquarePen aria-hidden size={18} />
|
<SquarePen aria-hidden size={18} />
|
||||||
</button>
|
</button>
|
||||||
@@ -60,7 +60,7 @@ export function ChatHeader({
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label="Computer"
|
aria-label="Computer"
|
||||||
onClick={() => setParams({ app: "home" })}
|
onClick={() => setParams({ app: "home" })}
|
||||||
className="inline-flex size-[42px] items-center justify-center rounded-radius-button text-coral shadow-launcher transition-all duration-normal ease-app hover:bg-coral/10 active:scale-95"
|
className="inline-flex size-[42px] items-center justify-center rounded-full text-coral shadow-launcher transition-all duration-(--duration-normal) ease-app hover:bg-coral/10 active:scale-95"
|
||||||
>
|
>
|
||||||
<Monitor aria-hidden size={20} />
|
<Monitor aria-hidden size={20} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ export function Composer({
|
|||||||
rows={1}
|
rows={1}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
className="max-h-40 min-h-8 flex-1 resize-none bg-transparent py-1 text-base text-foreground outline-none placeholder:text-subtle-foreground"
|
className="max-h-40 min-h-8 flex-1 resize-none bg-transparent py-1 text-sm text-foreground outline-none placeholder:text-subtle-foreground"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={disabled || value.trim() === ""}
|
disabled={disabled || value.trim() === ""}
|
||||||
className="rounded-radius-button bg-cream px-4 py-2 text-sm font-medium text-primary-foreground shadow-cta transition-colors duration-normal ease-app hover:bg-white disabled:opacity-40 disabled:shadow-none"
|
className="rounded-full bg-cream px-4 py-2 text-sm font-medium text-primary-foreground shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-white disabled:opacity-40 disabled:shadow-none"
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ export function WelcomeState({
|
|||||||
<p className="text-base text-muted-foreground">
|
<p className="text-base text-muted-foreground">
|
||||||
{agent.job_title} · Shared with your team
|
{agent.job_title} · Shared with your team
|
||||||
</p>
|
</p>
|
||||||
<div className="flex max-w-xl flex-wrap justify-center gap-2 pt-2">
|
<div className="flex max-w-[422px] flex-wrap justify-center gap-3 pt-2">
|
||||||
{SUGGESTED_PROMPTS.map(({ label, icon: Icon }) => (
|
{SUGGESTED_PROMPTS.map(({ label, icon: Icon }) => (
|
||||||
<button
|
<button
|
||||||
key={label}
|
key={label}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onPick(label)}
|
onClick={() => onPick(label)}
|
||||||
className="inline-flex h-9 items-center gap-2 rounded-radius-button bg-surface-warm-muted px-3.5 text-xs font-medium text-neutral-200 shadow-bubble ring-1 ring-neutral-700/70 transition-colors duration-normal ease-app ring-inset hover:bg-neutral-700/60 hover:ring-neutral-600"
|
className="inline-flex h-9 items-center gap-2 rounded-full bg-surface-warm-muted px-3.5 text-xs font-medium text-neutral-200 shadow-bubble ring-1 ring-neutral-700/70 transition-colors duration-(--duration-normal) ease-app ring-inset hover:bg-neutral-700/60 hover:ring-neutral-600"
|
||||||
>
|
>
|
||||||
<Icon aria-hidden size={14} className="shrink-0 text-coral" />
|
<Icon aria-hidden size={14} className="shrink-0 text-coral" />
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function StepTrace({ steps }: { steps: UiStep[] }) {
|
|||||||
aria-expanded={expanded}
|
aria-expanded={expanded}
|
||||||
aria-controls={regionId}
|
aria-controls={regionId}
|
||||||
onClick={() => setExpanded((v) => !v)}
|
onClick={() => setExpanded((v) => !v)}
|
||||||
className="rounded-radius-button px-2 py-0.5 text-xs text-muted-foreground transition-colors duration-normal ease-app hover:bg-hover-bg hover:text-foreground"
|
className="rounded-full px-2 py-0.5 text-xs text-muted-foreground transition-colors duration-(--duration-normal) ease-app hover:bg-hover-bg hover:text-foreground"
|
||||||
>
|
>
|
||||||
{expanded ? "▾" : "▸"} {steps.length}{" "}
|
{expanded ? "▾" : "▸"} {steps.length}{" "}
|
||||||
{steps.length === 1 ? "step" : "steps"}
|
{steps.length === 1 ? "step" : "steps"}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Monitor, Smartphone, Tablet } from "lucide-react";
|
||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
import type { DeviceSize } from "@/lib/url/panel-params";
|
import type { DeviceSize } from "@/lib/url/panel-params";
|
||||||
|
|
||||||
const SIZES: { id: DeviceSize; label: string }[] = [
|
const SIZES: { id: DeviceSize; label: string; icon: LucideIcon }[] = [
|
||||||
{ id: "full", label: "Full" },
|
{ id: "full", label: "Full", icon: Monitor },
|
||||||
{ id: "tablet", label: "Tablet" },
|
{ id: "tablet", label: "Tablet", icon: Tablet },
|
||||||
{ id: "phone", label: "Phone" },
|
{ id: "phone", label: "Phone", icon: Smartphone },
|
||||||
];
|
];
|
||||||
|
|
||||||
/** The §7 size toggle: Full / Tablet / Phone. */
|
/** The §7 size toggle (measured): three icon buttons (monitor/tablet/
|
||||||
|
* smartphone); the accessible name stays the size label. */
|
||||||
export function DeviceSizeToggle({
|
export function DeviceSizeToggle({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -23,15 +27,16 @@ export function DeviceSizeToggle({
|
|||||||
key={size.id}
|
key={size.id}
|
||||||
type="button"
|
type="button"
|
||||||
role="radio"
|
role="radio"
|
||||||
|
aria-label={size.label}
|
||||||
aria-checked={value === size.id}
|
aria-checked={value === size.id}
|
||||||
onClick={() => onChange(size.id)}
|
onClick={() => onChange(size.id)}
|
||||||
className={`rounded-(--radius) px-2 py-0.5 text-xxs ${
|
className={`inline-flex size-6 items-center justify-center rounded-full transition-colors duration-(--duration-normal) ease-app ${
|
||||||
value === size.id
|
value === size.id
|
||||||
? "bg-surface-warm-muted text-foreground"
|
? "bg-white/15 text-white"
|
||||||
: "text-muted-foreground hover:text-foreground"
|
: "text-white/60 hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{size.label}
|
<size.icon aria-hidden size={13} />
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,74 +1,104 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import Image from "next/image";
|
||||||
|
import type { MouseEvent } from "react";
|
||||||
|
|
||||||
import type { AppId } from "@/lib/url/panel-params";
|
import type { AppId } from "@/lib/url/panel-params";
|
||||||
|
import { GradientGlyph } from "@/components/ui/GradientGlyph";
|
||||||
|
import { APP_ICON, HOME_DOCK, HOME_GRID } from "./appCatalog";
|
||||||
|
|
||||||
interface HomeScreenProps {
|
interface HomeScreenProps {
|
||||||
agentName: string;
|
agentName: string;
|
||||||
onOpen: (app: AppId) => void;
|
/** Receives the tapped tile's rect so the app window can zoom from it. */
|
||||||
|
onOpen: (app: AppId, origin: DOMRect) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GRID: { app: AppId; label: string; glyph: string }[] = [
|
/* Brand tiles use the official service marks on a white squircle; the
|
||||||
{ app: "browser", label: "Browser", glyph: "🌐" },
|
rest get the coral-gradient lucide glyph on a glass tile. 56px tile,
|
||||||
{ app: "slack", label: "Slack", glyph: "💬" },
|
16px-radius icon container, press scales to 0.92 (measured). */
|
||||||
{ app: "chat", label: "Claw Chat", glyph: "🦀" },
|
function homeIcon(app: AppId) {
|
||||||
{ app: "apps", label: "Add", glyph: "+" },
|
if (app === "browser") {
|
||||||
];
|
return (
|
||||||
|
<span className="flex size-14 items-center justify-center rounded-2xl bg-white shadow-dock-tile">
|
||||||
const DOCK: { app: AppId; label: string; glyph: string }[] = [
|
<Image src="/services/chrome.svg" alt="" width={32} height={32} />
|
||||||
{ app: "skills", label: "Skills", glyph: "⚡" },
|
</span>
|
||||||
{ app: "files", label: "Files", glyph: "📁" },
|
);
|
||||||
{ app: "routines", label: "Routines", glyph: "🔔" },
|
}
|
||||||
{ app: "settings", label: "Settings", glyph: "⚙" },
|
if (app === "slack") {
|
||||||
];
|
return (
|
||||||
|
<span className="flex size-14 items-center justify-center rounded-2xl bg-white shadow-dock-tile">
|
||||||
|
<Image src="/services/slack.svg" alt="" width={30} height={30} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (app === "apps") {
|
||||||
|
return (
|
||||||
|
<span className="flex size-14 items-center justify-center rounded-2xl border border-dashed border-white/40 text-white/65">
|
||||||
|
<GradientGlyph icon={APP_ICON.apps} size={26} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span className="flex size-14 items-center justify-center rounded-2xl bg-black/50 shadow-dock-tile backdrop-blur-sm">
|
||||||
|
<GradientGlyph icon={APP_ICON[app]} size={28} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Tile({
|
function Tile({
|
||||||
|
app,
|
||||||
label,
|
label,
|
||||||
glyph,
|
onOpen,
|
||||||
onClick,
|
|
||||||
}: {
|
}: {
|
||||||
|
app: AppId;
|
||||||
label: string;
|
label: string;
|
||||||
glyph: string;
|
onOpen: (app: AppId, origin: DOMRect) => void;
|
||||||
onClick: () => void;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
aria-label={label}
|
||||||
className="flex flex-col items-center gap-1 rounded-(--radius) px-3 py-2 text-xxs text-foreground/90 hover:bg-white/10"
|
onClick={(e: MouseEvent<HTMLButtonElement>) =>
|
||||||
|
onOpen(app, e.currentTarget.getBoundingClientRect())
|
||||||
|
}
|
||||||
|
className="flex flex-col items-center gap-1.5 transition-transform duration-[0.1s] ease-out-app active:scale-[0.92]"
|
||||||
>
|
>
|
||||||
<span aria-hidden className="text-xl">
|
{homeIcon(app)}
|
||||||
{glyph}
|
<span className="text-[11px] text-white/80">{label}</span>
|
||||||
</span>
|
|
||||||
{label}
|
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The §7.0 home screen: app grid over the wallpaper, glassy dock below. */
|
/** The measured home screen: app grid over the wallpaper, frosted dock. */
|
||||||
export function HomeScreen({ agentName, onOpen }: HomeScreenProps) {
|
export function HomeScreen({ agentName, onOpen }: HomeScreenProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-1 flex-col justify-between p-4">
|
<div className="relative flex flex-1 flex-col justify-between p-5">
|
||||||
<div className="grid grid-cols-4 gap-2" aria-label={`${agentName}'s apps`}>
|
<div className="grid grid-cols-4 gap-3" aria-label={`${agentName}'s apps`}>
|
||||||
{GRID.map((tile) => (
|
{HOME_GRID.map((tile) => (
|
||||||
<Tile
|
<Tile
|
||||||
key={tile.app}
|
key={tile.app}
|
||||||
|
app={tile.app}
|
||||||
label={tile.label}
|
label={tile.label}
|
||||||
glyph={tile.glyph}
|
onOpen={onOpen}
|
||||||
onClick={() => onOpen(tile.app)}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Dock"
|
aria-label="Dock"
|
||||||
className="mx-auto flex gap-2 rounded-(--radius) border border-white/10 bg-white/5 px-3 py-1.5 shadow-(--shadow-dock-tile) backdrop-blur"
|
className="mx-auto flex gap-5 rounded-3xl bg-white/[0.07] px-6 pt-4 pb-3 shadow-dock-capsule ring-1 ring-white/[0.04] ring-inset backdrop-blur-[40px] backdrop-saturate-150"
|
||||||
>
|
>
|
||||||
{DOCK.map((tile) => (
|
{HOME_DOCK.map((tile) => (
|
||||||
<Tile
|
<button
|
||||||
key={tile.app}
|
key={tile.app}
|
||||||
label={tile.label}
|
type="button"
|
||||||
glyph={tile.glyph}
|
aria-label={tile.label}
|
||||||
onClick={() => onOpen(tile.app)}
|
onClick={(e) =>
|
||||||
/>
|
onOpen(tile.app, e.currentTarget.getBoundingClientRect())
|
||||||
|
}
|
||||||
|
className="flex size-12 items-center justify-center rounded-2xl bg-black shadow-dock-tile transition-transform duration-[0.1s] ease-out-app active:scale-[0.92]"
|
||||||
|
>
|
||||||
|
<GradientGlyph icon={APP_ICON[tile.app]} size={22} />
|
||||||
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function AddAppsApp({ agent }: { agent: Agent }) {
|
|||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={category === entry}
|
aria-selected={category === entry}
|
||||||
onClick={() => setCategory(entry)}
|
onClick={() => setCategory(entry)}
|
||||||
className={`shrink-0 rounded-radius-button px-2.5 py-1 text-xs font-medium transition-colors duration-normal ease-app ${
|
className={`shrink-0 rounded-full px-2.5 py-1 text-xs font-medium transition-colors duration-(--duration-normal) ease-app ${
|
||||||
category === entry
|
category === entry
|
||||||
? "bg-white text-primary-foreground"
|
? "bg-white text-primary-foreground"
|
||||||
: "bg-white/10 text-white/80 hover:text-white"
|
: "bg-white/10 text-white/80 hover:text-white"
|
||||||
@@ -99,7 +99,7 @@ export default function AddAppsApp({ agent }: { agent: Agent }) {
|
|||||||
<ul aria-label="App directory">
|
<ul aria-label="App directory">
|
||||||
{apps.map((app) => (
|
{apps.map((app) => (
|
||||||
<li key={app.id} className="px-1 py-1">
|
<li key={app.id} className="px-1 py-1">
|
||||||
<div className="flex items-center gap-2 rounded-xl px-2 py-2 transition-colors duration-normal ease-app hover:bg-neutral-800/40">
|
<div className="flex items-center gap-2 rounded-xl px-2 py-2 transition-colors duration-(--duration-normal) ease-app hover:bg-neutral-800/40">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm">{app.name}</p>
|
<p className="text-sm">{app.name}</p>
|
||||||
<p className="truncate text-xs text-muted-foreground">
|
<p className="truncate text-xs text-muted-foreground">
|
||||||
@@ -107,7 +107,7 @@ export default function AddAppsApp({ agent }: { agent: Agent }) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{app.connected ? (
|
{app.connected ? (
|
||||||
<span className="rounded-radius-button border border-border px-2 py-0.5 text-xxs text-muted-foreground">
|
<span className="rounded-full border border-border px-2 py-0.5 text-xxs text-muted-foreground">
|
||||||
✓ Connected
|
✓ Connected
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -117,7 +117,7 @@ export default function AddAppsApp({ agent }: { agent: Agent }) {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
setConnecting(connecting === app.id ? null : app.id)
|
setConnecting(connecting === app.id ? null : app.id)
|
||||||
}
|
}
|
||||||
className="inline-flex size-7 shrink-0 items-center justify-center rounded-radius-button border border-border text-muted-foreground transition-colors duration-normal ease-app hover:border-coral hover:text-foreground"
|
className="inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground transition-colors duration-(--duration-normal) ease-app hover:border-coral hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Plus aria-hidden size={14} />
|
<Plus aria-hidden size={14} />
|
||||||
</button>
|
</button>
|
||||||
@@ -138,7 +138,7 @@ export default function AddAppsApp({ agent }: { agent: Agent }) {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="rounded-radius-button bg-coral px-3 py-1.5 text-xs font-medium text-white shadow-cta transition-colors duration-normal ease-app hover:bg-coral-light"
|
className="rounded-full bg-coral px-3 py-1.5 text-xs font-medium text-white shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-coral-light"
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function BrowserApp({ agent }: { agent: Agent }) {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label="Back"
|
aria-label="Back"
|
||||||
disabled
|
disabled
|
||||||
className="inline-flex size-7 items-center justify-center rounded-radius-button text-muted-foreground disabled:opacity-40"
|
className="inline-flex size-7 items-center justify-center rounded-full text-muted-foreground disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<ChevronLeft aria-hidden size={16} />
|
<ChevronLeft aria-hidden size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -54,7 +54,7 @@ export default function BrowserApp({ agent }: { agent: Agent }) {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label="Forward"
|
aria-label="Forward"
|
||||||
disabled
|
disabled
|
||||||
className="inline-flex size-7 items-center justify-center rounded-radius-button text-muted-foreground disabled:opacity-40"
|
className="inline-flex size-7 items-center justify-center rounded-full text-muted-foreground disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<ChevronRight aria-hidden size={16} />
|
<ChevronRight aria-hidden size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default function ClawChatApp({ agent }: { agent: Agent }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setOpenThread(thread)}
|
onClick={() => setOpenThread(thread)}
|
||||||
className="w-full rounded-xl px-2 py-2 text-left transition-colors duration-normal ease-app hover:bg-neutral-800/40"
|
className="w-full rounded-xl px-2 py-2 text-left transition-colors duration-(--duration-normal) ease-app hover:bg-neutral-800/40"
|
||||||
>
|
>
|
||||||
<p className="flex items-center gap-2 text-sm">
|
<p className="flex items-center gap-2 text-sm">
|
||||||
{thread.subject}
|
{thread.subject}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function FilesApp({ agent }: { agent: Agent }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setDrive(entry.id)}
|
onClick={() => setDrive(entry.id)}
|
||||||
className="flex h-[45px] w-full items-center gap-3 rounded-xl px-3 text-base transition-colors duration-normal ease-app hover:bg-neutral-800/40"
|
className="flex h-[45px] w-full items-center gap-3 rounded-xl px-3 text-base transition-colors duration-(--duration-normal) ease-app hover:bg-neutral-800/40"
|
||||||
>
|
>
|
||||||
<GradientGlyph icon={Folder} size={18} />
|
<GradientGlyph icon={Folder} size={18} />
|
||||||
{entry.label}
|
{entry.label}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function RoutinesApp({ agent }: { agent: Agent }) {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label="Refresh routines"
|
aria-label="Refresh routines"
|
||||||
onClick={refresh}
|
onClick={refresh}
|
||||||
className="inline-flex size-7 items-center justify-center rounded-radius-button text-muted-foreground transition-colors duration-normal ease-app hover:bg-hover-bg hover:text-foreground"
|
className="inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors duration-(--duration-normal) ease-app hover:bg-hover-bg hover:text-foreground"
|
||||||
>
|
>
|
||||||
<RefreshCw aria-hidden size={14} />
|
<RefreshCw aria-hidden size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default function SettingsApp({ agent }: { agent: Agent }) {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="rounded-radius-button bg-cream px-4 py-2 text-sm font-semibold text-primary-foreground shadow-button transition-colors duration-normal ease-app hover:bg-white disabled:opacity-50"
|
className="rounded-full bg-cream px-4 py-2 text-sm font-semibold text-primary-foreground shadow-button transition-colors duration-(--duration-normal) ease-app hover:bg-white disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saving ? "Saving…" : "Save profile"}
|
{saving ? "Saving…" : "Save profile"}
|
||||||
</button>
|
</button>
|
||||||
@@ -121,14 +121,14 @@ export default function SettingsApp({ agent }: { agent: Agent }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setConfirmingDelete(false)}
|
onClick={() => setConfirmingDelete(false)}
|
||||||
className="rounded-radius-button border border-border px-3 py-1.5 transition-colors hover:bg-hover-bg"
|
className="rounded-full border border-border px-3 py-1.5 transition-colors hover:bg-hover-bg"
|
||||||
>
|
>
|
||||||
Keep
|
Keep
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={deleteClaw}
|
onClick={deleteClaw}
|
||||||
className="rounded-radius-button bg-destructive px-3 py-1.5 text-foreground"
|
className="rounded-full bg-destructive px-3 py-1.5 text-foreground"
|
||||||
>
|
>
|
||||||
Delete claw
|
Delete claw
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default function SkillsApp({ agent }: { agent: Agent }) {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label={`Install ${skill.title}`}
|
aria-label={`Install ${skill.title}`}
|
||||||
onClick={() => install(skill.id)}
|
onClick={() => install(skill.id)}
|
||||||
className="shrink-0 rounded-radius-button border border-border px-2 text-sm text-muted-foreground transition-colors duration-normal ease-app hover:border-coral hover:text-foreground"
|
className="shrink-0 rounded-full border border-border px-2 text-sm text-muted-foreground transition-colors duration-(--duration-normal) ease-app hover:border-coral hover:text-foreground"
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
@@ -101,7 +101,7 @@ export default function SkillsApp({ agent }: { agent: Agent }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAdding(true)}
|
onClick={() => setAdding(true)}
|
||||||
className="mx-auto w-[92%] rounded-radius-button bg-cream py-3 text-sm font-semibold text-primary-foreground shadow-button transition-colors duration-normal ease-app hover:bg-white"
|
className="mx-auto w-[92%] rounded-full bg-cream py-3 text-sm font-semibold text-primary-foreground shadow-button transition-colors duration-(--duration-normal) ease-app hover:bg-white"
|
||||||
>
|
>
|
||||||
Add Skill
|
Add Skill
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function SlackApp({ agent }: { agent: Agent }) {
|
|||||||
<div
|
<div
|
||||||
role="tablist"
|
role="tablist"
|
||||||
aria-label="Slack"
|
aria-label="Slack"
|
||||||
className="mb-3 inline-flex gap-1 self-start rounded-radius-button bg-white/10 p-1"
|
className="mb-3 inline-flex gap-1 self-start rounded-full bg-white/10 p-1"
|
||||||
>
|
>
|
||||||
{TABS.map((entry) => (
|
{TABS.map((entry) => (
|
||||||
<button
|
<button
|
||||||
@@ -64,7 +64,7 @@ export default function SlackApp({ agent }: { agent: Agent }) {
|
|||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={tab === entry}
|
aria-selected={tab === entry}
|
||||||
onClick={() => setTab(entry)}
|
onClick={() => setTab(entry)}
|
||||||
className={`rounded-radius-button px-3 py-1 text-xs font-medium transition-colors duration-normal ease-app ${
|
className={`rounded-full px-3 py-1 text-xs font-medium transition-colors duration-(--duration-normal) ease-app ${
|
||||||
tab === entry
|
tab === entry
|
||||||
? "bg-white text-[#4a154b]"
|
? "bg-white text-[#4a154b]"
|
||||||
: "text-white/80 hover:text-white"
|
: "text-white/80 hover:text-white"
|
||||||
@@ -104,7 +104,7 @@ export default function SlackApp({ agent }: { agent: Agent }) {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="self-start rounded-radius-button bg-coral px-4 py-2 text-xs font-medium text-white shadow-cta transition-colors duration-normal ease-app hover:bg-coral-light disabled:opacity-50"
|
className="self-start rounded-full bg-coral px-4 py-2 text-xs font-medium text-white shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-coral-light disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saving ? "Connecting…" : "Connect Slack"}
|
{saving ? "Connecting…" : "Connect Slack"}
|
||||||
</button>
|
</button>
|
||||||
@@ -130,7 +130,7 @@ export default function SlackApp({ agent }: { agent: Agent }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTab("Connection")}
|
onClick={() => setTab("Connection")}
|
||||||
className="rounded-radius-button bg-cream px-5 py-2.5 text-sm font-semibold text-primary-foreground shadow-button transition-colors duration-normal ease-app hover:bg-white"
|
className="rounded-full bg-cream px-5 py-2.5 text-sm font-semibold text-primary-foreground shadow-button transition-colors duration-(--duration-normal) ease-app hover:bg-white"
|
||||||
>
|
>
|
||||||
Connect Slack
|
Connect Slack
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function AppsDirectory({ apps: initial }: { apps: DirectoryApp[] }) {
|
|||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={category === entry}
|
aria-selected={category === entry}
|
||||||
onClick={() => setCategory(entry)}
|
onClick={() => setCategory(entry)}
|
||||||
className={`rounded-radius-button px-3 py-1 text-xs font-medium transition-colors duration-normal ease-app ${
|
className={`rounded-full px-3 py-1 text-xs font-medium transition-colors duration-(--duration-normal) ease-app ${
|
||||||
category === entry
|
category === entry
|
||||||
? "bg-foreground text-background"
|
? "bg-foreground text-background"
|
||||||
: "bg-surface-warm text-muted-foreground hover:text-foreground"
|
: "bg-surface-warm text-muted-foreground hover:text-foreground"
|
||||||
@@ -100,7 +100,7 @@ export function AppsDirectory({ apps: initial }: { apps: DirectoryApp[] }) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{app.connected ? (
|
{app.connected ? (
|
||||||
<span className="rounded-radius-button border border-border px-2 py-0.5 text-xxs text-muted-foreground">
|
<span className="rounded-full border border-border px-2 py-0.5 text-xxs text-muted-foreground">
|
||||||
✓ Connected
|
✓ Connected
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -110,7 +110,7 @@ export function AppsDirectory({ apps: initial }: { apps: DirectoryApp[] }) {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
setConnecting(connecting === app.id ? null : app.id)
|
setConnecting(connecting === app.id ? null : app.id)
|
||||||
}
|
}
|
||||||
className="inline-flex size-8 shrink-0 items-center justify-center rounded-radius-button border border-border text-muted-foreground transition-colors duration-normal ease-app hover:border-coral hover:text-foreground"
|
className="inline-flex size-8 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground transition-colors duration-(--duration-normal) ease-app hover:border-coral hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Plus aria-hidden size={15} />
|
<Plus aria-hidden size={15} />
|
||||||
</button>
|
</button>
|
||||||
@@ -128,7 +128,7 @@ export function AppsDirectory({ apps: initial }: { apps: DirectoryApp[] }) {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="rounded-radius-button bg-coral px-3 py-1.5 text-xs font-medium text-white shadow-cta transition-colors duration-normal ease-app hover:bg-coral-light"
|
className="rounded-full bg-coral px-3 py-1.5 text-xs font-medium text-white shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-coral-light"
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ function MembersTable({ members }: { members: User[] }) {
|
|||||||
<Avatar name={member.display_name} size="sm" />
|
<Avatar name={member.display_name} size="sm" />
|
||||||
{member.display_name}
|
{member.display_name}
|
||||||
{member.role === "owner" && (
|
{member.role === "owner" && (
|
||||||
<span className="rounded-radius-button bg-surface-warm-muted px-2 py-0.5 text-xxs text-muted-foreground">
|
<span className="rounded-full bg-surface-warm-muted px-2 py-0.5 text-xxs text-muted-foreground">
|
||||||
Owner
|
Owner
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -98,7 +98,7 @@ function OrgChart({ nodes }: { nodes: OrgChartNode[] }) {
|
|||||||
<Link
|
<Link
|
||||||
key={claw.id}
|
key={claw.id}
|
||||||
href={`/claws/${claw.id}`}
|
href={`/claws/${claw.id}`}
|
||||||
className="flex items-center gap-2 rounded-radius-button bg-surface-warm px-3 py-1.5 text-xs transition-colors duration-normal ease-app hover:bg-surface-warm-muted"
|
className="flex items-center gap-2 rounded-full bg-surface-warm px-3 py-1.5 text-xs transition-colors duration-(--duration-normal) ease-app hover:bg-surface-warm-muted"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
name={claw.name}
|
name={claw.name}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function Faq() {
|
|||||||
<ChevronDown
|
<ChevronDown
|
||||||
aria-hidden
|
aria-hidden
|
||||||
size={18}
|
size={18}
|
||||||
className={`shrink-0 text-marketing-slate transition-transform duration-normal ease-app ${
|
className={`shrink-0 text-marketing-slate transition-transform duration-(--duration-normal) ease-app ${
|
||||||
open === i ? "rotate-180" : ""
|
open === i ? "rotate-180" : ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Link from "next/link";
|
|||||||
* auth pills right. Our brand, our links. */
|
* auth pills right. Our brand, our links. */
|
||||||
export function MarketingNav() {
|
export function MarketingNav() {
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-4 z-50 mx-auto flex w-[min(960px,92%)] items-center justify-between rounded-radius-button bg-white px-5 py-2.5 shadow-[0_8px_30px_-12px_rgba(22,29,39,0.18)] ring-1 ring-marketing-border">
|
<header className="sticky top-4 z-50 mx-auto flex w-[min(960px,92%)] items-center justify-between rounded-full bg-white px-5 py-2.5 shadow-[0_8px_30px_-12px_rgba(22,29,39,0.18)] ring-1 ring-marketing-border">
|
||||||
<Link href="/" className="font-mono text-lg font-semibold tracking-tight">
|
<Link href="/" className="font-mono text-lg font-semibold tracking-tight">
|
||||||
clawmates
|
clawmates
|
||||||
</Link>
|
</Link>
|
||||||
@@ -22,13 +22,13 @@ export function MarketingNav() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="rounded-radius-button px-4 py-2 text-sm font-medium hover:bg-[#f1f3f7]"
|
className="rounded-full px-4 py-2 text-sm font-medium hover:bg-[#f1f3f7]"
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="rounded-radius-button bg-marketing-slate-dark px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-[#0f172a]"
|
className="rounded-full bg-marketing-slate-dark px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-[#0f172a]"
|
||||||
>
|
>
|
||||||
Get started
|
Get started
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ export function Hero() {
|
|||||||
<div className="flex flex-wrap justify-center gap-3 pt-8">
|
<div className="flex flex-wrap justify-center gap-3 pt-8">
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="flex h-14 items-center rounded-radius-button bg-marketing-coral px-10 text-xl font-bold text-white transition-colors hover:bg-[#d84a4a]"
|
className="flex h-14 items-center rounded-full bg-marketing-coral px-10 text-xl font-bold text-white transition-colors hover:bg-[#d84a4a]"
|
||||||
>
|
>
|
||||||
Get started for free
|
Get started for free
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
href="mailto:[email protected]"
|
href="mailto:[email protected]"
|
||||||
className="flex h-14 items-center rounded-radius-button border border-marketing-border bg-white px-10 text-base font-semibold text-[#0f172a] transition-colors hover:bg-[#f1f3f7]"
|
className="flex h-14 items-center rounded-full border border-marketing-border bg-white px-10 text-base font-semibold text-[#0f172a] transition-colors hover:bg-[#f1f3f7]"
|
||||||
>
|
>
|
||||||
Book a demo
|
Book a demo
|
||||||
</a>
|
</a>
|
||||||
@@ -151,7 +151,7 @@ export function CtaBand() {
|
|||||||
</h2>
|
</h2>
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="flex h-14 items-center rounded-radius-button bg-marketing-coral px-10 text-xl font-bold text-white transition-colors hover:bg-[#d84a4a]"
|
className="flex h-14 items-center rounded-full bg-marketing-coral px-10 text-xl font-bold text-white transition-colors hover:bg-[#d84a4a]"
|
||||||
>
|
>
|
||||||
Get started for free
|
Get started for free
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function AgentRosterItem({
|
|||||||
className="group flex w-full flex-col items-center gap-1 py-1.5"
|
className="group flex w-full flex-col items-center gap-1 py-1.5"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`rounded-[19px] p-0.5 transition-colors duration-normal ease-app ${
|
className={`rounded-[19px] p-0.5 transition-colors duration-(--duration-normal) ease-app ${
|
||||||
active ? "ring-2 ring-coral" : ""
|
active ? "ring-2 ring-coral" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -40,7 +40,7 @@ export function AgentRosterItem({
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`max-w-full truncate text-xs transition-colors duration-normal ease-app ${
|
className={`max-w-full truncate text-xs transition-colors duration-(--duration-normal) ease-app ${
|
||||||
active
|
active
|
||||||
? "text-foreground"
|
? "text-foreground"
|
||||||
: "text-muted-foreground group-hover:text-foreground"
|
: "text-muted-foreground group-hover:text-foreground"
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function GlobalNavItem({
|
|||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
aria-current={active ? "page" : undefined}
|
aria-current={active ? "page" : undefined}
|
||||||
className={`flex h-8 items-center gap-3 rounded-radius-button px-3 text-base transition-colors duration-normal ease-app ${
|
className={`flex h-8 items-center gap-3 rounded-full px-3 text-sm transition-colors duration-(--duration-normal) ease-app ${
|
||||||
active
|
active
|
||||||
? "text-foreground"
|
? "text-foreground"
|
||||||
: "text-muted-foreground hover:bg-hover-bg hover:text-foreground"
|
: "text-muted-foreground hover:bg-hover-bg hover:text-foreground"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { Menu, Plus, X } from "lucide-react";
|
import { Menu, Plus, X } from "lucide-react";
|
||||||
import { useRef, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import type { Agent, User } from "@/lib/api/schemas";
|
import type { Agent, User } from "@/lib/api/schemas";
|
||||||
import { RosterList } from "./RosterList";
|
import { RosterList } from "./RosterList";
|
||||||
@@ -22,10 +22,11 @@ interface LeftRailProps {
|
|||||||
export function LeftRail({ user, roster, creditsBalance }: LeftRailProps) {
|
export function LeftRail({ user, roster, creditsBalance }: LeftRailProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
// Close the drawer when the route changes (render-phase, no effect).
|
// Close the drawer when the route changes (render-phase prev-state
|
||||||
const lastPath = useRef(pathname);
|
// pattern — no effect, no ref).
|
||||||
if (lastPath.current !== pathname) {
|
const [seenPath, setSeenPath] = useState(pathname);
|
||||||
lastPath.current = pathname;
|
if (seenPath !== pathname) {
|
||||||
|
setSeenPath(pathname);
|
||||||
if (open) setOpen(false);
|
if (open) setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ export function LeftRail({ user, roster, creditsBalance }: LeftRailProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label="Open navigation"
|
aria-label="Open navigation"
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
className="fixed top-4 left-3 z-30 hidden size-9 items-center justify-center rounded-radius-button bg-surface-warm text-foreground shadow-card max-md:inline-flex"
|
className="fixed top-4 left-3 z-30 hidden size-9 items-center justify-center rounded-full bg-surface-warm text-foreground shadow-card max-md:inline-flex"
|
||||||
>
|
>
|
||||||
<Menu aria-hidden size={18} />
|
<Menu aria-hidden size={18} />
|
||||||
</button>
|
</button>
|
||||||
@@ -48,7 +49,7 @@ export function LeftRail({ user, roster, creditsBalance }: LeftRailProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<aside
|
<aside
|
||||||
className={`z-50 flex h-dvh w-rail shrink-0 flex-col px-1 py-4 transition-transform duration-normal ease-app max-md:fixed max-md:bg-background max-md:shadow-edge ${
|
className={`z-50 flex h-dvh w-rail shrink-0 flex-col px-1 py-4 transition-transform duration-(--duration-normal) ease-app max-md:fixed max-md:bg-background max-md:shadow-edge ${
|
||||||
open ? "max-md:translate-x-0" : "max-md:-translate-x-full"
|
open ? "max-md:translate-x-0" : "max-md:-translate-x-full"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -77,11 +78,11 @@ export function LeftRail({ user, roster, creditsBalance }: LeftRailProps) {
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className="inline-flex size-12 items-center justify-center rounded-radius-squircle border border-dashed border-border text-coral transition-colors duration-normal ease-app group-hover:border-coral"
|
className="inline-flex size-12 items-center justify-center rounded-[17px] border border-dashed border-border text-coral transition-colors duration-(--duration-normal) ease-app group-hover:border-coral"
|
||||||
>
|
>
|
||||||
<Plus size={20} />
|
<Plus size={20} />
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-muted-foreground transition-colors duration-normal ease-app group-hover:text-foreground">
|
<span className="text-xs text-muted-foreground transition-colors duration-(--duration-normal) ease-app group-hover:text-foreground">
|
||||||
New claw
|
New claw
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe("Avatar", () => {
|
|||||||
<Avatar name="Scout" shape="squircle" size="rail" />,
|
<Avatar name="Scout" shape="squircle" size="rail" />,
|
||||||
);
|
);
|
||||||
const span = container.querySelector("span[aria-hidden]")!;
|
const span = container.querySelector("span[aria-hidden]")!;
|
||||||
expect(span.className).toContain("rounded-radius-squircle");
|
expect(span.className).toContain("rounded-[17px]");
|
||||||
expect(span.className).not.toContain("rounded-full");
|
expect(span.className).not.toContain("rounded-full");
|
||||||
expect(span.className).toContain("size-12"); /* 48px rail size */
|
expect(span.className).toContain("size-12"); /* 48px rail size */
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function Avatar({
|
|||||||
.map((part) => part[0]!.toUpperCase())
|
.map((part) => part[0]!.toUpperCase())
|
||||||
.join("");
|
.join("");
|
||||||
const radius =
|
const radius =
|
||||||
shape === "squircle" ? "rounded-radius-squircle" : "rounded-full";
|
shape === "squircle" ? "rounded-[17px]" : "rounded-full";
|
||||||
return (
|
return (
|
||||||
<span className="relative inline-flex">
|
<span className="relative inline-flex">
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ describe("Button", () => {
|
|||||||
it("cream variant: pill with cream fill and dark text (the action primary)", () => {
|
it("cream variant: pill with cream fill and dark text (the action primary)", () => {
|
||||||
render(<Button variant="cream">Send</Button>);
|
render(<Button variant="cream">Send</Button>);
|
||||||
const button = screen.getByRole("button", { name: "Send" });
|
const button = screen.getByRole("button", { name: "Send" });
|
||||||
expect(button.className).toContain("rounded-radius-button");
|
expect(button.className).toContain("rounded-full");
|
||||||
expect(button.className).toContain("bg-cream");
|
expect(button.className).toContain("bg-cream");
|
||||||
expect(button.className).toContain("text-primary-foreground");
|
expect(button.className).toContain("text-primary-foreground");
|
||||||
});
|
});
|
||||||
@@ -35,7 +35,7 @@ describe("Button", () => {
|
|||||||
);
|
);
|
||||||
const button = screen.getByRole("button", { name: "New session" });
|
const button = screen.getByRole("button", { name: "New session" });
|
||||||
expect(button.className).toContain("size-9");
|
expect(button.className).toContain("size-9");
|
||||||
expect(button.className).toContain("rounded-radius-button");
|
expect(button.className).toContain("rounded-full");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("disabled drops to half opacity and blocks clicks", async () => {
|
it("disabled drops to half opacity and blocks clicks", async () => {
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|||||||
Computer opener. All pills; all on the 0.25s ease-app transition. */
|
Computer opener. All pills; all on the 0.25s ease-app transition. */
|
||||||
const VARIANTS: Record<Variant, string> = {
|
const VARIANTS: Record<Variant, string> = {
|
||||||
cream:
|
cream:
|
||||||
"rounded-radius-button bg-cream px-4 py-2 text-sm font-semibold text-primary-foreground shadow-button transition-colors duration-normal ease-app hover:bg-white active:translate-y-px disabled:opacity-50 disabled:shadow-none",
|
"rounded-full bg-cream px-4 py-2 text-sm font-semibold text-primary-foreground shadow-button transition-colors duration-(--duration-normal) ease-app hover:bg-white active:translate-y-px disabled:opacity-50 disabled:shadow-none",
|
||||||
coral:
|
coral:
|
||||||
"rounded-radius-button bg-coral px-4 py-2 text-sm font-medium text-white shadow-cta transition-colors duration-normal ease-app hover:bg-coral-light active:bg-coral-dark disabled:opacity-50",
|
"rounded-full bg-coral px-4 py-2 text-sm font-medium text-white shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-coral-light active:bg-coral-dark disabled:opacity-50",
|
||||||
ghost:
|
ghost:
|
||||||
"rounded-radius-button px-3 py-1.5 text-sm text-muted-foreground transition-colors duration-normal ease-app hover:bg-hover-bg hover:text-foreground disabled:opacity-50",
|
"rounded-full px-3 py-1.5 text-sm text-muted-foreground transition-colors duration-(--duration-normal) ease-app hover:bg-hover-bg hover:text-foreground disabled:opacity-50",
|
||||||
icon: "inline-flex size-9 items-center justify-center rounded-radius-button text-muted-foreground transition-colors duration-normal ease-app hover:bg-hover-bg hover:text-foreground disabled:opacity-50",
|
icon: "inline-flex size-9 items-center justify-center rounded-full text-muted-foreground transition-colors duration-(--duration-normal) ease-app hover:bg-hover-bg hover:text-foreground disabled:opacity-50",
|
||||||
launcher:
|
launcher:
|
||||||
"inline-flex size-[42px] items-center justify-center rounded-radius-button text-coral shadow-launcher transition-all duration-normal ease-app hover:bg-coral/10 active:scale-95 disabled:opacity-50",
|
"inline-flex size-[42px] items-center justify-center rounded-full text-coral shadow-launcher transition-all duration-(--duration-normal) ease-app hover:bg-coral/10 active:scale-95 disabled:opacity-50",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Button({ variant, className, ...rest }: ButtonProps) {
|
export function Button({ variant, className, ...rest }: ButtonProps) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function Card({
|
|||||||
<div
|
<div
|
||||||
className={`rounded-3xl border border-border bg-card p-6 shadow-card${
|
className={`rounded-3xl border border-border bg-card p-6 shadow-card${
|
||||||
interactive
|
interactive
|
||||||
? " transition-colors duration-normal ease-app hover:border-neutral-700"
|
? " transition-colors duration-(--duration-normal) ease-app hover:border-neutral-700"
|
||||||
: ""
|
: ""
|
||||||
}${className ? ` ${className}` : ""}`}
|
}${className ? ` ${className}` : ""}`}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function SearchPill({
|
|||||||
}: InputHTMLAttributes<HTMLInputElement>) {
|
}: InputHTMLAttributes<HTMLInputElement>) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`flex items-center gap-2 rounded-radius-button bg-surface-warm-muted px-3 py-1.5${className ? ` ${className}` : ""}`}
|
className={`flex items-center gap-2 rounded-full bg-surface-warm-muted px-3 py-1.5${className ? ` ${className}` : ""}`}
|
||||||
>
|
>
|
||||||
<Search aria-hidden size={14} className="shrink-0 text-subtle-foreground" />
|
<Search aria-hidden size={14} className="shrink-0 text-subtle-foreground" />
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function SegmentedTabs({
|
|||||||
<div
|
<div
|
||||||
role="tablist"
|
role="tablist"
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
className="inline-flex gap-1 rounded-radius-button bg-subtle p-1"
|
className="inline-flex gap-1 rounded-full bg-subtle p-1"
|
||||||
>
|
>
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<button
|
<button
|
||||||
@@ -28,7 +28,7 @@ export function SegmentedTabs({
|
|||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={tab === active}
|
aria-selected={tab === active}
|
||||||
onClick={() => onChange(tab)}
|
onClick={() => onChange(tab)}
|
||||||
className={`rounded-radius-button px-3 py-1 text-xs font-medium transition-colors duration-normal ease-app ${
|
className={`rounded-full px-3 py-1 text-xs font-medium transition-colors duration-(--duration-normal) ease-app ${
|
||||||
tab === active
|
tab === active
|
||||||
? "bg-surface-warm-muted text-foreground"
|
? "bg-surface-warm-muted text-foreground"
|
||||||
: "text-muted-foreground hover:text-foreground"
|
: "text-muted-foreground hover:text-foreground"
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ export function CreateClawForm() {
|
|||||||
type="button"
|
type="button"
|
||||||
disabled={!draft.name || !draft.jobTitle}
|
disabled={!draft.name || !draft.jobTitle}
|
||||||
onClick={() => setParams({ step: "access" })}
|
onClick={() => setParams({ step: "access" })}
|
||||||
className="rounded-radius-button bg-coral px-4 py-2 text-sm font-medium text-white shadow-cta transition-colors duration-normal ease-app hover:bg-coral-light disabled:opacity-40"
|
className="rounded-full bg-coral px-4 py-2 text-sm font-medium text-white shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-coral-light disabled:opacity-40"
|
||||||
>
|
>
|
||||||
Continue →
|
Continue →
|
||||||
</button>
|
</button>
|
||||||
@@ -237,7 +237,7 @@ export function CreateClawForm() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setParams({ step: "slack" })}
|
onClick={() => setParams({ step: "slack" })}
|
||||||
className="rounded-radius-button bg-coral px-4 py-2 text-sm font-medium text-white shadow-cta transition-colors duration-normal ease-app hover:bg-coral-light"
|
className="rounded-full bg-coral px-4 py-2 text-sm font-medium text-white shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-coral-light"
|
||||||
>
|
>
|
||||||
Continue →
|
Continue →
|
||||||
</button>
|
</button>
|
||||||
@@ -263,7 +263,7 @@ export function CreateClawForm() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setConfirming(true)}
|
onClick={() => setConfirming(true)}
|
||||||
className="rounded-radius-button bg-coral px-4 py-2 text-sm font-medium text-white shadow-cta transition-colors duration-normal ease-app hover:bg-coral-light"
|
className="rounded-full bg-coral px-4 py-2 text-sm font-medium text-white shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-coral-light"
|
||||||
>
|
>
|
||||||
Review & create
|
Review & create
|
||||||
</button>
|
</button>
|
||||||
@@ -292,7 +292,7 @@ export function CreateClawForm() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={create}
|
onClick={create}
|
||||||
className="rounded-radius-button bg-coral px-4 py-1.5 text-xs font-medium text-white shadow-cta transition-colors duration-normal ease-app hover:bg-coral-light"
|
className="rounded-full bg-coral px-4 py-1.5 text-xs font-medium text-white shadow-cta transition-colors duration-(--duration-normal) ease-app hover:bg-coral-light"
|
||||||
>
|
>
|
||||||
Create claw
|
Create claw
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user