Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
import type { Preview } from "@storybook/react";
import type { Decorator, Preview } from "@storybook/react";
import React from "react";
import "../src/app/globals.css";

/**
* Wraps every story in a div carrying the app's `.dark` theme class (the same
* mechanism `next-themes` uses on `<html>` via `attribute="class"`, see
* `src/app/layout.tsx`), driven by the "Theme" toolbar toggle below. This
* makes every story β€” foundations and existing components alike β€” re-render
* with the correct design tokens for the selected theme.
*/
const withTheme: Decorator = (Story, context) => {
const theme = context.globals.theme === "light" ? "light" : "dark";
return (
<div
className={theme === "dark" ? "dark" : ""}
style={{
background: "var(--background)",
color: "var(--foreground)",
minHeight: "100%",
padding: "1rem",
}}
>
<Story />
</div>
);
};

const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
Expand Down Expand Up @@ -34,6 +59,22 @@ const preview: Preview = {
},
},
},
globalTypes: {
theme: {
name: "Theme",
description: "Global theme (light/dark) applied via the app's .dark class",
defaultValue: "dark",
toolbar: {
icon: "mirror",
items: [
{ value: "light", title: "Light", icon: "sun" },
{ value: "dark", title: "Dark", icon: "moon" },
],
dynamicTitle: true,
},
},
},
decorators: [withTheme],
tags: ["autodocs"],
};

Expand Down
111 changes: 111 additions & 0 deletions src/stories/foundations/Colors.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { Meta, StoryObj } from "@storybook/react";
import React from "react";
import { FoundationSection, TokenCard, TokenGrid } from "./shared";

const meta: Meta = {
title: "Foundations/Colors",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component:
"Color tokens defined as CSS custom properties in `src/app/globals.css`. " +
"Theme-aware tokens are set on `:root` (light) and overridden under `.dark` " +
"(the class next-themes toggles on `<html>`) β€” use the Theme toolbar toggle " +
"above to see them shift. A handful of static brand accents are used directly " +
"as hex values around the app rather than as tokens; they're listed for reference.",
},
},
},
};

export default meta;
type Story = StoryObj;

const THEME_TOKENS: { name: string; cssVar: string }[] = [
{ name: "Background", cssVar: "--color-background" },
{ name: "Foreground", cssVar: "--color-foreground" },
{ name: "Surface", cssVar: "--color-surface" },
{ name: "Surface Raised", cssVar: "--color-surface-raised" },
{ name: "Border", cssVar: "--color-border" },
{ name: "Muted", cssVar: "--color-muted" },
];

const STATIC_TOKENS: { name: string; cssVar: string; hex: string }[] = [
{ name: "Neon Green", cssVar: "--color-neon-green", hex: "#39ff14" },
{ name: "Oracle Navy", cssVar: "--color-oracle-navy", hex: "#0a0f1e" },
{ name: "Oracle Border", cssVar: "--color-oracle-border", hex: "#1b2a3b" },
];

const BRAND_ACCENTS: { name: string; hex: string; usage: string }[] = [
{ name: "Wallet Button", hex: "#d9f99d", usage: ".wallet-btn background" },
{ name: "Wallet Button Hover", hex: "#bef574", usage: ".wallet-btn:hover accent" },
{ name: "Cyber Lime", hex: "#99dc1b", usage: "Admin tab active state / focus ring" },
];

function Swatch({ background }: { background: string }) {
return (
<div
style={{
height: 64,
background,
borderBottom: "1px solid var(--border)",
}}
/>
);
}

export const ThemeTokens: Story = {
render: () => (
<FoundationSection
title="Theme tokens"
description="Re-defined per theme via :root / .dark. Toggle the Theme control in the toolbar to compare."
>
<TokenGrid>
{THEME_TOKENS.map((t) => (
<TokenCard
key={t.cssVar}
label={t.name}
value={t.cssVar}
swatch={<Swatch background={`var(${t.cssVar})`} />}
/>
))}
</TokenGrid>
</FoundationSection>
),
};

export const StaticBrandTokens: Story = {
render: () => (
<FoundationSection
title="Static brand tokens"
description="Exposed as CSS custom properties but constant across both themes."
>
<TokenGrid>
{STATIC_TOKENS.map((t) => (
<TokenCard
key={t.cssVar}
label={t.name}
value={`${t.cssVar} Β· ${t.hex}`}
swatch={<Swatch background={t.hex} />}
/>
))}
</TokenGrid>
</FoundationSection>
),
};

export const OtherBrandAccents: Story = {
render: () => (
<FoundationSection
title="Other brand accents"
description="Hardcoded hex values used directly in a handful of components β€” not yet exposed as reusable tokens, listed here for reference."
>
<TokenGrid>
{BRAND_ACCENTS.map((t) => (
<TokenCard key={t.hex} label={t.name} value={`${t.hex} Β· ${t.usage}`} swatch={<Swatch background={t.hex} />} />
))}
</TokenGrid>
</FoundationSection>
),
};
59 changes: 59 additions & 0 deletions src/stories/foundations/Radii.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Meta, StoryObj } from "@storybook/react";
import React from "react";
import { FoundationSection, TokenGrid } from "./shared";

const meta: Meta = {
title: "Foundations/Radii",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component:
"`tailwind.config.js` does not extend `theme.borderRadius`, so every `rounded-*` " +
"utility in the app resolves to Tailwind v4's built-in default scale, reproduced here. " +
"Common app usages: `rounded-lg`/`rounded-xl` for cards and panels, `rounded-full` for " +
"toggles, badges, and avatars.",
},
},
},
};

export default meta;
type Story = StoryObj;

const RADII: { name: string; valueRem: number | "full" }[] = [
{ name: "rounded-none", valueRem: 0 },
{ name: "rounded-sm", valueRem: 0.125 },
{ name: "rounded (default)", valueRem: 0.25 },
{ name: "rounded-md", valueRem: 0.375 },
{ name: "rounded-lg", valueRem: 0.5 },
{ name: "rounded-xl", valueRem: 0.75 },
{ name: "rounded-2xl", valueRem: 1 },
{ name: "rounded-3xl", valueRem: 1.5 },
{ name: "rounded-full", valueRem: "full" },
];

export const DefaultScale: Story = {
render: () => (
<FoundationSection title="Border radius scale">
<TokenGrid>
{RADII.map((r) => (
<div key={r.name} style={{ padding: "0.5rem" }}>
<div
style={{
height: 64,
background: "var(--color-neon-green)",
opacity: 0.85,
borderRadius: r.valueRem === "full" ? 9999 : `${r.valueRem}rem`,
}}
/>
<p style={{ fontSize: "0.8125rem", fontWeight: 500, marginTop: "0.75rem", marginBottom: 0 }}>{r.name}</p>
<p style={{ fontSize: "0.75rem", color: "var(--muted)", fontFamily: "monospace", marginTop: "0.125rem" }}>
{r.valueRem === "full" ? "9999px" : `${r.valueRem}rem`}
</p>
</div>
))}
</TokenGrid>
</FoundationSection>
),
};
55 changes: 55 additions & 0 deletions src/stories/foundations/Shadows.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Meta, StoryObj } from "@storybook/react";
import React from "react";
import { FoundationSection, TokenGrid } from "./shared";

const meta: Meta = {
title: "Foundations/Shadows",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component:
"`tailwind.config.js` does not extend `theme.boxShadow`, so every `shadow-*` " +
"utility in the app resolves to Tailwind v4's built-in default scale, reproduced here.",
},
},
},
};

export default meta;
type Story = StoryObj;

const SHADOWS: { name: string; value: string }[] = [
{ name: "shadow-sm", value: "0 1px 2px 0 rgb(0 0 0 / 0.05)" },
{ name: "shadow (default)", value: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)" },
{ name: "shadow-md", value: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)" },
{ name: "shadow-lg", value: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)" },
{ name: "shadow-xl", value: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)" },
{ name: "shadow-2xl", value: "0 25px 50px -12px rgb(0 0 0 / 0.25)" },
{ name: "shadow-inner", value: "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)" },
];

export const DefaultScale: Story = {
render: () => (
<FoundationSection title="Shadow scale">
<TokenGrid>
{SHADOWS.map((s) => (
<div key={s.name} style={{ padding: "1.5rem 0.75rem" }}>
<div
style={{
height: 64,
borderRadius: "0.5rem",
background: "var(--surface)",
boxShadow: s.value,
}}
/>
<p style={{ fontSize: "0.8125rem", fontWeight: 500, marginTop: "0.75rem", marginBottom: 0 }}>{s.name}</p>
<p style={{ fontSize: "0.6875rem", color: "var(--muted)", fontFamily: "monospace", marginTop: "0.125rem" }}>
{s.value}
</p>
</div>
))}
</TokenGrid>
</FoundationSection>
),
};
108 changes: 108 additions & 0 deletions src/stories/foundations/Typography.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { Meta, StoryObj } from "@storybook/react";
import React from "react";
import { FoundationSection } from "./shared";

const meta: Meta = {
title: "Foundations/Typography",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component:
"Font family tokens come from `src/app/globals.css` (`--font-sans` / `--font-mono`, " +
"both currently mapped to the Geist Sans variable loaded in `src/app/layout.tsx` via " +
"`next/font/google`). The type scale and weights below are Tailwind CSS v4's built-in " +
"defaults β€” `tailwind.config.js` does not override `theme.extend`, so these are exactly " +
"what's available to every component in the app.",
},
},
},
};

export default meta;
type Story = StoryObj;

const FONT_FAMILY = "var(--font-sans, ui-sans-serif, system-ui, sans-serif)";

const TYPE_SCALE: { name: string; sizeRem: number; lineHeightRem: number }[] = [
{ name: "text-xs", sizeRem: 0.75, lineHeightRem: 1 },
{ name: "text-sm", sizeRem: 0.875, lineHeightRem: 1.25 },
{ name: "text-base", sizeRem: 1, lineHeightRem: 1.5 },
{ name: "text-lg", sizeRem: 1.125, lineHeightRem: 1.75 },
{ name: "text-xl", sizeRem: 1.25, lineHeightRem: 1.75 },
{ name: "text-2xl", sizeRem: 1.5, lineHeightRem: 2 },
{ name: "text-3xl", sizeRem: 1.875, lineHeightRem: 2.25 },
{ name: "text-4xl", sizeRem: 2.25, lineHeightRem: 2.5 },
{ name: "text-5xl", sizeRem: 3, lineHeightRem: 1 },
];

const WEIGHTS: { name: string; weight: number }[] = [
{ name: "font-normal", weight: 400 },
{ name: "font-medium", weight: 500 },
{ name: "font-semibold", weight: 600 },
{ name: "font-bold", weight: 700 },
];

export const FontFamilies: Story = {
render: () => (
<FoundationSection title="Font families">
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
<div>
<p style={{ fontSize: "0.75rem", color: "var(--muted)", fontFamily: "monospace", marginBottom: "0.25rem" }}>
--font-sans
</p>
<p style={{ fontFamily: FONT_FAMILY, fontSize: "1.25rem" }}>
The quick brown fox jumps over the lazy dog β€” Geist Sans
</p>
</div>
<div>
<p style={{ fontSize: "0.75rem", color: "var(--muted)", fontFamily: "monospace", marginBottom: "0.25rem" }}>
--font-mono <span style={{ opacity: 0.7 }}>(currently also Geist Sans, not a distinct monospace face)</span>
</p>
<p style={{ fontFamily: FONT_FAMILY, fontSize: "1.25rem" }}>0123456789 StellarFlow tx_hash</p>
</div>
</div>
</FoundationSection>
),
};

export const TypeScale: Story = {
render: () => (
<FoundationSection title="Type scale" description="Tailwind v4 defaults.">
<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
{TYPE_SCALE.map((t) => (
<div key={t.name} style={{ display: "flex", alignItems: "baseline", gap: "1rem" }}>
<span style={{ width: 90, fontSize: "0.75rem", color: "var(--muted)", fontFamily: "monospace" }}>
{t.name}
</span>
<span style={{ width: 130, fontSize: "0.75rem", color: "var(--muted)" }}>
{t.sizeRem}rem / {t.lineHeightRem}rem
</span>
<span style={{ fontFamily: FONT_FAMILY, fontSize: `${t.sizeRem}rem`, lineHeight: `${t.lineHeightRem}rem` }}>
StellarFlow
</span>
</div>
))}
</div>
</FoundationSection>
),
};

export const FontWeights: Story = {
render: () => (
<FoundationSection title="Font weights" description="Tailwind v4 defaults.">
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
{WEIGHTS.map((w) => (
<div key={w.name} style={{ display: "flex", alignItems: "baseline", gap: "1rem" }}>
<span style={{ width: 130, fontSize: "0.75rem", color: "var(--muted)", fontFamily: "monospace" }}>
{w.name} Β· {w.weight}
</span>
<span style={{ fontFamily: FONT_FAMILY, fontSize: "1.25rem", fontWeight: w.weight }}>
StellarFlow Network
</span>
</div>
))}
</div>
</FoundationSection>
),
};
Loading
Loading