Skip to content

Commit

Permalink
feat(docs): track token references
Browse files Browse the repository at this point in the history
  • Loading branch information
malangcat committed Dec 1, 2024
1 parent 5579a1f commit 59c47cf
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 44 deletions.
2 changes: 1 addition & 1 deletion docs/components/component-spec-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function ComponentSpecBlock(props: ComponentSpecTableProps) {
return (
<Fragment key={variantKey}>
<h3>{variantKey}</h3>
<ComponentVariantTable variant={variant} />
<ComponentVariantTable rootage={rootage} variant={variant} />
</Fragment>
);
});
Expand Down
233 changes: 192 additions & 41 deletions docs/components/component-variant-table.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,78 @@
"use client";

import { IconArrowDownLine } from "@daangn/react-monochrome-icon";
import {
ComponentSpecExpression,
resolveToken,
RootageCtx,
stringifyTokenExpression,
stringifyValueExpression,
ValueExpression,
} from "@seed-design/rootage-core";
import { useState } from "react";
import { HourglassIcon, LayersIcon, RulerIcon, SigmaIcon, SplineIcon } from "lucide-react";
import { Fragment, useMemo, useState } from "react";

export function ComponentVariantTable(props: { variant: ComponentSpecExpression[number] }) {
interface ComponentVariantTableProps {
rootage: RootageCtx;
variant: ComponentSpecExpression[number];
}

interface TableItem {
id: string;
stateKey: string;
slotKey: string;
propertyKey: string;
values: string[];
resolvedValue: ValueExpression;
}

export function ComponentVariantTable(props: ComponentVariantTableProps) {
const [hoveredRow, setHoveredRow] = useState<{
slotKey: string;
propertyKey: string;
} | null>(null);

const { variant } = props;
const tableItems = variant.state.flatMap((state) => {
const stateKey = state.key.join(", ");
return state.slot.flatMap((slot) => {
const slotKey = slot.key;
return slot.property.map((property) => {
const propertyKey = property.key;
const value = property.value;
return {
stateKey,
slotKey,
propertyKey,
value,
};
});
});
});
const { rootage, variant } = props;
const tableItems: TableItem[] = useMemo(
() =>
variant.state.flatMap((state) => {
const stateKey = state.key.join(", ");
return state.slot.flatMap((slot) => {
const slotKey = slot.key;
return slot.property.map((property) => {
const propertyKey = property.key;

if (property.value.type === "token") {
const { path, value } = resolveToken(rootage, property.value, {
global: "default",
color: "theme-light",
});
return {
id: `${stateKey}/${slotKey}/${propertyKey}`,
stateKey,
slotKey,
propertyKey,
values: [
...path.map((node) => stringifyTokenExpression(node.token)),
stringifyValueExpression(value),
],
resolvedValue: value,
};
}

return {
id: `${stateKey}/${slotKey}/${propertyKey}`,
stateKey,
slotKey,
propertyKey,
values: [stringifyValueExpression(property.value)],
resolvedValue: property.value,
};
});
});
}),
[rootage, variant],
);

return (
<table>
Expand All @@ -48,34 +91,142 @@ export function ComponentVariantTable(props: { variant: ComponentSpecExpression[
</tr>
</thead>
<tbody>
{tableItems.map(({ stateKey, slotKey, propertyKey, value }, index) => {
{tableItems.map((item, index) => {
const prevItem = tableItems[index - 1];
const shouldRenderState = stateKey !== prevItem?.stateKey;
const shouldRenderSlot = shouldRenderState || slotKey !== prevItem?.slotKey;
const shouldRenderProperty = shouldRenderSlot || propertyKey !== prevItem?.propertyKey;
const shouldRenderState = item.stateKey !== prevItem?.stateKey;
const shouldRenderSlot = shouldRenderState || item.slotKey !== prevItem?.slotKey;
const shouldRenderProperty =
shouldRenderSlot || item.propertyKey !== prevItem?.propertyKey;
return (
<tr
className={
hoveredRow?.slotKey === slotKey && hoveredRow?.propertyKey === propertyKey
? "bg-fd-muted"
: ""
<ComponentVariantRow
key={item.id}
item={item}
shouldRenderState={shouldRenderState}
shouldRenderSlot={shouldRenderSlot}
shouldRenderProperty={shouldRenderProperty}
isHighlighted={
hoveredRow?.slotKey === item.slotKey && hoveredRow?.propertyKey === item.propertyKey
}
key={`${stateKey}/${slotKey}/${propertyKey}`}
onMouseEnter={() => setHoveredRow({ slotKey, propertyKey })}
onMouseLeave={() => setHoveredRow(null)}
>
<td>{shouldRenderState ? stateKey : null}</td>
<td>{shouldRenderSlot ? slotKey : null}</td>
<td>{shouldRenderProperty ? propertyKey : null}</td>
<td>
{value.type === "token"
? stringifyTokenExpression(value)
: stringifyValueExpression(value)}
</td>
</tr>
onHoverStart={setHoveredRow}
onHoverEnd={() => setHoveredRow(null)}
/>
);
})}
</tbody>
</table>
);
}

function ComponentVariantRow(props: {
item: TableItem;
shouldRenderState: boolean;
shouldRenderSlot: boolean;
shouldRenderProperty: boolean;
isHighlighted: boolean;
onHoverStart: (details: { slotKey: string; propertyKey: string }) => void;
onHoverEnd: () => void;
}) {
const {
item,
shouldRenderState,
shouldRenderSlot,
shouldRenderProperty,
isHighlighted,
onHoverStart,
onHoverEnd,
} = props;
const { id, stateKey, slotKey, propertyKey, values, resolvedValue } = item;

const [isExpanded, setIsExpanded] = useState(false);

return (
<tr
className={isHighlighted ? "bg-fd-muted cursor-pointer" : "cursor-pointer"}
key={id}
onMouseEnter={() => onHoverStart({ slotKey, propertyKey })}
onMouseLeave={() => onHoverEnd()}
onClick={() => setIsExpanded((prev) => !prev)}
>
<td>{shouldRenderState ? stateKey : null}</td>
<td>{shouldRenderSlot ? slotKey : null}</td>
<td>{shouldRenderProperty ? propertyKey : null}</td>
<td className="align-middle">
<div className="flex flex-col gap-1">
{isExpanded ? (
values.map((value, index) => (
<Fragment key={value}>
<div className="flex items-center gap-2">
<TypeIndicator resolvedValue={resolvedValue} /> {value}
</div>
{index < values.length - 1 ? (
<div>
<IconArrowDownLine className="w-3 h-3" />
</div>
) : null}
</Fragment>
))
) : (
<div className="flex items-center gap-2">
<TypeIndicator resolvedValue={resolvedValue} /> {values[0]}
</div>
)}
</div>
</td>
</tr>
);
}

function TypeIndicator(props: { resolvedValue: ValueExpression }) {
const { resolvedValue } = props;

if (resolvedValue.type === "color") {
return (
<div
className="w-4 h-4 rounded-full border"
style={{ backgroundColor: resolvedValue.value }}
/>
);
}

if (resolvedValue.type === "dimension") {
return (
<div>
<RulerIcon className="w-4 h-4" />
</div>
);
}

if (resolvedValue.type === "duration") {
return (
<div>
<HourglassIcon className="w-4 h-4" />
</div>
);
}

if (resolvedValue.type === "number") {
return (
<div>
<SigmaIcon className="w-4 h-4" />
</div>
);
}

if (resolvedValue.type === "shadow") {
return (
<div>
<LayersIcon className="w-4 h-4" />
</div>
);
}

if (resolvedValue.type === "cubicBezier") {
return (
<div>
<SplineIcon className="w-4 h-4" />
</div>
);
}

return null;
}
4 changes: 2 additions & 2 deletions docs/components/get-rootage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Model, parse } from "@seed-design/rootage-core";
import { Model, buildRootage } from "@seed-design/rootage-core";

export const getRootage = async () => {
const index: { resources: { path: string }[] } = await import("@/public/rootage/index.json").then(
Expand All @@ -9,5 +9,5 @@ export const getRootage = async () => {
const models: Model[] = await Promise.all(
index.resources.map((resource) => import(`@/public/rootage${resource.path}`)),
);
return parse(models);
return buildRootage(models);
};

0 comments on commit 59c47cf

Please sign in to comment.