Skip to content

Commit daad16f

Browse files
committed
feat(docs): allow all demos to be created in stackblitz
I'm not supporting codesandbox at this time since it doesn't really support sass and the define API doesn't support devboxes.
1 parent ab8944b commit daad16f

File tree

19 files changed

+613
-164
lines changed

19 files changed

+613
-164
lines changed

apps/docs/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"run-script": "tsx --tsconfig scripts/tsconfig.json",
99
"create-env": "pnpm run run-script scripts/createEnv.ts",
1010
"create-scss-lookup": "pnpm run run-script scripts/createScssLookup.ts",
11+
"create-stackblitz-template": "pnpm run run-script scripts/createStackBlitzTemplate.ts",
1112
"typecheck-src": "tsc --noEmit",
1213
"typecheck-scripts": "tsc --noEmit -P scripts/tsconfig.json",
1314
"typecheck": "npm-run-all typecheck-src typecheck-scripts",
@@ -21,7 +22,7 @@
2122
"watch-demos": "pnpm run run-script scripts/watchDemos.ts",
2223
"watch-scss-lookup": "pnpm create-scss-lookup -- --watch",
2324
"generator-dev": "nodemon --config scripts/nodemon.docs-generator.json",
24-
"build-env": "npm-run-all --parallel create-env sassdoc create-scss-lookup",
25+
"build-env": "npm-run-all --parallel create-env sassdoc create-scss-lookup create-stackblitz-template",
2526
"build-typedoc": "pnpm run run-script scripts/typedoc.ts",
2627
"next-dev": "next dev",
2728
"next-build": "next build",

apps/docs/scripts/constants.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { join } from "node:path";
44
export const GENERATED_FILE_BANNER =
55
"// THIS FILE WAS GENERATED BY A SCRIPT AND SHOULD NOT BE UPDATED MANUALLY";
66

7+
const aliased = (name: string): string => name.replace(/^.+src\//, "@/");
8+
79
export const PROJECT_ROOT = getProjectRootDir();
810
export const CORE_ROOT = join(PROJECT_ROOT, "packages", "core");
911
export const CORE_SRC = join(CORE_ROOT, "src");
@@ -15,7 +17,7 @@ export const GENERATED_DIR = join(
1517
"generated"
1618
);
1719
export const GENERATED_SASSDOC_FILE = join(GENERATED_DIR, "sassdoc.ts");
18-
export const ALIASED_SASSDOC_FILE = GENERATED_SASSDOC_FILE.replace(
19-
/^.+src\//,
20-
"@/"
21-
);
20+
export const ALIASED_SASSDOC_FILE = aliased(GENERATED_SASSDOC_FILE);
21+
22+
export const GENERATED_STACKBLITZ_FILE = join(GENERATED_DIR, "stackblitz.ts");
23+
export const ALIASED_STACKBLITZ_FILE = aliased(GENERATED_SASSDOC_FILE);
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { getProjectRootDir } from "docs-generator/utils/getProjectRootDir";
2+
import { log } from "docs-generator/utils/log";
3+
import { readFileSync, readdirSync, statSync } from "node:fs";
4+
import { writeFile } from "node:fs/promises";
5+
import { basename, join } from "node:path";
6+
import { format } from "prettier";
7+
8+
import packageJson from "../package.json" with { type: "json" };
9+
import {
10+
GENERATED_FILE_BANNER,
11+
GENERATED_STACKBLITZ_FILE,
12+
} from "./constants.js";
13+
import { ensureGeneratedDir } from "./ensureGeneratedDir.js";
14+
15+
function getAllFiles(dir: string): readonly string[] {
16+
const files: string[] = [];
17+
const dirFiles = readdirSync(dir);
18+
dirFiles.forEach((file) => {
19+
if (
20+
/node_modules|(\.git$)|RootLayout|MainNavigation|README|CHANGELOG|(\.(ico|png))/.test(
21+
file
22+
)
23+
) {
24+
return;
25+
}
26+
27+
const fullPath = join(dir, file);
28+
if (statSync(fullPath).isDirectory()) {
29+
files.push(...getAllFiles(fullPath));
30+
} else {
31+
files.push(fullPath);
32+
}
33+
});
34+
35+
return files;
36+
}
37+
38+
interface TemplateHiddenInputProps {
39+
name: string;
40+
value: string;
41+
}
42+
43+
function toTemplateHiddenInputProps(
44+
fullFilePath: string
45+
): TemplateHiddenInputProps {
46+
const srcPath = fullFilePath.replace(/^.+vite-(t|j)s\//, "");
47+
const name = basename(srcPath);
48+
49+
let contents = readFileSync(fullFilePath, "utf8");
50+
if (name === "package.json") {
51+
const parsed = JSON.parse(contents);
52+
// sass-embedded doesn't work here for some reason
53+
parsed.devDependencies.sass = parsed.devDependencies["sass-embedded"];
54+
delete parsed.devDependencies["sass-embedded"];
55+
56+
contents = JSON.stringify(parsed, null, 2);
57+
} else if (name === "index.html") {
58+
contents = contents.replace(
59+
/id="root"/,
60+
`id="root" class="{{CLASS_NAME}}"`
61+
);
62+
}
63+
64+
return {
65+
name: `project[files][${srcPath}]`,
66+
value: contents,
67+
};
68+
}
69+
70+
async function run(): Promise<void> {
71+
await ensureGeneratedDir();
72+
73+
const examplesRoot = join(getProjectRootDir(), "examples");
74+
const typescriptFiles = getAllFiles(join(examplesRoot, "vite-ts"));
75+
const javascriptFiles = getAllFiles(join(examplesRoot, "vite-js"));
76+
77+
const contents = `${GENERATED_FILE_BANNER}
78+
79+
export const JS_STACKBLITZ_TEMPLATE = ${JSON.stringify(javascriptFiles.map((file) => toTemplateHiddenInputProps(file)))}
80+
export const TS_STACKBLITZ_TEMPLATE = ${JSON.stringify(typescriptFiles.map((file) => toTemplateHiddenInputProps(file)))}
81+
82+
export const STACKBLITZ_DEPENDENCIES: Record<string, string> = ${JSON.stringify(
83+
{
84+
...packageJson.dependencies,
85+
...packageJson.devDependencies,
86+
}
87+
)}
88+
`;
89+
90+
await writeFile(
91+
GENERATED_STACKBLITZ_FILE,
92+
await format(contents, { filepath: GENERATED_STACKBLITZ_FILE }),
93+
"utf8"
94+
);
95+
}
96+
97+
await log(run(), "", `Created ${GENERATED_STACKBLITZ_FILE}`);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { existsSync } from "node:fs";
2+
import { mkdir } from "node:fs/promises";
3+
4+
import { GENERATED_DIR } from "./constants.js";
5+
6+
export async function ensureGeneratedDir(): Promise<void> {
7+
if (!existsSync(GENERATED_DIR)) {
8+
await mkdir(GENERATED_DIR, { recursive: true });
9+
}
10+
}

apps/docs/scripts/sassdoc.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { log } from "docs-generator/utils/log";
2-
import { existsSync } from "node:fs";
3-
import { mkdir, writeFile } from "node:fs/promises";
2+
import { writeFile } from "node:fs/promises";
43
import { format } from "prettier";
54
import { type GeneratedSassDocWithOrder, generate } from "sassdoc-generator";
65
import { type FormattedSassDocItem } from "sassdoc-generator/types";
76

87
import {
98
ALIASED_SASSDOC_FILE,
109
CORE_SRC,
11-
GENERATED_DIR,
1210
GENERATED_FILE_BANNER,
1311
GENERATED_SASSDOC_FILE,
1412
} from "./constants.js";
13+
import { ensureGeneratedDir } from "./ensureGeneratedDir.js";
1514

1615
function stringify(
1716
map: ReadonlyMap<string, FormattedSassDocItem>,
@@ -53,10 +52,7 @@ export const SASSDOC_VARIABLES: Record<string, FormattedVariableItem | undefined
5352
}
5453

5554
async function run(): Promise<void> {
56-
if (!existsSync(GENERATED_DIR)) {
57-
await mkdir(GENERATED_DIR, { recursive: true });
58-
}
59-
55+
await ensureGeneratedDir();
6056
await createSassDocFile(await generate({ src: CORE_SRC }));
6157
}
6258

apps/docs/src/app/(main)/(markdown)/(demos)/hooks/use-file-upload/page.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ uploaded to a server.
1616
This example will show how to handle simple validation and preview uploaded
1717
files. Only the `Demo` files are editable.
1818

19-
```demo source="./FileUploadExample.tsx" readOnlyImports={["@/components/FilePreview/FilePreviewCard.jsx", "@/components/FilePreview/SimpleFilePreview.tsx", "@/components/FilePreview/FilePreviewCard.module.scss", "@/components/FileUploadErrorModal/FileUploadErrorModal.jsx", "@/components/FileUploadErrorModal/ErrorRenderer.jsx"]}
19+
```demo source="./FileUploadExample.tsx" readOnlyImports={["@/components/FilePreview/FilePreviewCard.jsx", "@/components/FilePreview/FilePreviewCard.module.scss", "@/components/FilePreview/SimpleFilePreview.jsx", "@/components/FileUploadErrorModal/FileUploadErrorModal.jsx", "@/components/FileUploadErrorModal/ErrorRenderer.jsx", "@/components/FileUploadErrorModal/ErrorHeader.jsx"]}
2020
2121
```
2222

@@ -30,7 +30,7 @@ upload files to the server using this hook.
3030
> !Success! This example also showcases some other UI that can be rendered by
3131
> utilizing the data returned by this hook.
3232
33-
```demo source="./ServerUploadExample.tsx" readOnlyImports={["@/components/FilePreview/FilePreviewCard.jsx", "@/components/FilePreview/SimpleFilePreview.tsx", "@/components/FileUploadErrorModal/FileUploadErrorModal.jsx", "@/components/FileUploadErrorModal/ErrorRenderer.jsx"]}
33+
```demo source="./ServerUploadExample.tsx" readOnlyImports={["@/components/FileUploadErrorModal/FileUploadErrorModal.jsx", "@/components/FileUploadErrorModal/ErrorRenderer.jsx", "@/components/FileUploadErrorModal/ErrorHeader.jsx"]}
3434
3535
```
3636

apps/docs/src/components/DemoCode/DemoCodeEditor.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { Slide } from "@react-md/core/transition/Slide";
2121
import { useToggle } from "@react-md/core/useToggle";
2222
import EditOffIcon from "@react-md/material-icons/EditOffIcon";
2323
import RefreshOutlinedIcon from "@react-md/material-icons/RefreshOutlinedIcon";
24-
import { type ReactElement, useMemo, useRef } from "react";
24+
import { type ReactElement, type ReactNode, useMemo, useRef } from "react";
2525

2626
import { GithubLink } from "../GithubLink.jsx";
2727
import { ConfigureTypescriptEnabled } from "../MainLayout/ConfigureTypescriptEnabled.jsx";
@@ -44,6 +44,7 @@ export interface DemoCodeEditorProps extends PreviewContainerOptions {
4444
startOnScss?: boolean;
4545
readOnlyFiles?: readonly ReactElement[];
4646
readOnlyFileNames?: readonly string[];
47+
beforeSourceLinkChildren?: ReactNode;
4748
}
4849

4950
export function DemoCodeEditor(props: DemoCodeEditorProps): ReactElement {
@@ -60,6 +61,7 @@ export function DemoCodeEditor(props: DemoCodeEditorProps): ReactElement {
6061
scssCodeFile,
6162
forceDarkMode,
6263
disablePadding,
64+
beforeSourceLinkChildren,
6365
readOnlyFiles = [],
6466
readOnlyFileNames = [],
6567
} = props;
@@ -125,6 +127,7 @@ export function DemoCodeEditor(props: DemoCodeEditorProps): ReactElement {
125127
</Chip>
126128
)}
127129
<Box className={styles.end} disablePadding disableWrap>
130+
{beforeSourceLinkChildren}
128131
<GithubLink
129132
file={source}
130133
iconSize="small"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { type ReadonlyCodeFile } from "@react-md/code/types";
2+
import { type ReactElement } from "react";
3+
4+
import { MarkdownCode } from "../MarkdownCode.jsx";
5+
import { TypescriptCodeBlock } from "../TypescriptCodeBlock.jsx";
6+
7+
export interface ReadOnlyFileProps {
8+
file: ReadonlyCodeFile;
9+
}
10+
11+
export function ReadOnlyFile(props: ReadOnlyFileProps): ReactElement {
12+
const { file } = props;
13+
if ("compiled" in file) {
14+
return (
15+
<TypescriptCodeBlock
16+
isTsx={file.name.endsWith(".tsx")}
17+
tsCode={file.code}
18+
jsCode={file.compiled}
19+
disableAppBar
20+
/>
21+
);
22+
}
23+
24+
return <MarkdownCode language={file.lang}>{file.code}</MarkdownCode>;
25+
}

apps/docs/src/components/FileUploadErrorModal/ErrorHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface ErrorHeaderProps {
1313
error: TooManyFilesError | FileSizeError | FileExtensionError;
1414
}
1515

16-
export default function ErrorHeader({ error }: ErrorHeaderProps): ReactElement {
16+
export function ErrorHeader({ error }: ErrorHeaderProps): ReactElement {
1717
if (isFileSizeError(error)) {
1818
const { type } = error;
1919
const limit = filesize(error.limit);

apps/docs/src/components/FileUploadErrorModal/ErrorRenderer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Typography } from "@react-md/core/typography/Typography";
77
import { filesize } from "filesize";
88
import { Fragment, type ReactElement } from "react";
99

10-
import ErrorHeader from "./ErrorHeader.jsx";
10+
import { ErrorHeader } from "./ErrorHeader.jsx";
1111

1212
export interface ErrorRendererProps {
1313
error: FileValidationError<never>;

0 commit comments

Comments
 (0)