Skip to content
Open
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
18 changes: 18 additions & 0 deletions frontend/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { StorybookConfig } from "@storybook/html-vite";

const config: StorybookConfig = {
stories: ["../src/stories/**/*.stories.@(ts|js)"],
addons: [
"@storybook/addon-essentials",
"@storybook/addon-a11y",
],
framework: {
name: "@storybook/html-vite",
options: {},
},
docs: {
autodocs: "tag",
},
};

export default config;
36 changes: 36 additions & 0 deletions frontend/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Preview } from "@storybook/html";
import "../src/style.css";

const preview: Preview = {
parameters: {
backgrounds: {
default: "dark",
values: [
{ name: "dark", value: "#0d1117" },
{ name: "light", value: "#f6f8fa" },
],
},
layout: "centered",
},
globalTypes: {
theme: {
description: "UI theme",
defaultValue: "dark",
toolbar: {
title: "Theme",
icon: "paintbrush",
items: ["dark", "light"],
dynamicTitle: true,
},
},
},
decorators: [
(story, context) => {
const theme = context.globals.theme ?? "dark";
document.documentElement.setAttribute("data-theme", theme);
return story();
},
],
};

export default preview;
9 changes: 8 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,21 @@
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest run"
"test": "vitest run",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@creit-tech/stellar-wallets-kit": "npm:@jsr/creit-tech__stellar-wallets-kit@^2.0.1",
"@stellar-broker/client": "^0.6.14",
"@stellar/stellar-sdk": "^14.6.1"
},
"devDependencies": {
"@storybook/addon-a11y": "^8.4.0",
"@storybook/addon-essentials": "^8.4.0",
"@storybook/html": "^8.4.0",
"@storybook/html-vite": "^8.4.0",
"storybook": "^8.4.0",
"typescript": "^5.7.3",
"vite": "^6.2.0",
"vitest": "^3.0.0"
Expand Down
105 changes: 105 additions & 0 deletions frontend/src/stories/AprCard.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type { Meta, StoryObj } from "@storybook/html";

interface AprRow {
label: string;
value: string;
color?: string;
}

interface AprCardArgs {
title: "Supply" | "Borrow";
baseApr: number;
blndEmissions: number;
extraRows: AprRow[];
}

function renderAprCard({ title, baseApr, blndEmissions, extraRows }: AprCardArgs): string {
const totalApr = baseApr + blndEmissions;
const isSupply = title === "Supply";
const primaryColor = isSupply ? "#2ea043" : "#f85149";

const extraHtml = extraRows
.map(
(r) => `
<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 0;">
<span style="color:var(--color-text-muted,#8b949e);font-size:12px;">${r.label}</span>
<span style="color:${r.color ?? "var(--color-text,#e6edf3)"};font-size:12px;font-weight:600;">${r.value}</span>
</div>`
)
.join("");

return `
<div class="apr-card" style="
width:200px;
background:var(--color-surface,#161b22);
border:1px solid var(--color-border,#21262d);
border-radius:8px;
padding:12px 16px;
">
<div class="apr-card-label" style="
font-size:11px;
text-transform:uppercase;
letter-spacing:0.05em;
color:var(--color-text-muted,#8b949e);
margin-bottom:8px;
">${title}</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
<span style="font-size:12px;color:var(--color-text-muted,#8b949e);">Base APR</span>
<span style="font-size:14px;font-weight:700;color:${primaryColor};">${baseApr.toFixed(2)}%</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;padding-bottom:8px;border-bottom:1px solid var(--color-border,#21262d);margin-bottom:8px;">
<span style="font-size:12px;color:var(--color-text-muted,#8b949e);">BLND emissions</span>
<span style="font-size:12px;font-weight:600;color:#d29922;">+${blndEmissions.toFixed(2)}%</span>
</div>
${extraHtml}
<div style="display:flex;justify-content:space-between;align-items:center;padding-top:8px;border-top:1px solid var(--color-border,#21262d);margin-top:${extraRows.length ? "8px" : "0"};">
<span style="font-size:12px;color:var(--color-text,#e6edf3);font-weight:600;">Total APY</span>
<span style="font-size:15px;font-weight:700;color:${primaryColor};">${totalApr.toFixed(2)}%</span>
</div>
</div>
`;
}

const meta: Meta<AprCardArgs> = {
title: "Components/APR Card",
tags: ["autodocs"],
render: (args) => renderAprCard(args),
argTypes: {
title: { control: { type: "select" }, options: ["Supply", "Borrow"] },
baseApr: { control: { type: "number", min: 0, max: 50, step: 0.01 } },
blndEmissions: { control: { type: "number", min: 0, max: 20, step: 0.01 } },
},
};

export default meta;
type Story = StoryObj<AprCardArgs>;

export const SupplyCard: Story = {
args: {
title: "Supply",
baseApr: 9.84,
blndEmissions: 1.23,
extraRows: [],
},
};

export const BorrowCard: Story = {
args: {
title: "Borrow",
baseApr: 5.12,
blndEmissions: 0.45,
extraRows: [],
},
};

export const SupplyWithExtras: Story = {
args: {
title: "Supply",
baseApr: 7.5,
blndEmissions: 2.1,
extraRows: [
{ label: "Protocol fee", value: "-0.50%", color: "#f85149" },
{ label: "Compounding", value: "+0.12%", color: "#2ea043" },
],
},
};
119 changes: 119 additions & 0 deletions frontend/src/stories/AssetPicker.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import type { Meta, StoryObj } from "@storybook/html";

interface Asset {
symbol: string;
name: string;
balance?: string;
}

interface AssetPickerArgs {
assets: Asset[];
selectedIndex: number;
showBalances: boolean;
}

function renderAssetPicker(args: AssetPickerArgs): HTMLElement {
const { assets, selectedIndex, showBalances } = args;
const wrap = document.createElement("div");
wrap.style.cssText = "width:360px;";

const bar = document.createElement("div");
bar.className = "asset-tabs-bar";
bar.style.cssText = `
background:var(--color-surface,#161b22);
border:1px solid var(--color-border,#21262d);
border-radius:8px;
padding:4px;
display:flex;
gap:4px;
`;

assets.forEach((asset, i) => {
const btn = document.createElement("button");
btn.role = "tab";
btn.setAttribute("aria-selected", String(i === selectedIndex));
btn.style.cssText = `
flex:1;
padding:8px 4px;
border:none;
border-radius:6px;
cursor:pointer;
font-family:inherit;
font-size:13px;
background:${i === selectedIndex ? "var(--color-accent,#1f6feb)" : "transparent"};
color:${i === selectedIndex ? "#ffffff" : "var(--color-text-muted,#8b949e)"};
font-weight:${i === selectedIndex ? "600" : "400"};
transition:background 0.15s;
`;

const label = document.createElement("div");
label.textContent = asset.symbol;

if (showBalances && asset.balance) {
const bal = document.createElement("div");
bal.style.cssText = "font-size:10px;margin-top:2px;opacity:0.8;";
bal.textContent = asset.balance;
btn.appendChild(label);
btn.appendChild(bal);
} else {
btn.appendChild(label);
}

btn.addEventListener("click", () => {
bar.querySelectorAll("button").forEach((b, j) => {
const isSelected = j === i;
b.setAttribute("aria-selected", String(isSelected));
(b as HTMLElement).style.background = isSelected
? "var(--color-accent,#1f6feb)"
: "transparent";
(b as HTMLElement).style.color = isSelected
? "#ffffff"
: "var(--color-text-muted,#8b949e)";
(b as HTMLElement).style.fontWeight = isSelected ? "600" : "400";
});
});

bar.appendChild(btn);
});

wrap.appendChild(bar);
return wrap;
}

const meta: Meta<AssetPickerArgs> = {
title: "Components/Asset Picker",
tags: ["autodocs"],
render: (args) => renderAssetPicker(args),
argTypes: {
selectedIndex: { control: { type: "number", min: 0 } },
showBalances: { control: "boolean" },
},
};

export default meta;
type Story = StoryObj<AssetPickerArgs>;

export const Etherfuse: Story = {
args: {
assets: [
{ symbol: "USDC", name: "USD Coin", balance: "1,240.00" },
{ symbol: "XLM", name: "Lumen", balance: "5,000.00" },
{ symbol: "CETES",name: "CETES Token", balance: "320.50" },
],
selectedIndex: 2,
showBalances: true,
},
};

export const MultiAsset: Story = {
args: {
assets: [
{ symbol: "USDC", name: "USD Coin" },
{ symbol: "wETH", name: "Wrapped Ether" },
{ symbol: "wBTC", name: "Wrapped Bitcoin" },
{ symbol: "XLM", name: "Lumen" },
],
selectedIndex: 0,
showBalances: false,
},
};
68 changes: 68 additions & 0 deletions frontend/src/stories/HfBadge.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { Meta, StoryObj } from "@storybook/html";

interface HfBadgeArgs {
value: number;
label?: string;
}

function renderHfBadge({ value, label = "Health Factor" }: HfBadgeArgs): string {
const color =
value >= 1.5 ? "var(--color-success, #2ea043)" :
value >= 1.2 ? "var(--color-warn, #d29922)" :
"var(--color-danger, #f85149)";

const displayVal = isFinite(value) ? value.toFixed(3) : "∞";

return `
<div style="
display:inline-flex;
flex-direction:column;
align-items:center;
gap:4px;
font-family: 'JetBrains Mono', monospace;
">
<span style="font-size:11px;color:var(--color-text-muted,#8b949e);text-transform:uppercase;letter-spacing:0.05em;">
${label}
</span>
<span style="
font-size:22px;
font-weight:700;
color:${color};
padding:4px 12px;
border:2px solid ${color};
border-radius:6px;
">
${displayVal}
</span>
</div>
`;
}

const meta: Meta<HfBadgeArgs> = {
title: "Components/HF Badge",
tags: ["autodocs"],
render: (args) => renderHfBadge(args),
argTypes: {
value: { control: { type: "number", min: 1, max: 5, step: 0.05 } },
label: { control: "text" },
},
};

export default meta;
type Story = StoryObj<HfBadgeArgs>;

export const Safe: Story = {
args: { value: 1.85, label: "Health Factor" },
};

export const Warning: Story = {
args: { value: 1.25, label: "Health Factor" },
};

export const Critical: Story = {
args: { value: 1.05, label: "Health Factor" },
};

export const Infinite: Story = {
args: { value: Infinity, label: "Health Factor" },
};
Loading