diff --git a/.circleci/config.yml b/.circleci/config.yml index 90303124f..5e639e46d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -56,6 +56,12 @@ jobs: steps: - checkout - install_js + - run: + name: Docs Infra Build + command: pnpm -F './packages/docs-infra' run build + - run: + name: ESLint + command: pnpm eslint:ci - run: name: ESLint command: pnpm eslint:ci diff --git a/.lintignore b/.lintignore index 2e75f5d40..a5e0b5c27 100644 --- a/.lintignore +++ b/.lintignore @@ -5,5 +5,7 @@ apps/tools-public/toolpad/**/*.yml build node_modules pnpm-lock.yaml +.next +docs/out __fixtures__ diff --git a/README.md b/README.md index 2b648b917..e2b98b835 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ Mono-repository for the MUI organization with code that can be public. See https://github.com/mui/mui-private for code that needs to be private. +## Documentation + +You can [read the Infra documentation here](./docs/README.md). + ## Applications ### [tools-public.mui.com](https://tools-public.mui.com/) @@ -27,6 +31,11 @@ Internal public Toolpad apps that run the operations of MUI, built using https:/ - Folder: `/packages/docs-infra/` - [Docs](./packages/docs-infra/README.md) +### [code-infra](./packages/code-infra/) + +- Folder: `/packages/code-infra/` +- [Docs](./packages/code-infra/README.md) + ## Versioning Steps: diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..5ef6a5207 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..e099e0dec --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +# MUI Infra Docs + +This package contains the documentation for the MUI Infra project, which is responsible for the infrastructure and tooling used in the various MUI documentation sites and libraries. + +[Read in Markdown](<./app/(shared)/page.mdx>) +[Read in Browser](https://infra.mui.com) + +For the most immersive experience, we recommend opening this documentation in VSCode, starting with this README and working deeper into the documentation by navigating through the links provided (ctrl + click). You should have [the MDX extension](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx) installed to view the documentation properly. + +To see demos live, run `pnpm run dev` and open diff --git a/docs/app/(shared)/layout.tsx b/docs/app/(shared)/layout.tsx new file mode 100644 index 000000000..6470235bd --- /dev/null +++ b/docs/app/(shared)/layout.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import type { Metadata } from 'next'; +import styles from '../layout.module.css'; + +export const metadata: Metadata = { + title: 'MUI Infra Documentation', + description: 'How to use the MUI Infra packages', +}; + +export default function Layout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+
{children}
+
+ ); +} diff --git a/docs/app/(shared)/page.mdx b/docs/app/(shared)/page.mdx new file mode 100644 index 000000000..40acd7398 --- /dev/null +++ b/docs/app/(shared)/page.mdx @@ -0,0 +1,9 @@ +# MUI Infra + +## Docs Infra + +Read more about `docs-infra` [here](../docs-infra/page.mdx). + +## Code Infra + +Read more about `code-infra` [here](../code-infra/page.mdx). diff --git a/docs/app/code-infra/layout.tsx b/docs/app/code-infra/layout.tsx new file mode 100644 index 000000000..fe4d94238 --- /dev/null +++ b/docs/app/code-infra/layout.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import type { Metadata } from 'next'; +import Link from 'next/link'; +import styles from '../layout.module.css'; + +export const metadata: Metadata = { + title: 'MUI Code Infra Documentation', + description: 'How to use the MUI Code-Infra package', +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+
+ MUI Code Infra +
+
{children}
+
+ ); +} diff --git a/docs/app/code-infra/page.mdx b/docs/app/code-infra/page.mdx new file mode 100644 index 000000000..8dc058d18 --- /dev/null +++ b/docs/app/code-infra/page.mdx @@ -0,0 +1,21 @@ +# Code Infra + +This is the documentation for the MUI Internal Code Infra package. + +You can install this package using: + +```bash variant=pnpm +pnpm install @mui/internal-code-infra +``` + +```bash variant=yarn +yarn add @mui/internal-code-infra +``` + +```bash variant=npm +npm install @mui/internal-code-infra +``` + +## Usage + +TODO diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeController.tsx b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeController.tsx new file mode 100644 index 000000000..bb2436740 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeController.tsx @@ -0,0 +1,15 @@ +'use client'; + +import * as React from 'react'; +import { CodeControllerContext } from '@mui/internal-docs-infra/CodeControllerContext'; +import type { ControlledCode } from '@mui/internal-docs-infra/CodeHighlighter/types'; + +export function CodeController({ children }: { children: React.ReactNode }) { + const [code, setCode] = React.useState(undefined); + + const contextValue = React.useMemo(() => ({ code, setCode }), [code, setCode]); + + return ( + {children} + ); +} diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditor.tsx b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditor.tsx new file mode 100644 index 000000000..d3dbee261 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditor.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter'; +import { createParseSource } from '@mui/internal-docs-infra/pipeline/parseSource'; + +import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider'; +import { CodeController } from './CodeController'; +import { CodeEditorContent } from './CodeEditorContent'; + +const initialCode = { + Default: { + url: 'file://live-example.js', + fileName: 'live-example.js', + source: `// Welcome to the live code editor! +function greet(name) { + return \`Hello, \${name}!\`; +} +`, + }, +}; + +export function CodeEditor() { + return ( + + + + + + ); +} diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.module.css b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.module.css new file mode 100644 index 000000000..26263573a --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.module.css @@ -0,0 +1,37 @@ +.container { + border: 1px solid #d0cdd7; + border-radius: 8px; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 12px; + border-bottom: 1px solid #d0cdd7; +} + +.name { + color: #65636d; + font-size: 13px; + font-family: var(--font-geist-mono); +} + +.headerActions { + display: flex; + align-items: center; + gap: 8px; +} + +.switchContainer { + display: flex; +} +.code { + padding: 6px; +} + +.codeBlock { + margin: 0; + padding: 6px; + overflow-x: auto; +} diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx new file mode 100644 index 000000000..2f9972650 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx @@ -0,0 +1,50 @@ +'use client'; + +import * as React from 'react'; +import { useEditable } from 'use-editable'; +import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types'; +import { useCode } from '@mui/internal-docs-infra/useCode'; +import { LabeledSwitch } from '@/components/LabeledSwitch'; +import styles from './CodeEditorContent.module.css'; + +import '@wooorm/starry-night/style/light'; // load the light theme for syntax highlighting + +export function CodeEditorContent(props: ContentProps) { + const preRef = React.useRef(null); + const code = useCode(props, { preClassName: styles.codeBlock, preRef }); + + const hasJsTransform = code.availableTransforms.includes('js'); + const isJsSelected = code.selectedTransform === 'js'; + const labels = { false: 'TS', true: 'JS' }; + const toggleJs = React.useCallback( + (checked: boolean) => { + code.selectTransform(checked ? 'js' : null); + }, + [code], + ); + + const onInput = React.useCallback( + (text: string) => { + code.setSource?.(text); + }, + [code], + ); + + useEditable(preRef, onInput, { indentation: 2 }); + + return ( +
+
+ {code.selectedFileName} +
+ {hasJsTransform && ( +
+ +
+ )} +
+
+
{code.selectedFile}
+
+ ); +} diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/index.ts b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/index.ts new file mode 100644 index 000000000..be190b299 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/index.ts @@ -0,0 +1,7 @@ +import { createDemo } from '@/functions/createDemo'; +import { CodeEditor } from './CodeEditor'; + +export const DemoCodeControllerCodeEditor = createDemo(import.meta.url, CodeEditor, { + name: 'Live Code Editor', + slug: 'live-code-editor', +}); diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoController.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoController.tsx new file mode 100644 index 000000000..b42c1ca94 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoController.tsx @@ -0,0 +1,59 @@ +'use client'; + +import * as React from 'react'; +import { useRunner } from 'react-runner'; +import { CodeControllerContext } from '@mui/internal-docs-infra/CodeControllerContext'; +import type { ControlledCode } from '@mui/internal-docs-infra/CodeHighlighter/types'; +import { useCodeExternals } from '@mui/internal-docs-infra/CodeExternalsContext'; + +function Runner({ code }: { code: string }) { + const externalsContext = useCodeExternals(); + const scope = React.useMemo(() => { + let externals = externalsContext?.externals; + if (!externals) { + externals = { imports: { react: React } }; + } + + return { import: { ...externals } }; + }, [externalsContext]); + + const { element, error } = useRunner({ code, scope }); + + if (error) { + return
{error}
; + } + + return element; +} + +export function DemoController({ children }: { children: React.ReactNode }) { + const [code, setCode] = React.useState(undefined); + + const components = React.useMemo( + () => + code + ? Object.keys(code).reduce( + (acc, cur) => { + const source = code[cur]?.source; + if (!source) { + return acc; + } + + acc[cur] = ; + return acc; + }, + {} as Record, + ) + : undefined, + [code], + ); + + const contextValue = React.useMemo( + () => ({ code, setCode, components }), + [code, setCode, components], + ); + + return ( + {children} + ); +} diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx new file mode 100644 index 000000000..e634b8568 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider'; +import { DemoController } from './DemoController'; +import { DemoCheckboxBasic } from './demo-basic'; + +export function DemoLive() { + return ( + + + + + + ); +} diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css new file mode 100644 index 000000000..dfc66f576 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css @@ -0,0 +1,72 @@ +.container { + border: 1px solid #d0cdd7; + border-radius: 8px; +} + +.demoSection { + padding: 24px; +} + +.codeSection { + border-top: 1px solid #d0cdd7; +} + +.header { + border-bottom: 1px solid #d0cdd7; + height: 48px; + position: relative; +} + +.headerContainer { + position: absolute; + width: 100%; + display: flex; + justify-content: space-between; + gap: 8px; +} + +.headerContainer:before { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 1px; + margin: -1px; + background-color: #d0cdd7; +} + +.headerActions { + display: flex; + align-items: center; + gap: 8px; + padding-right: 8px; + height: 48px; +} + +.tabContainer { + display: flex; + align-items: center; + gap: 8px; + margin-left: -1px; + padding-bottom: 2px; + overflow-x: auto; +} + +.switchContainer { + display: flex; +} + +.switchContainerHidden { + display: none; +} + +.code { + padding: 10px 6px; +} + +.codeBlock { + margin: 0; + padding: 6px; + overflow-x: auto; +} diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx new file mode 100644 index 000000000..167c91925 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx @@ -0,0 +1,88 @@ +'use client'; + +import * as React from 'react'; +import { useEditable } from 'use-editable'; +import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types'; +import { useDemo } from '@mui/internal-docs-infra/useDemo'; +import { LabeledSwitch } from '@/components/LabeledSwitch'; +import { Tabs } from '@/components/Tabs'; +import { Select } from '@/components/Select'; +import styles from './DemoLiveContent.module.css'; + +import '@wooorm/starry-night/style/light'; + +const variantNames: Record = { + CssModules: 'CSS Modules', +}; + +export function DemoLiveContent(props: ContentProps) { + const preRef = React.useRef(null); + const demo = useDemo(props, { preClassName: styles.codeBlock, preRef }); + + const hasJsTransform = demo.availableTransforms.includes('js'); + const isJsSelected = demo.selectedTransform === 'js'; + + const labels = { false: 'TS', true: 'JS' }; + const toggleJs = React.useCallback( + (checked: boolean) => { + demo.selectTransform(checked ? 'js' : null); + }, + [demo], + ); + + const tabs = React.useMemo( + () => demo.files.map(({ name }) => ({ id: name, name })), + [demo.files], + ); + const variants = React.useMemo( + () => + demo.variants.map((variant) => ({ value: variant, label: variantNames[variant] || variant })), + [demo.variants], + ); + + const onChange = React.useCallback( + (text: string) => { + demo.setSource?.(text); + }, + [demo], + ); + useEditable(preRef, onChange, { indentation: 2, disabled: !demo.setSource }); + + return ( +
+
{demo.component}
+
+
+
+
+ +
+
+ {demo.variants.length > 1 && ( + demo.selectVariant(e.target.value)}> + {demo.variants.map((variant) => ( + + ))} + + +