From af439e76f9559db9118e7523cf8c0ec80a532351 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Wed, 24 Jun 2026 01:36:23 +0200 Subject: [PATCH 1/5] feat(docs): add live @workflowbuilder/ui component gallery --- apps/docs/astro.config.mjs | 16 +- apps/docs/package.json | 7 + .../ui-showcase/ui-showcase.module.css | 51 ++++ .../components/ui-showcase/ui-showcase.tsx | 216 +++++++++++++ .../content/docs/ui-library/components.mdx | 13 + .../src/content/docs/ui-library/overview.mdx | 59 ++++ pnpm-lock.yaml | 285 ++++++++++++++++++ 7 files changed, 646 insertions(+), 1 deletion(-) create mode 100644 apps/docs/src/components/ui-showcase/ui-showcase.module.css create mode 100644 apps/docs/src/components/ui-showcase/ui-showcase.tsx create mode 100644 apps/docs/src/content/docs/ui-library/components.mdx create mode 100644 apps/docs/src/content/docs/ui-library/overview.mdx diff --git a/apps/docs/astro.config.mjs b/apps/docs/astro.config.mjs index 599ee9638..a170e3c8f 100644 --- a/apps/docs/astro.config.mjs +++ b/apps/docs/astro.config.mjs @@ -1,6 +1,7 @@ import { copyFileSync, mkdirSync } from 'node:fs'; import path from 'node:path'; +import react from '@astrojs/react'; import starlight from '@astrojs/starlight'; import umami from '@yeskunall/astro-umami'; import { defineConfig, passthroughImageService } from 'astro/config'; @@ -70,6 +71,7 @@ export default defineConfig({ }, integrations: [ icon(), + react(), umami({ id: UMAMI_WEBSITE_ID }), starlight({ plugins: [ @@ -113,7 +115,12 @@ export default defineConfig({ }, }), ], - customCss: ['./src/styles/custom.css'], + // `@workflowbuilder/ui` styles are safe to load globally: styles.css is + // just the @layer order + one :root var + opt-in `.ax-public-*` typography + // classes (no global reset), and tokens.css only defines `--ax-*` custom + // properties keyed on `html[data-theme]` — which Starlight already toggles, + // so the live component showcases follow the docs light/dark theme. + customCss: ['./src/styles/custom.css', '@workflowbuilder/ui/styles.css', '@workflowbuilder/ui/tokens.css'], components: { Head: './src/components/head.astro', Search: './src/components/search.astro', @@ -159,6 +166,13 @@ export default defineConfig({ { label: 'Node Schemas', autogenerate: { directory: 'node-schemas' } }, { label: 'Built-in Nodes', autogenerate: { directory: 'nodes' } }, { label: 'Plugins', autogenerate: { directory: 'plugins' } }, + { + label: 'UI Library', + items: [ + { label: 'Overview', link: '/ui-library/overview/' }, + { label: 'Components', link: '/ui-library/components/' }, + ], + }, // API Reference — pages auto-generated by `starlight-typedoc` from // packages/sdk's barrel into `src/content/docs/api//`. // Folder names match the `@category` tag in source TSDoc verbatim. diff --git a/apps/docs/package.json b/apps/docs/package.json index 9fe55bfe4..e5d7b9259 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -14,18 +14,25 @@ "lint:fix": "eslint . --fix" }, "dependencies": { + "@astrojs/react": "^4.4.0", "@astrojs/starlight": "^0.37.6", + "@base-ui/react": "catalog:", "@iconify-json/ph": "^1.2.2", "@workflowbuilder/sdk": "workspace:*", + "@workflowbuilder/ui": "workspace:*", "@yeskunall/astro-umami": "^0.0.7", "astro": "^5.18.0", "astro-icon": "^1.1.5", "marked": "^15.0.0", + "react": "catalog:", + "react-dom": "catalog:", "rehype-external-links": "^3.0.0", "starlight-image-zoom": "^0.13.2" }, "devDependencies": { "@astrojs/check": "^0.9.6", + "@types/react": "catalog:", + "@types/react-dom": "^19.1.0", "eslint-plugin-astro": "^1.3.1", "starlight-typedoc": "^0.21.3", "typedoc": "^0.28.9", diff --git a/apps/docs/src/components/ui-showcase/ui-showcase.module.css b/apps/docs/src/components/ui-showcase/ui-showcase.module.css new file mode 100644 index 000000000..9b552e96f --- /dev/null +++ b/apps/docs/src/components/ui-showcase/ui-showcase.module.css @@ -0,0 +1,51 @@ +.showcase { + display: flex; + flex-direction: column; + gap: 2rem; + margin-top: 1rem; +} + +.section { + display: flex; + flex-direction: column; + gap: 0.85rem; +} + +.title { + margin: 0; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--sl-color-gray-3); +} + +.card { + display: flex; + flex-wrap: wrap; + gap: 1rem 1.5rem; + align-items: center; + padding: 1.25rem; + border: 1px solid var(--sl-color-gray-5); + border-radius: 0.5rem; + background: var(--sl-color-black); +} + +.stack { + display: flex; + flex-direction: column; + gap: 0.75rem; + min-width: 14rem; + max-width: 22rem; +} + +.field { + display: flex; + flex-direction: column; + gap: 0.35rem; +} + +.fieldLabel { + font-size: 0.8125rem; + color: var(--sl-color-gray-2); +} diff --git a/apps/docs/src/components/ui-showcase/ui-showcase.tsx b/apps/docs/src/components/ui-showcase/ui-showcase.tsx new file mode 100644 index 000000000..2f9b0a803 --- /dev/null +++ b/apps/docs/src/components/ui-showcase/ui-showcase.tsx @@ -0,0 +1,216 @@ +import { + Accordion, + Avatar, + Button, + Checkbox, + DatePicker, + IconSwitch, + Input, + Modal, + Radio, + SegmentPicker, + Select, + type SelectItem, + Separator, + Status, + Switch, + TextArea, + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@workflowbuilder/ui'; +import { type ReactNode, useState } from 'react'; + +import styles from './ui-showcase.module.css'; + +const AVATAR_IMAGE = + 'data:image/svg+xml;utf8,' + + encodeURIComponent( + "AL", + ); + +const SELECT_ITEMS: SelectItem[] = [ + { value: 'opus', label: 'Claude Opus' }, + { value: 'sonnet', label: 'Claude Sonnet' }, + { value: 'haiku', label: 'Claude Haiku' }, +]; + +function Section({ title, children }: { title: string; children: ReactNode }) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function Field({ label, children }: { label: string; children: ReactNode }) { + return ( + + ); +} + +/** + * Live gallery of `@workflowbuilder/ui` components, rendered as a React island + * so the docs show the real, interactive components (not screenshots). + */ +export function UiShowcase() { + const [switchOn, setSwitchOn] = useState(true); + const [iconSwitchOn, setIconSwitchOn] = useState(false); + const [checked, setChecked] = useState(true); + const [radioValue, setRadioValue] = useState('weekly'); + const [inputValue, setInputValue] = useState(''); + const [areaValue, setAreaValue] = useState(''); + const [model, setModel] = useState('opus'); + const [date, setDate] = useState(null); + const [view, setView] = useState('list'); + const [modalOpen, setModalOpen] = useState(false); + + return ( +
+
+ + + + + + + +
+ +
+ + setSwitchOn(next)} /> + + + setIconSwitchOn(next)} + icon={☀️} + IconChecked={🌙} + /> + + + setChecked(event.target.checked)} /> + + + + {['daily', 'weekly', 'monthly'].map((value) => ( + + setRadioValue(value)} + /> + {value} + + ))} + + + + setView(next)}> + List + Grid + Board + + +
+ +
+
+ + setInputValue(event.target.value)} + /> + + + + + +