Skip to content

Commit 5a8599c

Browse files
committed
docs(stories): redesign documentation and expand Storybook examples
Rebuild Props and Handlers doc pages with dedicated React components (PropTable, HandlerCards, DocTable) and a shared CSS module for consistent styling. Convert broken markdown tables to styled JSX across hook and example stories. Add new examples: product-card, text-selection, center-zoomed-out, virtualize. Refresh existing examples with improved layouts and interactivity. Made-with: Cursor
1 parent f9d7ac8 commit 5a8599c

49 files changed

Lines changed: 8309 additions & 2594 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.storybook/preview.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const preview: Preview = {
1919
"Advanced",
2020
"Components",
2121
"Examples",
22+
["Product card", "*"],
2223
"Hooks",
2324
],
2425
},

src/stories/docs/DocTable.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from "react";
2+
import styles from "./docs.module.css";
3+
4+
export type DocTableColumn = {
5+
header: string;
6+
style?: "name" | "type" | "default" | "text";
7+
};
8+
9+
export function DocTable({
10+
columns,
11+
rows,
12+
}: {
13+
columns: DocTableColumn[];
14+
rows: string[][];
15+
}) {
16+
return (
17+
<table className={styles.docTable}>
18+
<thead>
19+
<tr className={styles.docTableHeadRow}>
20+
{columns.map((col, i) => (
21+
<th key={i} className={styles.docTableTh}>
22+
{col.header}
23+
</th>
24+
))}
25+
</tr>
26+
</thead>
27+
<tbody>
28+
{rows.map((row, ri) => (
29+
<tr
30+
key={ri}
31+
className={
32+
ri % 2 === 1 ? styles.docTableRowAlt : styles.docTableRow
33+
}
34+
>
35+
{row.map((cell, ci) => {
36+
const colStyle = columns[ci]?.style ?? (ci === 0 ? "name" : "text");
37+
const cls =
38+
colStyle === "name"
39+
? styles.docTableCellName
40+
: colStyle === "type"
41+
? styles.docTableCellType
42+
: colStyle === "default"
43+
? styles.docTableCellDefault
44+
: styles.docTableCellText;
45+
return (
46+
<td key={ci} className={cls}>
47+
{cell}
48+
</td>
49+
);
50+
})}
51+
</tr>
52+
))}
53+
</tbody>
54+
</table>
55+
);
56+
}

src/stories/docs/HandlerCards.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import React from "react";
2+
import type { ControlsFnOptionsType } from "./handlers";
3+
import styles from "./docs.module.css";
4+
5+
function parseParam(raw: string) {
6+
const eqIdx = raw.indexOf("=");
7+
const colonIdx = raw.indexOf(":");
8+
9+
if (colonIdx === -1) return { name: raw.trim(), type: "", def: "" };
10+
11+
const name = raw.slice(0, colonIdx).trim();
12+
let type: string;
13+
let def = "";
14+
15+
if (eqIdx !== -1 && eqIdx > colonIdx) {
16+
type = raw.slice(colonIdx + 1, eqIdx).trim();
17+
def = raw.slice(eqIdx + 1).trim();
18+
} else {
19+
type = raw.slice(colonIdx + 1).trim();
20+
}
21+
22+
return { name, type, def };
23+
}
24+
25+
export function HandlerCards({
26+
rows,
27+
}: {
28+
rows: ControlsFnOptionsType[];
29+
}) {
30+
return (
31+
<div className={styles.container}>
32+
<div className={styles.sectionTitle}>
33+
<span className={styles.sectionTitleAccent} />
34+
Handlers
35+
</div>
36+
<p className={styles.handlersIntro}>
37+
Methods available via <strong>render props</strong>,{" "}
38+
<strong>ref</strong>, or the{" "}
39+
<span className={styles.handlersIntroHook}>useControls</span>{" "}
40+
hook. Each handler triggers an animated transition on the
41+
transform state.
42+
</p>
43+
44+
<div className={styles.handlerGrid}>
45+
{rows.map((row, i) => (
46+
<div key={i} className={styles.handlerCard}>
47+
<div className={styles.handlerCardHeader}>
48+
<span className={styles.handlerName}>
49+
{row.name}
50+
{"("}
51+
{row.parameters && row.parameters.length > 0 && (
52+
<span className={styles.handlerNameParams}>
53+
{"{ "}
54+
{row.parameters
55+
.map((raw) => parseParam(raw).name)
56+
.join(", ")}
57+
{" }"}
58+
</span>
59+
)}
60+
{")"}
61+
</span>
62+
<span className={styles.handlerBadge}>Method</span>
63+
</div>
64+
65+
<div className={styles.handlerCardBody}>
66+
{row.description && (
67+
<p className={styles.handlerDescription}>
68+
{row.description}
69+
</p>
70+
)}
71+
72+
{row.parameters && row.parameters.length > 0 && (
73+
<table className={styles.paramsTable}>
74+
<thead>
75+
<tr>
76+
<th className={styles.paramsTableHeader}>Param</th>
77+
<th className={styles.paramsTableHeader}>Type</th>
78+
<th className={styles.paramsTableHeader}>Default</th>
79+
</tr>
80+
</thead>
81+
<tbody>
82+
{row.parameters.map((raw, j) => {
83+
const p = parseParam(raw);
84+
return (
85+
<tr key={j}>
86+
<td className={styles.paramsTableCellName}>
87+
{p.name}
88+
</td>
89+
<td className={styles.paramsTableCellType}>
90+
{p.type}
91+
</td>
92+
<td className={styles.paramsTableCellDefault}>
93+
{p.def || "—"}
94+
</td>
95+
</tr>
96+
);
97+
})}
98+
</tbody>
99+
</table>
100+
)}
101+
</div>
102+
</div>
103+
))}
104+
</div>
105+
</div>
106+
);
107+
}

src/stories/docs/PropTable.tsx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React from "react";
2+
import type { ControlsOptionsType } from "./props";
3+
import styles from "./docs.module.css";
4+
5+
type Group = {
6+
header: ControlsOptionsType | null;
7+
children: ControlsOptionsType[];
8+
};
9+
10+
export function PropTable({
11+
rows,
12+
title,
13+
}: {
14+
rows: ControlsOptionsType[];
15+
title: string;
16+
}) {
17+
const groups: Group[] = [];
18+
let current: Group | null = null;
19+
20+
rows.forEach((row) => {
21+
if (row.isObjectRow) {
22+
current = { header: row, children: [] };
23+
groups.push(current);
24+
} else if (current) {
25+
current.children.push(row);
26+
} else {
27+
groups.push({ header: null, children: [row] });
28+
}
29+
});
30+
31+
let rowIndex = 0;
32+
33+
return (
34+
<div className={styles.container}>
35+
<div className={styles.sectionTitle}>
36+
<span className={styles.sectionTitleAccent} />
37+
{title}
38+
</div>
39+
40+
<div className={styles.columnHeader}>
41+
<span>Property</span>
42+
<span>Type</span>
43+
<span>Default</span>
44+
<span>Description</span>
45+
</div>
46+
47+
{groups.map((group, gi) => {
48+
if (group.header) rowIndex = 0;
49+
return (
50+
<div key={gi}>
51+
{group.header && (
52+
<div className={styles.groupHeader}>
53+
<span className={styles.groupName}>
54+
{typeof group.header.name === "string"
55+
? group.header.name
56+
: (
57+
group.header.name as React.ReactElement<{
58+
children?: React.ReactNode;
59+
}>
60+
)?.props?.children ?? ""}
61+
</span>
62+
{group.children.length > 0 && (
63+
<span className={styles.groupCount}>
64+
{group.children.length} props
65+
</span>
66+
)}
67+
</div>
68+
)}
69+
70+
{group.children.map((row, ri) => {
71+
const isNested = !!group.header;
72+
const propName =
73+
typeof row.name === "string" ? row.name : null;
74+
const isAlt = rowIndex++ % 2 === 1;
75+
76+
const rowClass = isNested
77+
? isAlt
78+
? styles.propRowNestedAlt
79+
: styles.propRowNested
80+
: isAlt
81+
? styles.propRowAlt
82+
: styles.propRow;
83+
84+
return (
85+
<div key={ri} className={rowClass}>
86+
<span className={styles.propName}>
87+
{isNested && (
88+
<span className={styles.nestDot}>.</span>
89+
)}
90+
{propName ?? row.name}
91+
</span>
92+
93+
<span className={styles.typeText}>
94+
{row.type.filter(Boolean).join(" | ")}
95+
</span>
96+
97+
<span>
98+
{row.defaultValue && row.defaultValue !== "" ? (
99+
<span className={styles.defaultText}>
100+
{row.defaultValue}
101+
</span>
102+
) : (
103+
<span className={styles.defaultEmpty}></span>
104+
)}
105+
</span>
106+
107+
<span className={styles.description}>
108+
{row.description}
109+
</span>
110+
</div>
111+
);
112+
})}
113+
</div>
114+
);
115+
})}
116+
</div>
117+
);
118+
}

0 commit comments

Comments
 (0)