@techrox/page-studio 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/PageStudio.jsx","../src/BuilderEnhancements.jsx","../src/BuilderTopBar.jsx","../src/index.js"],"sourcesContent":["'use client';\n\n// PageStudio — the editor entry point consumers render. Wraps Puck with:\n// - an adapter for persistence (loadPage / savePage / onCreatePage)\n// - a StudioProvider that injects host primitives (Link, services, …)\n// into the block tree\n// - a replaceable top bar (header prop) with sane brand-agnostic defaults\n// - sidebar enhancements (Blocks / Layers tabs, count badges)\n//\n// Everything except `pageKey` is optional. Passing only `pageKey` works —\n// you'll get the default block library, default top bar, and a console\n// warning when Publish is clicked without an adapter wired.\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n useTransition,\n} from 'react';\nimport { Puck, useGetPuck, legacySideBarPlugin } from '@puckeditor/core';\nimport { App as AntdApp } from 'antd';\n\nimport {\n applyConfigDefaults,\n createPuckConfig,\n defaultOverrides,\n emptyPuckData,\n normalizePuckData,\n PageStudioProvider,\n} from '@techrox/page-studio-blocks';\n\nimport BuilderEnhancements from './BuilderEnhancements.jsx';\nimport DefaultTopBar from './BuilderTopBar.jsx';\n\nimport '@puckeditor/core/puck.css';\n\n// 0.21 ships a new side navigation rail. Our BuilderEnhancements observes\n// the legacy `_Sidebar--left` layout, so keep that DOM by applying the\n// legacy plugin once at module load.\nconst PSD_LEGACY_SIDEBAR = legacySideBarPlugin();\n\n// The header override is passed to Puck via `overrides.header`. Puck treats\n// the value as a component type — when its reference changes, React diffs\n// and unmounts the old override before mounting the new one. During that\n// gap Puck briefly paints its default top bar, which flashes a stock\n// Publish button on screen. To avoid that we keep the override component\n// reference stable for the entire lifetime of the editor and pipe live\n// props (branding, savedAt, callbacks, etc.) through a context provider\n// rendered by PageStudio. Live updates flow as normal context updates —\n// the component never gets unmounted.\nconst HeaderPropsContext = createContext(null);\n\nfunction StableHeaderOverride() {\n const getPuck = useGetPuck();\n const live = useContext(HeaderPropsContext);\n if (!live) return null;\n const props = {\n pageKey: live.pageKey,\n pageTitle: live.pageTitle,\n account: live.account,\n livePath: live.livePath,\n homeHref: live.homeHref,\n savedAt: live.savedAt,\n pending: live.pending,\n onPublish: () => live.handlePublish(getPuck().appState.data),\n onSignOut: live.onSignOut,\n onCreatePage: live.onCreatePage,\n branding: live.branding,\n extraActions: live.headerActions,\n LinkComponent: live.LinkComponent,\n };\n if (typeof live.header === 'function') return live.header(props);\n if (live.header) return live.header;\n return <DefaultTopBar {...props} />;\n}\n\n// `overrides.header` is referentially stable so Puck never remounts the\n// override. The internal stack also stabilizes `headerActions: () => null`\n// — that's how we suppress Puck's own actions area.\nconst NO_OP_ACTIONS = () => null;\nfunction buildStableOverrides(hostOverrides) {\n return { ...hostOverrides, header: StableHeaderOverride, headerActions: NO_OP_ACTIONS };\n}\n\n// Apply brand colors via BOTH an inline style on <html> (for the outer\n// editor chrome) and a <style> tag in <head> (so Puck's iframe canvas,\n// which clones head styles into its document, picks them up too). Pure\n// inline styles on document.documentElement do not propagate to the\n// iframe — only <style>/<link> elements do.\n//\n// We write to `--tps-*` because that's what the block library reads. The\n// host page may also paint these vars on a wrapping element (e.g. a\n// brand-switcher's `[data-brand]` div), but that element doesn't exist\n// inside Puck's iframe — so without this :root injection the canvas\n// falls back to the package's default values and ignores the brand.\nfunction setCssVars(branding) {\n if (typeof document === 'undefined' || !branding) return;\n const root = document.documentElement.style;\n if (branding.primaryColor) root.setProperty('--tps-primary', branding.primaryColor);\n if (branding.accentColor) root.setProperty('--tps-accent', branding.accentColor);\n if (branding.inkColor) root.setProperty('--tps-ink', branding.inkColor);\n\n const lines = [];\n if (branding.primaryColor) lines.push(`--tps-primary: ${branding.primaryColor};`);\n if (branding.accentColor) lines.push(`--tps-accent: ${branding.accentColor};`);\n if (branding.inkColor) lines.push(`--tps-ink: ${branding.inkColor};`);\n if (!lines.length) return;\n\n let tag = document.getElementById('tps-brand-vars');\n if (!tag) {\n tag = document.createElement('style');\n tag.id = 'tps-brand-vars';\n document.head.appendChild(tag);\n }\n const next = `:root { ${lines.join(' ')} }`;\n if (tag.textContent !== next) tag.textContent = next;\n}\n\nexport default function PageStudio({\n pageKey,\n initialData,\n pageTitle,\n account,\n livePath,\n homeHref,\n branding,\n studio,\n adapter = {},\n config,\n blockDefaults,\n overrides = defaultOverrides,\n header,\n headerActions,\n onSignOut,\n sidebarLabels,\n LinkComponent,\n // Forwarded to Puck. Defaults to iframe enabled — Puck v0.20 mounts the\n // @dnd-kit context inside the canvas iframe; with the iframe disabled,\n // strict-mode double-mount and Vite HMR can leave the canvas with no live\n // drop targets, so dropped blocks vanish into an empty content array.\n // Hosts that need the canvas to share the host document (e.g. to inherit\n // global CSS without copying it across) can pass { enabled: false }.\n iframe = { enabled: true },\n}) {\n const { message } = AntdApp.useApp();\n const [pending, startTransition] = useTransition();\n const [savedAt, setSavedAt] = useState(null);\n\n // Resolve the config ONCE on first mount and freeze the reference. Puck\n // treats `config` as a structural prop — passing a new object on every\n // brand switch makes it re-diff its block registry, drop @dnd-kit's\n // collision cache and rebuild a chunk of internal memos. That's the\n // bulk of what makes brand switching feel sluggish.\n //\n // Trade-off: a `blockDefaults` change after mount (e.g. host swapping\n // tenant defaults mid-edit) no longer updates the per-block defaults\n // applied to newly dropped blocks — those will use whatever defaults\n // were active at first mount. CSS vars (color, ink, accent) still\n // update live via setCssVars. For the showcase that's exactly what we\n // want: brand swaps repaint colors instantly without churning Puck.\n // Hosts that genuinely need brand-aware drop defaults should remount\n // PageStudio with a fresh key when they swap tenants.\n const [resolvedConfig] = useState(\n () => config || createPuckConfig({ defaults: blockDefaults }),\n );\n\n const [data, setData] = useState(() =>\n initialData && Array.isArray(initialData.content)\n ? applyConfigDefaults(normalizePuckData(initialData), resolvedConfig)\n : null,\n );\n const [loading, setLoading] = useState(!data && !!adapter.loadPage);\n\n // Apply brand CSS variables synchronously during render — before Puck\n // mounts its iframe and before the first paint. Running this in useEffect\n // makes the canvas paint once with the package-default teal and then\n // repaint when the effect commits, producing a visible colour flash on\n // every load. The function is idempotent (it only writes when the head\n // <style> content actually differs) so repeated calls during strict-mode\n // double-render are a no-op.\n setCssVars(branding);\n\n // Initial load when initialData wasn't server-supplied. Hosts that SSR\n // their CMS read should always pass initialData and skip this code path.\n useEffect(() => {\n if (data || !adapter.loadPage) return;\n let cancelled = false;\n setLoading(true);\n adapter.loadPage(pageKey)\n .then((loaded) => {\n if (cancelled) return;\n setData(\n loaded && Array.isArray(loaded.content)\n ? applyConfigDefaults(normalizePuckData(loaded), resolvedConfig)\n : emptyPuckData,\n );\n })\n .catch((err) => {\n if (cancelled) return;\n message.error(err?.message || 'Could not load page.');\n setData(emptyPuckData);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n }, [pageKey, adapter, data, message]);\n\n const handlePublish = (nextData) => {\n if (!adapter.savePage) {\n message.error('No savePage adapter configured.');\n return;\n }\n startTransition(async () => {\n try {\n await adapter.savePage(pageKey, nextData);\n setSavedAt(new Date().toISOString());\n message.success('Published.');\n } catch (err) {\n message.error(err?.message || 'Save failed.');\n }\n });\n };\n\n // Live values consumed by the stable header override via context. The\n // context VALUE may change every render — that's fine, it triggers a\n // normal re-render of StableHeaderOverride. The override COMPONENT\n // reference (passed to Puck via overrides.header) never changes, so\n // Puck never unmounts it. That's what kills the default-button flash\n // on brand switch.\n const headerLive = useMemo(() => ({\n pageKey,\n pageTitle,\n account,\n livePath,\n homeHref,\n savedAt,\n pending,\n handlePublish,\n onSignOut,\n onCreatePage: adapter.onCreatePage,\n branding,\n headerActions,\n LinkComponent,\n header,\n }), [\n pageKey, pageTitle, account, livePath, homeHref, savedAt, pending,\n handlePublish, onSignOut, adapter.onCreatePage, branding, headerActions,\n LinkComponent, header,\n ]);\n\n // Build the overrides object ONCE per `overrides` prop change. The\n // header slot is the stable component declared at module scope —\n // brand swaps don't invalidate the reference.\n const mergedOverrides = useMemo(\n () => buildStableOverrides(overrides),\n [overrides],\n );\n\n if (loading || !data) {\n return (\n <div className=\"psd-builder-page psd-builder-page--loading\">\n <div className=\"psd-builder-loading\">Loading editor…</div>\n </div>\n );\n }\n\n return (\n <PageStudioProvider value={studio}>\n <HeaderPropsContext.Provider value={headerLive}>\n <div className=\"psd-builder-page\">\n <BuilderEnhancements\n blocksLabel={sidebarLabels?.blocks}\n layersLabel={sidebarLabels?.layers}\n searchPlaceholder={sidebarLabels?.search}\n />\n <Puck\n config={resolvedConfig}\n data={data}\n overrides={mergedOverrides}\n onPublish={handlePublish}\n iframe={iframe}\n plugins={[PSD_LEGACY_SIDEBAR]}\n />\n </div>\n </HeaderPropsContext.Provider>\n </PageStudioProvider>\n );\n}\n","'use client';\n\n// Post-mount DOM enhancements for Puck's left sidebar:\n// 1. Hide Puck's \"Components\" / \"Outline\" section titles\n// 2. Inject a tab bar at the top that toggles between the two sections\n// 3. Inject a search input that filters the block cards by name + summary\n// 4. Append a count badge to each component-category header (visible-only)\n//\n// IMPORTANT: every DOM mutation we make MUST be idempotent (no-op when the\n// target state already matches) AND we disconnect the MutationObserver\n// while applying so our own writes don't feed back into the observer and\n// cause an infinite loop / tab freeze.\n\nimport { useEffect } from 'react';\n\nconst STORAGE_KEY = 'psd.builderTab';\n\nexport default function BuilderEnhancements({\n blocksLabel = 'Blocks',\n layersLabel = 'Layers',\n searchPlaceholder = 'Search blocks',\n} = {}) {\n useEffect(() => {\n if (typeof document === 'undefined') return;\n\n let observer = null;\n // Search query persists across re-renders even if Puck wipes the sidebar\n // and we have to re-inject the input. Lives in this closure (per mount).\n let query = '';\n\n // Filtering is done via attributes on elements WE own (.tps-block-card\n // and _ComponentList_ groups). CSS in editor/styles.css hides the\n // matching Puck wrapper via :has(). Writing only attributes — not\n // inline styles on Puck's wrappers — keeps us out of a fight with\n // Puck's React reconciler when it re-renders the picker after a drop.\n const matchesQuery = (card, q) => {\n if (!q) return true;\n const name = (card.querySelector('.tps-block-card__name')?.textContent || '').toLowerCase();\n const desc = (card.querySelector('.tps-block-card__desc')?.textContent || '').toLowerCase();\n return name.includes(q) || desc.includes(q);\n };\n\n const setAttrIfChanged = (el, name, value) => {\n const current = el.getAttribute(name);\n if (value == null) {\n if (current !== null) el.removeAttribute(name);\n } else if (current !== value) {\n el.setAttribute(name, value);\n }\n };\n\n const updateCounts = (sidebar) => {\n const headers = sidebar.querySelectorAll('[class*=\"_ComponentList-title_\"]');\n headers.forEach((header) => {\n const parent = header.closest('[class*=\"_ComponentList_\"]');\n if (!parent) return;\n const list = parent.querySelector('[class*=\"_ComponentList-content_\"]');\n if (!list) return;\n const cards = list.querySelectorAll('.tps-block-card');\n const visible = Array.from(cards).filter(\n (c) => c.getAttribute('data-psd-hidden') !== 'true',\n ).length;\n const count = String(visible);\n let badge = header.querySelector('.psd-cat-count');\n if (!badge) {\n badge = document.createElement('span');\n badge.className = 'psd-cat-count';\n badge.textContent = count;\n const chevron = header.querySelector('[class*=\"_ComponentList-titleIcon_\"]');\n if (chevron) {\n header.insertBefore(badge, chevron);\n } else {\n header.appendChild(badge);\n }\n } else if (badge.textContent !== count) {\n badge.textContent = count;\n }\n });\n };\n\n const applyFilter = (sidebar) => {\n const q = query.trim().toLowerCase();\n const cards = sidebar.querySelectorAll('.tps-block-card');\n cards.forEach((card) => {\n const hidden = !matchesQuery(card, q);\n setAttrIfChanged(card, 'data-psd-hidden', hidden ? 'true' : null);\n });\n\n // Mark the actual grid cell so it collapses out of layout — not just\n // our inner card. Puck's <DrawerItem> renders an unnamed <div> as the\n // direct child of [data-puck-drawer]; that <div> IS the grid cell.\n // Hiding only the .tps-block-card or any class-bearing inner wrapper\n // leaves the cell empty so visible matches stay in their original\n // grid slots (which is what the user was seeing). We tag the cell\n // directly here so a single CSS attribute selector can collapse it.\n const drawers = sidebar.querySelectorAll('[data-puck-drawer]');\n drawers.forEach((drawer) => {\n Array.from(drawer.children).forEach((cell) => {\n const card = cell.querySelector('.tps-block-card');\n if (!card) {\n setAttrIfChanged(cell, 'data-psd-cell-hidden', null);\n return;\n }\n const hidden = !matchesQuery(card, q);\n setAttrIfChanged(cell, 'data-psd-cell-hidden', hidden ? 'true' : null);\n });\n });\n\n // Hide entire category groups when all of their cards are filtered out.\n const groups = sidebar.querySelectorAll('[class*=\"_ComponentList_\"]');\n groups.forEach((g) => {\n const items = g.querySelectorAll('.tps-block-card');\n if (!items.length) {\n setAttrIfChanged(g, 'data-psd-empty', null);\n return;\n }\n const allHidden = Array.from(items).every(\n (c) => c.getAttribute('data-psd-hidden') === 'true',\n );\n setAttrIfChanged(g, 'data-psd-empty', q && allHidden ? 'true' : null);\n });\n\n // Counts must refresh on every filter change AND every drag/drop\n // (which adds/removes cards). Call from here so the search-input\n // handler also gets badge updates without re-running apply().\n updateCounts(sidebar);\n };\n\n const apply = () => {\n const sidebar = document.querySelector('[class*=\"_Sidebar--left\"]');\n if (!sidebar) return;\n\n const sections = sidebar.querySelectorAll('[class*=\"_SidebarSection_\"]');\n if (sections.length < 2) return;\n\n sections.forEach((s) => {\n const isComponents = !!s.querySelector('[class*=\"_ComponentList_\"]');\n const want = isComponents ? 'components' : 'outline';\n if (s.getAttribute('data-psd-section') !== want) {\n s.setAttribute('data-psd-section', want);\n }\n });\n\n sections.forEach((s) => {\n const title = s.querySelector('[class*=\"_SidebarSection-title_\"]');\n if (title && !title.hasAttribute('data-psd-hidden-title')) {\n title.setAttribute('data-psd-hidden-title', 'true');\n }\n });\n\n let tabs = sidebar.querySelector('.psd-sidebar-tabs');\n if (!tabs) {\n tabs = document.createElement('div');\n tabs.className = 'psd-sidebar-tabs';\n tabs.innerHTML = `\n <button type=\"button\" class=\"psd-sidebar-tab\" data-tab=\"components\">${blocksLabel}</button>\n <button type=\"button\" class=\"psd-sidebar-tab\" data-tab=\"outline\">${layersLabel}</button>\n `;\n sidebar.insertBefore(tabs, sidebar.firstChild);\n\n const setActive = (which) => {\n if (sidebar.getAttribute('data-psd-active-tab') === which) return;\n sidebar.setAttribute('data-psd-active-tab', which);\n try { sessionStorage.setItem(STORAGE_KEY, which); } catch {}\n tabs.querySelectorAll('button').forEach((b) => {\n const active = b.dataset.tab === which;\n if (b.classList.contains('is-active') !== active) {\n b.classList.toggle('is-active', active);\n }\n });\n };\n\n tabs.addEventListener('click', (e) => {\n const btn = e.target.closest('button[data-tab]');\n if (btn) setActive(btn.dataset.tab);\n });\n\n let initial = 'components';\n try { initial = sessionStorage.getItem(STORAGE_KEY) || 'components'; } catch {}\n setActive(initial);\n }\n\n // Search input — visible only when the components tab is active\n // (the [data-psd-active-tab=\"components\"] CSS gate does the toggling).\n let search = sidebar.querySelector('.psd-sidebar-search');\n if (!search) {\n search = document.createElement('div');\n search.className = 'psd-sidebar-search';\n const input = document.createElement('input');\n input.type = 'search';\n input.className = 'psd-sidebar-search__input';\n input.placeholder = searchPlaceholder;\n input.setAttribute('aria-label', searchPlaceholder);\n search.appendChild(input);\n // Place directly after the tab bar.\n if (tabs.nextSibling) sidebar.insertBefore(search, tabs.nextSibling);\n else sidebar.appendChild(search);\n\n input.addEventListener('input', () => {\n query = input.value || '';\n applyFilter(sidebar);\n });\n }\n // Re-sync the input value across Puck re-renders.\n const searchInput = search.querySelector('input');\n if (searchInput && searchInput.value !== query) searchInput.value = query;\n\n applyFilter(sidebar);\n };\n\n const safeApply = () => {\n if (observer) observer.disconnect();\n try { apply(); } catch { /* never break the host page */ }\n if (observer) observer.observe(document.body, { childList: true, subtree: true });\n };\n\n safeApply();\n\n let queued = false;\n observer = new MutationObserver(() => {\n if (queued) return;\n queued = true;\n requestAnimationFrame(() => {\n queued = false;\n safeApply();\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n\n return () => {\n if (observer) observer.disconnect();\n };\n }, [blocksLabel, layersLabel, searchPlaceholder]);\n\n return null;\n}\n","'use client';\n\n// Default top bar for the editor. Replaceable via the `header` prop on\n// <PageStudio /> — if you need full control, pass a render function.\n//\n// Designed to be brand-agnostic: every visible label/color comes from\n// `branding` or sensible neutrals. Account/home/publish are all optional;\n// hide what you don't need by leaving the corresponding prop unset.\n\nimport { Avatar, Button, ConfigProvider, Dropdown, Space, theme as antdTheme } from 'antd';\nimport {\n ArrowLeftOutlined,\n EyeOutlined,\n HomeOutlined,\n LogoutOutlined,\n PlusOutlined,\n RocketOutlined,\n UserOutlined,\n} from '@ant-design/icons';\n\nfunction DefaultLogo() {\n return (\n <svg width={20} height={20} viewBox=\"0 0 64 64\" aria-hidden>\n <rect width=\"64\" height=\"64\" rx=\"14\" fill=\"currentColor\" />\n </svg>\n );\n}\n\nexport default function BuilderTopBar({\n pageKey,\n pageTitle,\n account,\n livePath,\n savedAt,\n pending,\n onPublish,\n onSignOut,\n onCreatePage,\n homeHref,\n branding = {},\n extraActions,\n LinkComponent = 'a',\n}) {\n const brand = {\n name: 'Page Studio',\n logo: <DefaultLogo />,\n primaryColor: '#0F766E',\n ...branding,\n };\n const Link = LinkComponent;\n\n const accountMenu = account\n ? {\n items: [\n {\n key: 'who',\n disabled: true,\n label: (\n <div style={{ padding: '4px 0', minWidth: 200 }}>\n <div style={{ fontSize: 13, fontWeight: 600, color: '#0f172a' }}>\n {account.name}\n </div>\n {account.email && account.email !== account.name && (\n <div style={{ fontSize: 11, color: '#94a3b8', marginTop: 2 }}>\n {account.email}\n </div>\n )}\n </div>\n ),\n },\n { type: 'divider' },\n homeHref && {\n key: 'home',\n icon: <HomeOutlined />,\n label: <Link href={homeHref}>Admin home</Link>,\n },\n onSignOut && {\n key: 'signout',\n icon: <LogoutOutlined />,\n label: (\n <button\n type=\"button\"\n onClick={onSignOut}\n style={{ all: 'unset', cursor: 'pointer', width: '100%', display: 'block' }}\n >\n Sign out\n </button>\n ),\n },\n ].filter(Boolean),\n }\n : null;\n\n // Locally re-theme AntD so the Publish button + avatar pick up the\n // configured brand colors regardless of the host's ConfigProvider — the\n // top bar should look like the brand, not like the surrounding admin.\n const brandTheme = {\n algorithm: antdTheme.defaultAlgorithm,\n token: {\n colorPrimary: brand.primaryColor,\n colorInfo: brand.primaryColor,\n },\n };\n\n return (\n <ConfigProvider theme={brandTheme}>\n <div className=\"psd-builder-bar\" style={{ color: '#fff' }}>\n {homeHref ? (\n <Link\n href={homeHref}\n className=\"psd-builder-bar__brand\"\n title=\"Back to admin\"\n style={{ color: 'inherit' }}\n >\n <ArrowLeftOutlined style={{ fontSize: 12 }} />\n <span aria-hidden style={{ color: brand.primaryColor, display: 'inline-flex' }}>\n {brand.logo}\n </span>\n <span>{brand.name}</span>\n </Link>\n ) : (\n <div className=\"psd-builder-bar__brand\">\n <span aria-hidden style={{ color: brand.primaryColor, display: 'inline-flex' }}>\n {brand.logo}\n </span>\n <span>{brand.name}</span>\n </div>\n )}\n\n <div className=\"psd-builder-bar__crumbs\">\n <span className=\"psd-builder-bar__current\">{pageTitle || pageKey}</span>\n {pageKey && (\n <span style={{ marginLeft: 8, opacity: 0.5, fontSize: 11 }}>\n <code style={{ fontSize: 10 }}>{pageKey}</code>\n </span>\n )}\n {savedAt && (\n <span style={{ marginLeft: 12, fontSize: 11, opacity: 0.55 }}>\n · Saved {new Date(savedAt).toLocaleTimeString()}\n </span>\n )}\n {pending && (\n <span style={{ marginLeft: 12, fontSize: 11, opacity: 0.55 }}>· Saving…</span>\n )}\n </div>\n\n <Space size={6} className=\"psd-builder-bar__actions\">\n {extraActions}\n {onCreatePage && (\n <Button size=\"small\" icon={<PlusOutlined />} onClick={onCreatePage}>\n New page\n </Button>\n )}\n {livePath && (\n <Link href={livePath} target=\"_blank\" rel=\"noreferrer\">\n <Button size=\"small\" icon={<EyeOutlined />}>\n View live\n </Button>\n </Link>\n )}\n <Button\n type=\"primary\"\n size=\"small\"\n icon={<RocketOutlined />}\n onClick={onPublish}\n loading={pending}\n >\n Publish\n </Button>\n {account && accountMenu && (\n <Dropdown menu={accountMenu} placement=\"bottomRight\">\n <Button type=\"text\" size=\"small\" style={{ color: '#fff' }}>\n <Avatar\n size={22}\n icon={<UserOutlined />}\n style={{ background: brand.primaryColor }}\n />\n </Button>\n </Dropdown>\n )}\n </Space>\n </div>\n </ConfigProvider>\n );\n}\n","export { default as PageStudio } from './PageStudio.jsx';\nexport { default as BuilderEnhancements } from './BuilderEnhancements.jsx';\nexport { default as BuilderTopBar } from './BuilderTopBar.jsx';\n\n// Re-export the blocks package's context so consumers don't need a separate\n// import to wrap their public-site renderer with the same studio config.\nexport {\n PageStudioProvider,\n useStudio,\n createPuckConfig,\n defaultBlocks,\n defaultCategories,\n defaultOverrides,\n emptyPuckData,\n} from '@techrox/page-studio-blocks';\n"],"mappings":"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","names":["useEffect","jsx","jsxs","useEffect","PageStudioProvider","createPuckConfig","defaultOverrides","emptyPuckData"]}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@techrox/page-studio",
3
+ "version": "1.0.0",
4
+ "description": "Drag-and-drop visual page builder for React, built on Puck. Adapter-driven persistence, brand theming, sidebar block library, replaceable top bar.",
5
+ "license": "MIT",
6
+ "author": "techrox",
7
+ "homepage": "https://github.com/techrox/page-studio#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/techrox/page-studio.git",
11
+ "directory": "packages/editor"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/techrox/page-studio/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.cjs"
23
+ },
24
+ "./styles.css": "./src/styles.css"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src/styles.css",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "sideEffects": [
33
+ "*.css"
34
+ ],
35
+ "peerDependencies": {
36
+ "@ant-design/icons": ">=5",
37
+ "@puckeditor/core": ">=0.21",
38
+ "antd": ">=5",
39
+ "react": ">=18",
40
+ "react-dom": ">=18",
41
+ "@techrox/page-studio-blocks": "1.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@ant-design/icons": "^5.4.0",
45
+ "@puckeditor/core": "^0.21.2",
46
+ "antd": "^5.19.0",
47
+ "react": "^18.3.1",
48
+ "react-dom": "^18.3.1",
49
+ "@techrox/page-studio-blocks": "1.0.0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "keywords": [
55
+ "page-builder",
56
+ "visual-editor",
57
+ "drag-and-drop",
58
+ "puck",
59
+ "puckeditor",
60
+ "cms",
61
+ "headless-cms",
62
+ "wysiwyg",
63
+ "react",
64
+ "antd",
65
+ "page-studio"
66
+ ],
67
+ "engines": {
68
+ "node": ">=18"
69
+ },
70
+ "scripts": {
71
+ "build": "tsup",
72
+ "dev": "tsup --watch",
73
+ "test": "vitest run",
74
+ "test:watch": "vitest"
75
+ }
76
+ }
package/src/styles.css ADDED
@@ -0,0 +1,193 @@
1
+ /* @techrox/page-studio — editor chrome (top bar, sidebar tabs, loading state).
2
+ * Block styling lives in @techrox/page-studio-blocks/styles.css. Both
3
+ * stylesheets share the same `--tps-*` CSS variables; override them at
4
+ * :root in your host (or let PageStudio's `branding` prop inject them) to
5
+ * rebrand both at once.
6
+ *
7
+ * Note: ".tps-builder-page" is intentionally retained as an alias so
8
+ * downstream styles written against the legacy Page Studio class names keep
9
+ * working. New rules should target ".psd-builder-page". */
10
+
11
+ .psd-builder-page,
12
+ .tps-builder-page {
13
+ display: flex;
14
+ flex-direction: column;
15
+ min-height: 100vh;
16
+ background: var(--tps-bg-soft, #f8fafc);
17
+ }
18
+
19
+ .psd-builder-page--loading {
20
+ align-items: center;
21
+ justify-content: center;
22
+ }
23
+ .psd-builder-loading {
24
+ font-size: 13px;
25
+ color: var(--tps-muted, #64748b);
26
+ }
27
+
28
+ /* --- Top bar ------------------------------------------------------------ */
29
+ /* With `overrides.header`, Puck renders our component directly as a child
30
+ * of `_PuckLayout-inner_`. That layout is a 3-column CSS grid with named
31
+ * areas "header header header" / "left editor right" — so unless our bar
32
+ * is explicitly placed in `grid-area: header`, it auto-flows into a single
33
+ * cell and only spans the left sidebar's column. The grid-area assignment
34
+ * here is what makes the bar span the full row width. */
35
+ .psd-builder-bar {
36
+ grid-area: header;
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 16px;
40
+ min-height: 48px;
41
+ padding: 6px 16px;
42
+ background: var(--tps-ink, #0f172a);
43
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
44
+ font-size: 13px;
45
+ }
46
+
47
+ .psd-builder-bar__brand {
48
+ display: inline-flex;
49
+ align-items: center;
50
+ gap: 8px;
51
+ font-weight: 700;
52
+ letter-spacing: 0.2px;
53
+ text-decoration: none;
54
+ color: #fff;
55
+ padding: 4px 6px;
56
+ border-radius: 6px;
57
+ }
58
+ .psd-builder-bar__brand:hover {
59
+ background: rgba(255, 255, 255, 0.08);
60
+ }
61
+
62
+ .psd-builder-bar__crumbs {
63
+ flex: 1;
64
+ min-width: 0;
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 6px;
68
+ color: rgba(255, 255, 255, 0.85);
69
+ white-space: nowrap;
70
+ overflow: hidden;
71
+ text-overflow: ellipsis;
72
+ }
73
+ .psd-builder-bar__current {
74
+ font-weight: 600;
75
+ }
76
+
77
+ .psd-builder-bar__actions {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ gap: 6px;
81
+ }
82
+
83
+ /* --- Sidebar tabs (injected by BuilderEnhancements) --------------------- */
84
+ /* Sticky so they remain accessible while the blocks/outline list below
85
+ * scrolls. Puck's left sidebar is `overflow-y: auto`, which is the
86
+ * scrolling ancestor sticky positions against. */
87
+ .psd-sidebar-tabs {
88
+ position: sticky;
89
+ top: 0;
90
+ z-index: 5;
91
+ display: flex;
92
+ gap: 4px;
93
+ padding: 12px 12px 0;
94
+ background: #fff;
95
+ border-bottom: 1px solid var(--tps-line, #e2e8f0);
96
+ }
97
+ .psd-sidebar-tab {
98
+ flex: 1;
99
+ padding: 8px 10px;
100
+ font-size: 12px;
101
+ font-weight: 600;
102
+ color: var(--tps-muted, #64748b);
103
+ background: transparent;
104
+ border: 0;
105
+ border-bottom: 2px solid transparent;
106
+ cursor: pointer;
107
+ }
108
+ .psd-sidebar-tab.is-active {
109
+ color: var(--tps-ink, #0f172a);
110
+ border-bottom-color: var(--tps-primary, #0f766e);
111
+ }
112
+
113
+ /* Tab visibility — driven by data-attr the script sets on the sidebar.
114
+ * Use a descendant (not child) combinator: Puck's legacySideBarPlugin
115
+ * wraps both SidebarSections in an intermediate <div style="overflowY: auto">,
116
+ * so the sections are grandchildren of _Sidebar--left, not direct children. */
117
+ [class*='_Sidebar--left'][data-psd-active-tab='components']
118
+ [class*='_SidebarSection_'][data-psd-section='outline'] {
119
+ display: none !important;
120
+ }
121
+ [class*='_Sidebar--left'][data-psd-active-tab='outline']
122
+ [class*='_SidebarSection_'][data-psd-section='components'] {
123
+ display: none !important;
124
+ }
125
+ [data-psd-hidden-title='true'] {
126
+ display: none !important;
127
+ }
128
+
129
+ /* Block search — sticky under the tab bar; visible only on the Blocks tab. */
130
+ .psd-sidebar-search {
131
+ position: sticky;
132
+ top: 41px; /* below the .psd-sidebar-tabs (40px content + 1px border) */
133
+ z-index: 4;
134
+ padding: 10px 12px;
135
+ background: #fff;
136
+ border-bottom: 1px solid var(--tps-line, #e2e8f0);
137
+ }
138
+ [class*='_Sidebar--left'][data-psd-active-tab='outline'] > .psd-sidebar-search {
139
+ display: none;
140
+ }
141
+ .psd-sidebar-search__input {
142
+ width: 100%;
143
+ height: 32px;
144
+ padding: 0 10px;
145
+ font: inherit;
146
+ font-size: 13px;
147
+ color: var(--tps-ink, #0f172a);
148
+ background: var(--tps-bg-soft, #f8fafc);
149
+ border: 1px solid var(--tps-line, #e2e8f0);
150
+ border-radius: 8px;
151
+ outline: 0;
152
+ transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
153
+ }
154
+ .psd-sidebar-search__input::placeholder { color: var(--tps-hint, #94a3b8); }
155
+ .psd-sidebar-search__input:focus {
156
+ background: #fff;
157
+ border-color: var(--tps-primary, #0b60d8);
158
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--tps-primary, #0b60d8) 15%, transparent);
159
+ }
160
+ /* Strip native search clear button — Puck cards aren't full-width either,
161
+ * so the trailing UI feels out of place. */
162
+ .psd-sidebar-search__input::-webkit-search-cancel-button { -webkit-appearance: none; }
163
+
164
+ /* Search filter — collapse the actual grid cell so visible matches reflow
165
+ * up to the top-left of the picker.
166
+ *
167
+ * Puck v0.21 (<DrawerItem>) renders an unnamed <div> as the direct child
168
+ * of [data-puck-drawer]; that <div> is what the CSS grid in
169
+ * @techrox/page-studio-blocks (styles.css `[data-puck-drawer]`) treats
170
+ * as the cell. The element has no class, so we tag it from JS with
171
+ * `data-psd-cell-hidden` (see BuilderEnhancements.jsx applyFilter) and
172
+ * collapse via attribute selector. The .tps-block-card[data-psd-hidden]
173
+ * rule below is a belt-and-braces fallback for environments where the
174
+ * cell tagging hasn't (yet) run. */
175
+ [data-psd-cell-hidden='true'] { display: none !important; }
176
+ .tps-block-card[data-psd-hidden='true'] { display: none !important; }
177
+ [class*='_ComponentList_'][data-psd-empty='true'] { display: none !important; }
178
+
179
+ /* Category count badges */
180
+ .psd-cat-count {
181
+ display: inline-flex;
182
+ align-items: center;
183
+ justify-content: center;
184
+ min-width: 18px;
185
+ height: 18px;
186
+ padding: 0 5px;
187
+ margin-left: 8px;
188
+ font-size: 11px;
189
+ font-weight: 600;
190
+ color: var(--tps-muted, #64748b);
191
+ background: var(--tps-bg-soft, #f1f5f9);
192
+ border-radius: 10px;
193
+ }