Server Deploy / deploy (push) Successful in 53s
The console had components but no shell: a brand bar and a nav strip stacked into 100px carrying eight words, no sign-out, no account identity, and an Overview link hardcoded to text-accent so it read as the current page on every screen. Nine pages each hand-rolled their own header. AppBar replaces both bars and derives its active state from usePathname. Settings moves into AccountMenu — it is your password, not a destination — taking appearance with it, which finally sets the data-theme attribute the token blocks have supported in both directions since they were written. That leaves three customer destinations: Overview, People, Billing. PageFrame adds a support rail so a page has a floor, and InstanceRecord replaces InstanceCard with one component that opens and closes: an account with a single instance used to render a third of a row of summary with its substance a click away. It defaults open when the instance is the only one or needs attention. No plan card in the rail: tier, limits and expiry belong to a licence and a licence belongs to one instance, so an account holding a Free cloud instance and a Professional self-hosted one has no single plan. The rail carries only what is account-wide. Tokens and globals.css are untouched — they stay verbatim shared with site/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import clsx from "clsx";
|
|
import Link from "next/link";
|
|
|
|
type Variant = "solid" | "line";
|
|
|
|
/*
|
|
* Matches site/'s .btn--solid and .btn--line exactly, including the neutral
|
|
* border on the secondary variant. site/ does not have an accent-outlined
|
|
* button and this app should not invent one.
|
|
*/
|
|
export function buttonClass(variant: Variant = "solid", disabled = false, className?: string) {
|
|
return clsx(
|
|
"inline-flex items-center gap-2 rounded border px-4 py-2.5 text-[0.94rem] font-semibold",
|
|
"transition-[filter,border-color] duration-150 hover:brightness-110",
|
|
variant === "solid"
|
|
? "border-accent bg-accent text-accent-ink"
|
|
: "border-rule bg-panel text-ink hover:border-ink-3",
|
|
disabled && "cursor-not-allowed border-rule bg-panel text-ink-3 hover:brightness-100",
|
|
className,
|
|
);
|
|
}
|
|
|
|
type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: Variant };
|
|
|
|
export function Button({ variant = "solid", className, ...rest }: Props) {
|
|
return <button {...rest} className={buttonClass(variant, rest.disabled, className)} />;
|
|
}
|
|
|
|
/*
|
|
* A link that looks like a button. It exists so a navigation action never has to
|
|
* be an <a> wrapped around a <button> — invalid markup, and it gives screen
|
|
* readers two nested controls where the page means one.
|
|
*/
|
|
export function LinkButton({
|
|
href,
|
|
variant = "solid",
|
|
external,
|
|
className,
|
|
children,
|
|
}: {
|
|
href: string;
|
|
variant?: Variant;
|
|
external?: boolean;
|
|
className?: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
const cls = buttonClass(variant, false, className);
|
|
return external ? (
|
|
<a href={href} className={cls}>
|
|
{children}
|
|
</a>
|
|
) : (
|
|
<Link href={href} className={cls}>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|