From 76439cd0044d1ae633689192e25f10aafafe4800 Mon Sep 17 00:00:00 2001 From: "moxey.eth" Date: Mon, 30 Oct 2023 17:32:26 +1100 Subject: [PATCH] feat: steps --- biome.json | 3 +- pnpm-lock.yaml | 3 + site/docs/index.mdx | 14 +++-- site/docs/kitchen-sink/index.mdx | 26 ++++++++ site/package.json | 8 ++- src/app/components/Step.css.ts | 100 ++++++++++++++++++++++++++++++ src/app/components/Step.tsx | 34 ++++++++++ src/app/components/Steps.css.ts | 16 +++++ src/app/components/Steps.tsx | 13 ++++ src/app/components/mdx/Div.tsx | 2 + src/app/components/mdx/Pre.css.ts | 14 +++-- src/app/components/mdx/Steps.tsx | 24 +++++++ src/app/styles/vars.css.ts | 4 +- src/package.json | 3 +- src/react.ts | 3 + src/vite/plugins/mdx.ts | 2 + src/vite/plugins/remark/code.ts | 2 +- src/vite/plugins/remark/steps.ts | 50 +++++++++++++++ 18 files changed, 305 insertions(+), 16 deletions(-) create mode 100644 src/app/components/Step.css.ts create mode 100644 src/app/components/Step.tsx create mode 100644 src/app/components/Steps.css.ts create mode 100644 src/app/components/Steps.tsx create mode 100644 src/app/components/mdx/Steps.tsx create mode 100644 src/react.ts create mode 100644 src/vite/plugins/remark/steps.ts diff --git a/biome.json b/biome.json index e15beb58..4ddf0413 100644 --- a/biome.json +++ b/biome.json @@ -18,7 +18,8 @@ }, "suspicious": { "noAssignInExpressions": "off", - "noExplicitAny": "off" + "noExplicitAny": "off", + "noArrayIndexKey": "off" } } }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8db251c..5493c588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,6 +114,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + vocs: + specifier: workspace:* + version: link:../src src: dependencies: diff --git a/site/docs/index.mdx b/site/docs/index.mdx index cbaddc46..ff4b28e0 100644 --- a/site/docs/index.mdx +++ b/site/docs/index.mdx @@ -34,7 +34,9 @@ bun x vocs init -i bun You can install Vocs in an existing project, or start from scratch, by installing Vocs as a dependency. -### 1. Install +::::steps + +### Install First, we will install `vocs` as a dependency in our project. @@ -58,7 +60,7 @@ bun i vocs ::: -### 2. Add Scripts to `package.json` +### Add Scripts to `package.json` After that, let's add some scripts to our `package.json` for Vocs. @@ -74,7 +76,7 @@ After that, let's add some scripts to our `package.json` for Vocs. } ``` -### 3. Build your First Page +### Build your First Page Create a directory called `docs`, and add a file inside of it called `index.mdx`: @@ -84,7 +86,7 @@ Create a directory called `docs`, and add a file inside of it called `index.mdx` Welcome to my docs. ``` -### 4. Run +### Run Next, run the development server: @@ -92,4 +94,6 @@ Next, run the development server: npm run dev ``` -Then open up your browser to `http://localhost:5173`, and you can see your first page! \ No newline at end of file +Then open up your browser to `http://localhost:5173`, and you can see your first page! + +:::: \ No newline at end of file diff --git a/site/docs/kitchen-sink/index.mdx b/site/docs/kitchen-sink/index.mdx index 2fdf3685..3ad4a3bc 100644 --- a/site/docs/kitchen-sink/index.mdx +++ b/site/docs/kitchen-sink/index.mdx @@ -245,6 +245,32 @@ bun i viem ::: +## Steps + +::::steps +### Step one + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vestibulum ante non neque convallis tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam a iaculis libero. + +### Step two + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vestibulum ante non neque convallis tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam a iaculis libero. + +:::code-group +```tsx [console.log] +console.log('hello world') +``` + +```tsx [alert] +alert('hello world') +``` +::: + +### Step three + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vestibulum ante non neque convallis tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam a iaculis libero. +:::: + ## Blockquote > Blockquotes are very handy in email to emulate reply text. diff --git a/site/package.json b/site/package.json index f9acdd72..8b282f66 100644 --- a/site/package.json +++ b/site/package.json @@ -5,10 +5,14 @@ "scripts": { "dev": "node --loader @swc-node/register/esm ../src/cli/index.ts dev", "build": "NODE_ENV=production node --loader @swc-node/register/esm ../src/cli/index.ts build", - "preview": "node --loader @swc-node/register/esm ../src/cli/index.ts preview" + "preview": "node --loader @swc-node/register/esm ../src/cli/index.ts preview", + "dist:dev": "vocs dev", + "dist:build": "vocs build", + "dist:preview": "vocs preview" }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "vocs": "workspace:*" } } diff --git a/src/app/components/Step.css.ts b/src/app/components/Step.css.ts new file mode 100644 index 00000000..337e4b7c --- /dev/null +++ b/src/app/components/Step.css.ts @@ -0,0 +1,100 @@ +import { globalStyle, style } from '@vanilla-extract/css' + +import { fontWeightVars, primitiveColorVars, spaceVars, viewportVars } from '../styles/vars.css.js' +import { root as H2 } from './mdx/H2.css.js' +import { root as H3 } from './mdx/H3.css.js' +import { root as H4 } from './mdx/H4.css.js' +import { root as H5 } from './mdx/H5.css.js' +import { root as H6 } from './mdx/H6.css.js' +import { root as CodeGroup, tabsList } from './mdx/CodeGroup.css.js' + +export const root = style({ + selectors: { + '&:not(:last-child)': { + marginBottom: spaceVars['24'], + }, + }, +}) + +export const title = style( + { + marginBottom: spaceVars['8'], + position: 'relative', + '::before': { + alignItems: 'center', + backgroundColor: primitiveColorVars.background4, + borderRadius: '100%', + border: `0.5em solid ${primitiveColorVars.background}`, + boxSizing: 'content-box', + color: primitiveColorVars.text2, + content: 'counter(step)', + counterIncrement: 'step', + display: 'flex', + fontSize: '0.625em', + fontWeight: fontWeightVars.regular, + height: '2em', + justifyContent: 'center', + left: 'calc(-25.125px - 1.45em)', + position: 'absolute', + top: '-0.5em', + width: '2em', + }, + }, + 'title', +) + +export const content = style( + { + selectors: { + [`${H2}+&,${H3}+&,${H4}+&,${H5}+&,${H6}+&`]: { + marginTop: `calc(${spaceVars['8']} * -1)`, + }, + }, + }, + 'content', +) + +globalStyle(`${content} > *:not(:last-child)`, { + marginBottom: spaceVars['16'], +}) + +globalStyle(`${content} > *:last-child`, { + marginBottom: spaceVars['0'], +}) + +globalStyle(`${content} [data-rehype-pretty-code-fragment]`, { + '@media': { + [viewportVars['max-720px']]: { + borderTop: `6px solid ${primitiveColorVars.background}`, + borderBottom: `6px solid ${primitiveColorVars.background}`, + marginLeft: `calc(-1 * ${spaceVars['28']} - 2px)`, + }, + }, +}) + +globalStyle(`${content} ${CodeGroup}`, { + '@media': { + [viewportVars['max-720px']]: { + marginLeft: 0, + marginRight: 0, + }, + }, +}) + +globalStyle(`${content} ${tabsList}`, { + '@media': { + [viewportVars['max-720px']]: { + borderTop: `6px solid ${primitiveColorVars.background}`, + marginLeft: `calc(-1 * ${spaceVars['44']})`, + marginRight: `calc(-1 * ${spaceVars['16']})`, + }, + }, +}) + +globalStyle(`${content} ${CodeGroup} [data-rehype-pretty-code-fragment]`, { + '@media': { + [viewportVars['max-720px']]: { + borderTop: 'none', + }, + }, +}) diff --git a/src/app/components/Step.tsx b/src/app/components/Step.tsx new file mode 100644 index 00000000..1bf00cee --- /dev/null +++ b/src/app/components/Step.tsx @@ -0,0 +1,34 @@ +import { type ClassValue, clsx } from 'clsx' +import type { ReactNode } from 'react' + +import * as styles from './Step.css.js' +import { H2 } from './mdx/H2.js' +import { H3 } from './mdx/H3.js' +import { H4 } from './mdx/H4.js' +import { H5 } from './mdx/H5.js' +import { H6 } from './mdx/H6.js' + +export type StepProps = { + children: ReactNode + className?: ClassValue + title: ReactNode | string + titleLevel?: 2 | 3 | 4 | 5 | 6 +} + +export function Step({ children, className, title, titleLevel = 2 }: StepProps) { + const Element = (() => { + if (titleLevel === 2) return H2 + if (titleLevel === 3) return H3 + if (titleLevel === 4) return H4 + if (titleLevel === 5) return H5 + if (titleLevel === 6) return H6 + throw new Error('Invalid.') + })() + + return ( +
+ {title} +
{children}
+
+ ) +} diff --git a/src/app/components/Steps.css.ts b/src/app/components/Steps.css.ts new file mode 100644 index 00000000..9b5994a7 --- /dev/null +++ b/src/app/components/Steps.css.ts @@ -0,0 +1,16 @@ +import { style } from '@vanilla-extract/css' + +import { primitiveColorVars, spaceVars, viewportVars } from '../styles/vars.css.js' + +export const root = style({ + borderLeft: `1.5px solid ${primitiveColorVars.border}`, + counterReset: 'step', + paddingLeft: spaceVars['24'], + marginLeft: spaceVars['12'], + marginTop: spaceVars['24'], + '@media': { + [viewportVars['max-720px']]: { + marginLeft: spaceVars['4'], + }, + }, +}) diff --git a/src/app/components/Steps.tsx b/src/app/components/Steps.tsx new file mode 100644 index 00000000..8fd8d92c --- /dev/null +++ b/src/app/components/Steps.tsx @@ -0,0 +1,13 @@ +import { type ClassValue, clsx } from 'clsx' +import type { ReactNode } from 'react' + +import * as styles from './Steps.css.js' + +export type StepsProps = { + children: ReactNode + className?: ClassValue +} + +export function Steps({ children, className }: StepsProps) { + return
{children}
+} diff --git a/src/app/components/mdx/Div.tsx b/src/app/components/mdx/Div.tsx index 34794de0..32b816a4 100644 --- a/src/app/components/mdx/Div.tsx +++ b/src/app/components/mdx/Div.tsx @@ -6,6 +6,7 @@ import { CodeGroup } from './CodeGroup.js' import { CodeTitle } from './CodeTitle.js' import * as styles from './Div.css.js' import { Subtitle } from './Subtitle.js' +import { Steps } from './Steps.js' export function Div(props: DetailedHTMLProps, HTMLDivElement>) { const className = clsx(props.className, styles.root) @@ -15,6 +16,7 @@ export function Div(props: DetailedHTMLProps, HTM return if ('data-rehype-pretty-code-fragment' in props) return + if ('data-vocs-steps' in props) return if (props.role === 'doc-subtitle') return return
} diff --git a/src/app/components/mdx/Pre.css.ts b/src/app/components/mdx/Pre.css.ts index c781719e..de1b3987 100644 --- a/src/app/components/mdx/Pre.css.ts +++ b/src/app/components/mdx/Pre.css.ts @@ -2,11 +2,15 @@ import { globalStyle, style } from '@vanilla-extract/css' import { spaceVars, viewportVars } from '../../styles/vars.css.js' export const root = style({ - '@media': { - [viewportVars['max-720px']]: { - borderRadius: 0, - marginLeft: `calc(-1 * ${spaceVars['16']})`, - marginRight: `calc(-1 * ${spaceVars['16']})`, + selectors: { + '&&': { + '@media': { + [viewportVars['max-720px']]: { + borderRadius: 0, + marginLeft: `calc(-1 * ${spaceVars['16']})`, + marginRight: `calc(-1 * ${spaceVars['16']})`, + }, + }, }, }, }) diff --git a/src/app/components/mdx/Steps.tsx b/src/app/components/mdx/Steps.tsx new file mode 100644 index 00000000..257eb6f8 --- /dev/null +++ b/src/app/components/mdx/Steps.tsx @@ -0,0 +1,24 @@ +import type { ReactNode } from 'react' + +import { Steps as Steps_ } from '../Steps.js' +import { Step } from '../Step.js' + +export function Steps({ children }: { children: ReactNode }) { + if (!Array.isArray(children)) return null + return ( + + {children.map(({ props }, i) => { + const [title, ...children] = props.children + return ( + + {children} + + ) + })} + + ) +} diff --git a/src/app/styles/vars.css.ts b/src/app/styles/vars.css.ts index 838e91ad..19539837 100644 --- a/src/app/styles/vars.css.ts +++ b/src/app/styles/vars.css.ts @@ -92,7 +92,7 @@ createGlobalTheme(':root.dark', primitiveColorVars, { backgroundIrisTint: globalColors.irisA.irisA4, backgroundRedTint: globalColors.redA.redA3, backgroundYellowTint: globalColors.yellowA.yellowA2, - border: globalColors.mauveDark.mauve5, + border: globalColors.mauveDark.mauve6, border2: globalColors.mauveDark.mauve9, borderAccent: globalColors.amberDark.amber11, borderBlue: globalColors.blueA.blueA4, @@ -322,6 +322,7 @@ export const spaceVars = createGlobalThemeContract( '28': '28', '32': '32', '40': '40', + '44': '44', '48': '48', '56': '56', '64': '64', @@ -348,6 +349,7 @@ createGlobalTheme(':root', spaceVars, { '28': '1.75rem', '32': '2rem', '40': '2.5rem', + '44': '2.75rem', '48': '3rem', '56': '3.5rem', '64': '4rem', diff --git a/src/package.json b/src/package.json index 379639d3..cad06ad3 100644 --- a/src/package.json +++ b/src/package.json @@ -9,7 +9,8 @@ "exports": { ".": "./_lib/index.js", "./head": "./_lib/head.js", - "./internal": "./_lib/internal.js" + "./internal": "./_lib/internal.js", + "./react": "./_lib/react.js" }, "peerDependencies": { "react": "^18.2.0", diff --git a/src/react.ts b/src/react.ts new file mode 100644 index 00000000..34f713b7 --- /dev/null +++ b/src/react.ts @@ -0,0 +1,3 @@ +export { Callout, type CalloutProps } from './app/components/Callout.js' +export { Steps, type StepsProps } from './app/components/Steps.js' +export { Step, type StepProps } from './app/components/Step.js' diff --git a/src/vite/plugins/mdx.ts b/src/vite/plugins/mdx.ts index 024d0c66..e8bd7025 100644 --- a/src/vite/plugins/mdx.ts +++ b/src/vite/plugins/mdx.ts @@ -16,6 +16,7 @@ import { remarkCallout } from './remark/callout.js' import { remarkCodeGroup } from './remark/code-group.js' import { remarkCode } from './remark/code.js' import { remarkInferFrontmatter } from './remark/inferred-frontmatter.js' +import { remarkSteps } from './remark/steps.js' import { remarkStrongBlock } from './remark/strong-block.js' import { remarkSubheading } from './remark/subheading.js' @@ -30,6 +31,7 @@ export function mdx() { remarkCallout, remarkCode, remarkCodeGroup, + remarkSteps, remarkStrongBlock, remarkSubheading, ], diff --git a/src/vite/plugins/remark/code.ts b/src/vite/plugins/remark/code.ts index 0d5a5d0a..70b65d90 100644 --- a/src/vite/plugins/remark/code.ts +++ b/src/vite/plugins/remark/code.ts @@ -8,7 +8,7 @@ export function remarkCode() { return (tree: Root) => { visit(tree, (node, _, parent) => { if (node.type !== 'code') return - if (parent?.type === 'containerDirective') return + if (parent?.type === 'containerDirective' && parent.name !== 'steps') return const [match, title] = node.meta?.match(/\[(.*)\]/) || [] if (match) node.meta = node.meta?.replace(match, `title=\"${title}\"`) diff --git a/src/vite/plugins/remark/steps.ts b/src/vite/plugins/remark/steps.ts new file mode 100644 index 00000000..f9d65aaa --- /dev/null +++ b/src/vite/plugins/remark/steps.ts @@ -0,0 +1,50 @@ +/// +/// + +import { h } from 'hastscript' +import type { Heading, Root } from 'mdast' +import { visit } from 'unist-util-visit' + +export function remarkSteps() { + return (tree: Root) => { + visit(tree, (node) => { + if (node.type !== 'containerDirective') return + if (node.name !== 'steps') return + + const data = node.data || (node.data = {}) + const tagName = 'div' + + node.attributes = { + ...node.attributes, + 'data-vocs-steps': 'true', + } + + data.hName = tagName + data.hProperties = h(tagName, node.attributes || {}).properties + + const depth = (node.children.find((child) => child.type === 'heading') as Heading)?.depth ?? 2 + + let currentChild + const children = [] + for (const child of node.children) { + if (child.type === 'heading' && child.depth === depth) { + if (currentChild && currentChild.children.length > 0) children.push(currentChild) + currentChild = { + type: 'paragraph', + children: [], + data: { + hName: 'div', + hProperties: { + 'data-depth': depth, + }, + }, + } as any + } + currentChild!.children.push(child) + } + children.push(currentChild) + + node.children = children + }) + } +}