diff --git a/src/components/brand.test.tsx b/src/components/brand.test.tsx index 4baf894..27ade69 100644 --- a/src/components/brand.test.tsx +++ b/src/components/brand.test.tsx @@ -72,6 +72,30 @@ describe("brand components", () => { await expectNoAxeViolations(container); }); + it("overrides only the wordmark text with label, keeping the theme symbol", async () => { + const { container } = render( + + + , + ); + // The product name renders in place of the default "Mind" wordmark... + expect(screen.getByText("Drive")).toBeInTheDocument(); + expect(screen.queryByText("Mind")).not.toBeInTheDocument(); + // ...in the same serif lockup, and the shared mark is still painted. + expect(screen.getByText("Drive")).toHaveClass("font-serif"); + expect(container.querySelector("img")).toBeInTheDocument(); + await expectNoAxeViolations(container); + }); + + it("defaults the wordmark to the theme label when no label is given", () => { + render( + + + , + ); + expect(screen.getByText("Mind")).toBeInTheDocument(); + }); + it("drops the wordmark with symbolOnly", () => { render( diff --git a/src/components/brand.tsx b/src/components/brand.tsx index 67626a5..f3d152a 100644 --- a/src/components/brand.tsx +++ b/src/components/brand.tsx @@ -22,6 +22,13 @@ export type SymbolProps = Omit, "src" export type LogoProps = React.HTMLAttributes & { /** Render only the symbol, dropping the wordmark text. */ symbolOnly?: boolean; + /** + * Wordmark text. Defaults to the active theme's `label` ("Mind"). Pass the + * product name (e.g. `"Drive"`) so an app's lockup reads as the shared mark + + * its own name, set in the same brand serif. Overrides only the text โ€” the + * symbol/mark still comes from the active theme. + */ + label?: string; }; /** @@ -32,15 +39,16 @@ export type LogoProps = React.HTMLAttributes & { * fully bespoke wordmark image via its `logo` asset; when present that image is * used as-is instead. */ -export function Logo({ className, symbolOnly, ...props }: LogoProps) { +export function Logo({ className, symbolOnly, label, ...props }: LogoProps) { const { theme, resolvedMode } = useMindTheme(); const mode = pickMode(resolvedMode); + const wordmark = label ?? theme.label; const custom = theme.logo?.[mode]; if (custom) { return (
{/* eslint-disable-next-line @next/next/no-img-element -- data-URI brand asset */} - {`${theme.label} + {`${wordmark}
); } @@ -53,7 +61,7 @@ export function Logo({ className, symbolOnly, ...props }: LogoProps) { ) : null} {symbolOnly ? null : ( - {theme.label} + {wordmark} )} diff --git a/stories/foundations/Logo.mdx b/stories/foundations/Logo.mdx index 094c498..7fbe06c 100644 --- a/stories/foundations/Logo.mdx +++ b/stories/foundations/Logo.mdx @@ -41,6 +41,23 @@ import { Logo, Symbol } from "@mind-studio/ui"; +## ๐Ÿท๏ธ Per-app wordmark + +Every Mind product **shares the one weave mark** but signs itself with **its own +name** โ€” the mark + "Drive", "Slides", "Chat", and so on โ€” set in the same Fraunces +serif, weight, and size as the master wordmark. Pass the product name via `label`; +it overrides **only the text**, never the artwork. With no `label`, the lockup falls +back to the active brand's name ("Mind"), so the host shell and landing stay +unchanged. + +```tsx + {/* mark + "Drive" in the brand serif */} + {/* mark + "Slides" */} + {/* mark + "Mind" (default) */} +``` + + + ## ๐Ÿ“ Construction The mark is drawn on a circular grid โ€” every node shares one unit radius and a diff --git a/stories/foundations/Logo.stories.tsx b/stories/foundations/Logo.stories.tsx index da775b8..18cbb6c 100644 --- a/stories/foundations/Logo.stories.tsx +++ b/stories/foundations/Logo.stories.tsx @@ -46,7 +46,7 @@ function Caption({ children }: { children: React.ReactNode }) { * renders in the real typeface rather than a baked image. `ink` fixes the text * colour for the panel it sits on. */ -function Wordmark({ ink, size = 40 }: { ink: string; size?: number }) { +function Wordmark({ ink, size = 40, text = "Mind" }: { ink: string; size?: number; text?: string }) { return ( @@ -54,7 +54,7 @@ function Wordmark({ ink, size = 40 }: { ink: string; size?: number }) { className="font-serif font-semibold leading-none tracking-tight" style={{ fontSize: size * 0.82, color: ink }} > - Mind + {text} ); @@ -108,6 +108,32 @@ export const Lockups: Story = { ), }; +/* ------------------------------------------------------- product wordmarks */ + +const PRODUCTS = ["Mind", "Drive", "Slides", "Chat", "Calendar", "Videos"] as const; + +export const ProductLockups: Story = { + tags: ["!dev"], + render: () => ( +
+ +
+ {PRODUCTS.map((p) => ( + + ))} +
+
+ +
+ {PRODUCTS.map((p) => ( + + ))} +
+
+
+ ), +}; + /* ------------------------------------------------------------ construction */ export const Construction: Story = {