Skip to content

Commit

Permalink
Add initial preview and export of auto generated styleguide #41
Browse files Browse the repository at this point in the history
  • Loading branch information
drewdecarme committed Feb 7, 2025
1 parent d403f3e commit 163ca82
Show file tree
Hide file tree
Showing 14 changed files with 753 additions and 17 deletions.
Binary file modified .yarn/install-state.gz
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const bodyStyles = css`
grid-template-rows: auto 1fr;
height: 100%;
background: white;
min-width: ${makeRem(300)};
.s-head {
border-bottom: 1px solid ${makeColor("neutral-light", { opacity: 0.1 })};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { css } from "@linaria/core";
import { Fragment } from "react/jsx-runtime";

import { StyleGuideBasicHome } from "./StyleGuideBasicHome";
import { styleGuideSections } from "./style-guide.utils";
import { StyleGuidePageBreak } from "./StyleGuidePageBreak";

const styles = css`
background: white;
Expand All @@ -11,8 +13,14 @@ export function StyleGuideBasic() {
return (
<div className={styles}>
<StyleGuideBasicHome />
<StyleGuidePageBreak />
{styleGuideSections.map(({ Component, ...restProps }, i) => {
return <Component key={`section-${i}`} {...restProps} />;
return (
<Fragment key={`section-${i}`}>
<Component {...restProps} />
<StyleGuidePageBreak />
</Fragment>
);
})}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
ColorAccessibilityChecker,
getAccessibleTextColor,
} from "@buttery/tokens-utils";
import { Fragment } from "react/jsx-runtime";
import { useRef } from "react";

import { IconTick01 } from "~/icons/IconTick01";
import { IconCancel } from "~/icons/IconCancel";
Expand Down Expand Up @@ -110,9 +112,10 @@ export function StyleGuideBasicColor({
const bVariants = convertBrandColorIntoVariants(color);
const nVariants = convertNeutralColorIntoVariants(color);
const variants = Object.assign(bVariants, nVariants);
const pageRef = useRef<HTMLElement | null>(null);

return (
<StyleGuidePage>
<StyleGuidePage ref={pageRef} className="style-guide">
<StyleGuidePageLeft dxMarker={dxMarker} dxTitle={dxTitle}>
<p>
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Magnam,
Expand Down Expand Up @@ -143,8 +146,9 @@ export function StyleGuideBasicColor({
{Object.entries(variants).map(
([colorName, { base: baseHex, ...restVariants }], i) => {
const baseWcag = checker.analyze(baseHex, bgColor, 16);

return (
<>
<Fragment key={baseHex}>
<tr key={`${colorName}-${i}`}>
<td>
<div
Expand Down Expand Up @@ -211,7 +215,7 @@ export function StyleGuideBasicColor({
);
})}
<tr className={gapStyles} />
</>
</Fragment>
);
}
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export function StyleGuideBasicFont(props: StyleGuideSharedProps) {
{match(font)
.with({ source: "manual" }, (state) =>
Object.entries(state.families).map(([familyId, familyDef]) => {
console.log(familyDef);
return (
<div
key={familyId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function StyleGuideBasicTypography(props: StyleGuideSharedProps) {
<div
className="typ-display"
style={{
fontFamily: `"${fontFamily}"`,
fontFamily: fontFamily ? `"${fontFamily}"` : undefined,
fontSize: variant.size,
fontWeight: variant.weight.split("-")[1],
lineHeight: variant.lineHeight,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Button } from "~/components/Button";
import { IconDownload05 } from "~/icons/IconDownload05";
import { IconFloppyDisk } from "~/icons/IconFloppyDisk";

import { useExportStyleGuide } from "./style-guide.useDownload";

const styles = css`
display: flex;
justify-content: flex-end;
Expand All @@ -13,6 +15,8 @@ const styles = css`
`;

export function StyleGuideControlBar() {
const { exportStyleGuide } = useExportStyleGuide();

return (
<div className={styles}>
<Button
Expand All @@ -28,6 +32,7 @@ export function StyleGuideControlBar() {
dxSize="big"
dxStyle="outlined"
dxHelp="Export to PDF"
onClick={exportStyleGuide}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ const styles = css`
export const StyleGuidePage = forwardRef<HTMLElement, StyleGuidePageProps>(
function StyleGuidePage({ children, className, ...restProps }, ref) {
return (
<section {...restProps} className={classes(styles, className)} ref={ref}>
<section
{...restProps}
className={classes(styles, "page", className)}
ref={ref}
>
{children}
</section>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { classes } from "@buttery/components";
import { css } from "@linaria/core";
import type { JSX } from "react";
import { forwardRef } from "react";

export type StyleGuidePageBreakPropsNative = JSX.IntrinsicElements["div"];
export type StyleGuidePageBreakProps = StyleGuidePageBreakPropsNative;

const styles = css`
display: block;
break-after: page;
page-break-after: always;
`;

export const StyleGuidePageBreak = forwardRef<
HTMLDivElement,
StyleGuidePageBreakProps
>(function StyleGuidePageBreak({ children, className, ...restProps }, ref) {
return (
<div
{...restProps}
className={classes(styles, "page-break", className)}
ref={ref}
>
{children}
</div>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useConfigurationContext } from "../Config.context";

/**
* Returns a named exportStyleGuide function that will
* save the configuration. All sever loaders will
* re-update
*/
export function useExportStyleGuide() {
const { getConfigFromState } = useConfigurationContext();

async function exportStyleGuide() {
const config = getConfigFromState();

const formData = new FormData();
formData.append("config", JSON.stringify(config, null, 2));

// Send a POST request and open the PDF in a new tab
const response = await fetch("/api/style-guide/export", {
method: "POST",
body: formData,
});

if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
window.open(url, "_blank"); // Open the PDF in a new tab
}
}

return { exportStyleGuide };
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { makeRem } from "@buttery/tokens/playground";
import { css } from "@linaria/core";
import type { JSX } from "react";
import { getAccessibleTextColor } from "@buttery/tokens-utils";

import { StyleGuideBasicColor } from "./StyleGuideBasicColor";
import { StyleGuideBasicFont } from "./StyleGuideBasicFont";
import { StyleGuideBasicSize } from "./StyleGuideBasicSize";
import { StyleGuideBasicSpacing } from "./StyleGuideBasicSpacing";
import { StyleGuideBasicTypography } from "./StyleGuideBasicTypography";

export function getSGColorClass(colorName: string, ...args: string[]) {
return `sg-${colorName}${args ? args.join("-") : ""}`;
}
export function getSgColorClassValues(hex: string) {
return {
backgroundColor: hex,
color: getAccessibleTextColor(hex),
};
}

export const styleGuideTableStyles = css`
position: relative;
border: 0;
Expand Down
143 changes: 143 additions & 0 deletions packages/buttery-tokens-studio/app/routes/api.style-guide.export.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import path from "node:path";
import { readdir, readFile, writeFile } from "node:fs/promises";

import puppeteer from "puppeteer";
// import { json } from "@remix-run/node";
import type { ButteryTokensConfig } from "@buttery/tokens-utils/schemas";
import { renderToString } from "react-dom/server";
// import React from "react";
import type { ActionFunctionArgs } from "react-router";
import { collect } from "@linaria/server";

import { ConfigurationProvider } from "~/features/Config.context";
import { StyleGuideBasic } from "~/features/style-guide/StyleGuideBasic";
import {
convertBrandColorIntoVariants,
convertNeutralColorIntoVariants,
getInitColorStateFromConfig,
} from "~/features/color/color.utils";
import {
getSGColorClass,
getSgColorClassValues,
} from "~/features/style-guide/style-guide.utils";

function htmlTemplate(htmlString: string, cssString: string) {
return `<!DOCTYPE html><html>
<head>
<style type="text/css">${cssString}</style>
</head>
<body>${htmlString}</body></html>`;
}

export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const config = String(formData.get("config"));
const configJson = JSON.parse(config) as ButteryTokensConfig;

const clientAssetsDir =
process.env.NODE_ENV !== "production"
? path.resolve(process.cwd(), "./build/client/assets")
: "";
const cssOutFile =
process.env.NODE_ENV !== "production"
? path.resolve(process.cwd(), "./build/app-css.css")
: "";
const htmlOutFile =
process.env.NODE_ENV !== "production"
? path.resolve(process.cwd(), "./build/app-html.html")
: "";

// Manually create some CSS styles
const color = getInitColorStateFromConfig(configJson);
const bVariants = convertBrandColorIntoVariants(color);
const nVariants = convertNeutralColorIntoVariants(color);
const variants = Object.assign(bVariants, nVariants);

// Loop through all of the color and their variants and create individual classes
let styleGuideCSS = "";
for (const [colorName, { base }] of Object.entries(variants)) {
const attributes = getSgColorClassValues(base);
styleGuideCSS = styleGuideCSS.concat(`
.${getSGColorClass(colorName)} {
background-color: ${attributes.backgroundColor};
color: ${attributes.color};
}`);
}

const dirents = await readdir(clientAssetsDir, { withFileTypes: true });
let restCss = "";
let root = "";
for await (const dirent of dirents) {
if (!dirent.name.includes(".css")) continue;
const cssFilePath = path.join(dirent.parentPath, dirent.name);
const cssContent = await readFile(cssFilePath, { encoding: "utf8" });
// add the root to the beginning
if (dirent.name.includes("root-")) {
root = cssContent;
continue;
}
restCss = restCss.concat(cssContent);
}

// Assemble the root, the style guide colors and the rest of the CSS
const css = styleGuideCSS.concat(root).concat(restCss);

// Assemble the HTML with the critical CSS to the react tree
const reactHtml = renderToString(
<ConfigurationProvider originalConfig={configJson}>
<StyleGuideBasic />
</ConfigurationProvider>
);
const { critical: criticalCSS } = collect(reactHtml, css);
const html = htmlTemplate(reactHtml, criticalCSS);

// Print out some test's for verification
if (process.env.NODE_ENV !== "production") {
await writeFile(htmlOutFile, html);
await writeFile(cssOutFile, criticalCSS);
}

// Render the style guide as HTML
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setContent(html, {
waitUntil: "load",
});
await page.evaluate(() => {
document.querySelectorAll(".page-break").forEach((el) => {
const node = el as HTMLElement;
node.style.display = "block";
node.style.breakAfter = "page";
node.style.pageBreakAfter = "always"; // Fallback
});

document.querySelectorAll(".page").forEach((el) => {
const node = el as HTMLElement;
node.style.borderBottom = "unset";
node.style.marginBottom = "unset";
node.style.paddingBottom = "unset";
node.style.padding = "unset";
node.style.border = "1px solid green";
});
});
const pdfBuffer = await page.pdf({
format: "A4",
printBackground: true,
landscape: true,
margin: {
top: ".5in",
right: ".5in",
bottom: ".5in",
left: ".5in",
},
displayHeaderFooter: false,
});
await browser.close();

return new Response(pdfBuffer, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "inline; filename=style-guide.pdf", // Open in browser instead of downloading
},
});
}
2 changes: 2 additions & 0 deletions packages/buttery-tokens-studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@buttery/logs": "workspace:",
"@buttery/tokens-utils": "workspace:*",
"@buttery/utils": "workspace:",
"@linaria/server": "^6.2.0",
"@monaco-editor/react": "4.6.0",
"@react-router/express": "7.1.3",
"@react-router/fs-routes": "7.1.3",
Expand All @@ -30,6 +31,7 @@
"immer": "10.1.1",
"isbot": "5.1.22",
"monaco-editor": "0.52.2",
"puppeteer": "^24.1.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-router": "7.1.3",
Expand Down
Loading

0 comments on commit 163ca82

Please sign in to comment.