@elytracms/studio 0.0.41 → 0.0.43
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.
- package/dist/components/assets/asset-detail.js +1 -1
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/assets-module.js +19 -6
- package/dist/components/assets/assets-module.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +2 -1
- package/dist/components/assets/assets-store.js +4 -8
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/assets-view-preference.d.ts +0 -8
- package/dist/components/assets/assets-view-preference.js +12 -13
- package/dist/components/assets/assets-view-preference.js.map +1 -1
- package/dist/components/auth-context.d.ts +2 -1
- package/dist/components/auth-context.js +6 -8
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +2 -2
- package/dist/components/builder/builder-store.js +4 -8
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas.js +16 -2
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/cms/bulk-actions.js +2 -1
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.js +2 -1
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-composition-validators.d.ts +25 -0
- package/dist/components/cms/cms-composition-validators.js +75 -0
- package/dist/components/cms/cms-composition-validators.js.map +1 -0
- package/dist/components/cms/cms-labels.d.ts +15 -0
- package/dist/components/cms/cms-labels.js +27 -0
- package/dist/components/cms/cms-labels.js.map +1 -0
- package/dist/components/cms/cms-store.d.ts +23 -10
- package/dist/components/cms/cms-store.js +78 -154
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +1 -1
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +67 -67
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/details-pane.d.ts +11 -21
- package/dist/components/cms/details-pane.js +14 -31
- package/dist/components/cms/details-pane.js.map +1 -1
- package/dist/components/cms/discard-draft.d.ts +18 -0
- package/dist/components/cms/discard-draft.js +18 -0
- package/dist/components/cms/discard-draft.js.map +1 -1
- package/dist/components/cms/document-editor-context.d.ts +2 -1
- package/dist/components/cms/document-editor-context.js +4 -9
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/document-ref-utils.d.ts +33 -0
- package/dist/components/cms/document-ref-utils.js +47 -0
- package/dist/components/cms/document-ref-utils.js.map +1 -0
- package/dist/components/cms/document-url-path.d.ts +39 -0
- package/dist/components/cms/document-url-path.js +60 -0
- package/dist/components/cms/document-url-path.js.map +1 -0
- package/dist/components/cms/field-control.js +19 -13
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/issues-pane.js +1 -1
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/rich-text/block-commands.d.ts +36 -0
- package/dist/components/cms/rich-text/block-commands.js +47 -0
- package/dist/components/cms/rich-text/block-commands.js.map +1 -0
- package/dist/components/cms/rich-text/slash-command.js +25 -8
- package/dist/components/cms/rich-text/slash-command.js.map +1 -1
- package/dist/components/cms/rich-text/toolbar.js +3 -2
- package/dist/components/cms/rich-text/toolbar.js.map +1 -1
- package/dist/components/cms/version-compare.js +1 -1
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-model.d.ts +31 -0
- package/dist/components/cms/versions-model.js +30 -0
- package/dist/components/cms/versions-model.js.map +1 -1
- package/dist/components/cms/view-bar.js +9 -6
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/collab/collab-context.d.ts +2 -1
- package/dist/components/collab/collab-context.js +4 -8
- package/dist/components/collab/collab-context.js.map +1 -1
- package/dist/components/collection-view/collection-view-context.d.ts +9 -1
- package/dist/components/collection-view/collection-view-context.js +4 -9
- package/dist/components/collection-view/collection-view-context.js.map +1 -1
- package/dist/components/components/component-detail.js +2 -5
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.js +8 -4
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/registry-browser.js +24 -5
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/transfer-dialog.d.ts +10 -0
- package/dist/components/components/transfer-dialog.js +24 -8
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/delivery-health-banner.d.ts +12 -6
- package/dist/components/delivery-health-banner.js +27 -29
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/dev-login-badge.d.ts +0 -2
- package/dist/components/dev-login-badge.js +1 -3
- package/dist/components/dev-login-badge.js.map +1 -1
- package/dist/components/inspector/inspector-sidebar.js +20 -3
- package/dist/components/inspector/inspector-sidebar.js.map +1 -1
- package/dist/components/playground-badge.d.ts +0 -2
- package/dist/components/playground-badge.js +0 -2
- package/dist/components/playground-badge.js.map +1 -1
- package/dist/components/project-list.js +2 -6
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.d.ts +6 -2
- package/dist/components/project-session.js +32 -83
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js +2 -6
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/redirects/redirects-store.d.ts +2 -1
- package/dist/components/redirects/redirects-store.js +4 -8
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/components/studio-context.d.ts +2 -1
- package/dist/components/studio-context.js +4 -8
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-shell.js +1 -1
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/components/theme-context.d.ts +2 -1
- package/dist/components/theme-context.js +4 -8
- package/dist/components/theme-context.js.map +1 -1
- package/dist/components/use-hydration-phase.d.ts +65 -0
- package/dist/components/use-hydration-phase.js +64 -0
- package/dist/components/use-hydration-phase.js.map +1 -0
- package/dist/components/users/users-store.d.ts +2 -1
- package/dist/components/users/users-store.js +4 -8
- package/dist/components/users/users-store.js.map +1 -1
- package/dist/components/workspace-projects.d.ts +14 -0
- package/dist/components/workspace-projects.js +21 -0
- package/dist/components/workspace-projects.js.map +1 -1
- package/dist/lib/data/data-store.d.ts +2 -1
- package/dist/lib/data/data-store.js +4 -8
- package/dist/lib/data/data-store.js.map +1 -1
- package/dist/lib/delivery/instant-publish.d.ts +9 -13
- package/dist/lib/delivery/instant-publish.js +0 -7
- package/dist/lib/delivery/instant-publish.js.map +1 -1
- package/dist/lib/diff-view-preference.js +4 -4
- package/dist/lib/diff-view-preference.js.map +1 -1
- package/dist/lib/i18n/messages.en.js +7 -5
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/local-preference.d.ts +37 -0
- package/dist/lib/local-preference.js +36 -0
- package/dist/lib/local-preference.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +3 -1
- package/dist/lib/persistence/convex-adapter.js +20 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/rail.js +7 -6
- package/dist/lib/rail.js.map +1 -1
- package/dist/lib/required-context.d.ts +24 -0
- package/dist/lib/required-context.js +28 -0
- package/dist/lib/required-context.js.map +1 -0
- package/dist/lib/theme.js +11 -4
- package/dist/lib/theme.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/segmented-control.d.ts +46 -0
- package/dist/ui/segmented-control.js +13 -0
- package/dist/ui/segmented-control.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collab-context.js","sourceRoot":"","sources":["../../../src/components/collab/collab-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"collab-context.js","sourceRoot":"","sources":["../../../src/components/collab/collab-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAEzG,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAqBjD,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,qBAAqB,CAAc,QAAQ,CAAC,CAAA;AAEvG,MAAM,EAAE,GAAU,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AAE9C,MAAM,UAAU,cAAc,CAAC,EAC7B,SAAS,EACT,QAAQ,GAIT;IACC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,GAAG,UAAU,CACnD,CAAC,CAAc,EAAE,IAAiB,EAAE,EAAE,CAAC,IAAI,EAC3C,OAAsB,CACvB,CAAA;IAED,MAAM,GAAG,GAAG,MAAM,CAAuB,IAAI,CAAC,CAAA;IAC9C,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAA;QAChG,+EAA+E;QAC/E,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;QACzD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QACnC,GAAG,CAAC,OAAO,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAc,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAQ,CAAA;QAC3B,OAAO;YACL,MAAM;YACN,SAAS;YACT,EAAE,EAAE,EAAE;YACN,WAAW;YACX,cAAc,EAAE,mBAAmB;YACnC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;gBACb,MAAM,MAAM,GAAG,EAAE,EAAE,CAAA;gBACnB,IAAI,EAAE,CAAA;gBACN,OAAO,MAAM,CAAA;YACf,CAAC;SACF,CAAA;QACD,yEAAyE;QACzE,oEAAoE;IACtE,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC,CAAA;IAElC,OAAO,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA0B,CAAA;AAClF,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,CAAA","sourcesContent":["import { useMemo, useReducer, useRef } from 'react'\nimport type { ReactNode } from 'react'\nimport { Collaboration, createSequentialClock, createSequentialIds } from '@elytracms/core/collaboration'\nimport type { Actor, PreviewMode } from '@elytracms/core/collaboration'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\nimport { seedCollabSession } from './collab-seed'\n\n/**\n * Per-project collaboration session (Phase 10). Holds one {@link Collaboration} store seeded with\n * an initial version + presence for the current user, and exposes a `mutate` helper that re-renders\n * consumers after an imperative store change. Non-persistent (session): it is the builder canvas's\n * session-scoped presence/perspective store. EC-287 retired the project graph and its publish path,\n * so the seed no longer reflects a pinned graph revision; content publishing is per document\n * (EC-224), surfaced by the CMS document Versions pane.\n */\nexport interface CollabStore {\n collab: Collaboration\n projectId: string\n me: Actor\n /** Draft vs published perspective the canvas/preview renders (EC-123). */\n previewMode: PreviewMode\n setPreviewMode: (mode: PreviewMode) => void\n /** Run an imperative store mutation, then re-render consumers. */\n mutate: <T>(fn: () => T) => T\n}\n\nconst { Context: CollabContext, useRequired: useCollab } = createRequiredContext<CollabStore>('Collab')\n\nconst ME: Actor = { id: 'u-you', name: 'You' }\n\nexport function CollabProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const [tick, bump] = useReducer((n: number) => n + 1, 0)\n const [previewMode, setPreviewModeState] = useReducer(\n (_: PreviewMode, next: PreviewMode) => next,\n 'draft' as PreviewMode,\n )\n\n const ref = useRef<Collaboration | null>(null)\n if (ref.current === null) {\n const collab = new Collaboration({ clock: createSequentialClock(), ids: createSequentialIds() })\n // Seed one empty Initial version + presence for the current user (EC-122/126).\n seedCollabSession(collab, projectId, { author: ME.name })\n collab.presence.join(projectId, ME)\n ref.current = collab\n }\n\n const store = useMemo<CollabStore>(() => {\n const collab = ref.current!\n return {\n collab,\n projectId,\n me: ME,\n previewMode,\n setPreviewMode: setPreviewModeState,\n mutate: (fn) => {\n const result = fn()\n bump()\n return result\n },\n }\n // `tick` (bumped by `mutate`) and `previewMode` are in deps so consumers\n // re-render after an imperative store mutation or a preview change.\n }, [projectId, previewMode, tick])\n\n return <CollabContext.Provider value={store}>{children}</CollabContext.Provider>\n}\n\nexport { useCollab }\n"]}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source';
|
|
3
|
+
/**
|
|
4
|
+
* The React seam for the portable list + edit surfaces (EC-240 / AD-19). The
|
|
5
|
+
* surfaces and their nested pieces (view bar, cells, filter builder, field
|
|
6
|
+
* form) read their {@link CollectionViewSource} from THIS context instead of the
|
|
7
|
+
* concrete `useCms()`, so the SAME components render over the CMS store, the
|
|
8
|
+
* users rail, or any future rail.
|
|
9
|
+
*/
|
|
10
|
+
declare const useCollectionView: () => CollectionViewSource;
|
|
3
11
|
export declare function CollectionViewProvider({ source, children, }: {
|
|
4
12
|
source: CollectionViewSource;
|
|
5
13
|
children: ReactNode;
|
|
6
14
|
}): import("react").JSX.Element;
|
|
7
15
|
/** The active {@link CollectionViewSource}. Throws if used outside a provider. */
|
|
8
|
-
export
|
|
16
|
+
export { useCollectionView };
|
|
9
17
|
/** The active source, or `null` outside a provider (optional consumers). */
|
|
10
18
|
export declare function useOptionalCollectionView(): CollectionViewSource | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { createRequiredContext } from '@elytracms/studio/lib/required-context';
|
|
3
4
|
/**
|
|
4
5
|
* The React seam for the portable list + edit surfaces (EC-240 / AD-19). The
|
|
5
6
|
* surfaces and their nested pieces (view bar, cells, filter builder, field
|
|
@@ -7,18 +8,12 @@ import { createContext, useContext } from 'react';
|
|
|
7
8
|
* concrete `useCms()`, so the SAME components render over the CMS store, the
|
|
8
9
|
* users rail, or any future rail.
|
|
9
10
|
*/
|
|
10
|
-
const CollectionViewContext =
|
|
11
|
+
const { Context: CollectionViewContext, useRequired: useCollectionView } = createRequiredContext('CollectionView');
|
|
11
12
|
export function CollectionViewProvider({ source, children, }) {
|
|
12
13
|
return _jsx(CollectionViewContext.Provider, { value: source, children: children });
|
|
13
14
|
}
|
|
14
15
|
/** The active {@link CollectionViewSource}. Throws if used outside a provider. */
|
|
15
|
-
export
|
|
16
|
-
const source = useContext(CollectionViewContext);
|
|
17
|
-
if (!source) {
|
|
18
|
-
throw new Error('useCollectionView must be used within a <CollectionViewProvider>');
|
|
19
|
-
}
|
|
20
|
-
return source;
|
|
21
|
-
}
|
|
16
|
+
export { useCollectionView };
|
|
22
17
|
/** The active source, or `null` outside a provider (optional consumers). */
|
|
23
18
|
export function useOptionalCollectionView() {
|
|
24
19
|
return useContext(CollectionViewContext);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection-view-context.js","sourceRoot":"","sources":["../../../src/components/collection-view/collection-view-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"collection-view-context.js","sourceRoot":"","sources":["../../../src/components/collection-view/collection-view-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAA;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAE9E;;;;;;GAMG;AACH,MAAM,EAAE,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,iBAAiB,EAAE,GACtE,qBAAqB,CAAuB,gBAAgB,CAAC,CAAA;AAE/D,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,GAIT;IACC,OAAO,KAAC,qBAAqB,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,YAAG,QAAQ,GAAkC,CAAA;AACnG,CAAC;AAED,kFAAkF;AAClF,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAE5B,4EAA4E;AAC5E,MAAM,UAAU,yBAAyB;IACvC,OAAO,UAAU,CAAC,qBAAqB,CAAC,CAAA;AAC1C,CAAC","sourcesContent":["import { useContext, type ReactNode } from 'react'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\n/**\n * The React seam for the portable list + edit surfaces (EC-240 / AD-19). The\n * surfaces and their nested pieces (view bar, cells, filter builder, field\n * form) read their {@link CollectionViewSource} from THIS context instead of the\n * concrete `useCms()`, so the SAME components render over the CMS store, the\n * users rail, or any future rail.\n */\nconst { Context: CollectionViewContext, useRequired: useCollectionView } =\n createRequiredContext<CollectionViewSource>('CollectionView')\n\nexport function CollectionViewProvider({\n source,\n children,\n}: {\n source: CollectionViewSource\n children: ReactNode\n}) {\n return <CollectionViewContext.Provider value={source}>{children}</CollectionViewContext.Provider>\n}\n\n/** The active {@link CollectionViewSource}. Throws if used outside a provider. */\nexport { useCollectionView }\n\n/** The active source, or `null` outside a provider (optional consumers). */\nexport function useOptionalCollectionView(): CollectionViewSource | null {\n return useContext(CollectionViewContext)\n}\n"]}
|
|
@@ -12,7 +12,7 @@ import { ReferencesPanel } from '@elytracms/studio/components/references/referen
|
|
|
12
12
|
import { InspectorSidebar } from '@elytracms/studio/components/inspector/inspector-sidebar';
|
|
13
13
|
import { useSetBreadcrumb } from '@elytracms/studio/components/breadcrumb-context';
|
|
14
14
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
15
|
-
import { Badge, Button, Code, Heading, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
15
|
+
import { Badge, Button, Code, Heading, Row, Spacer, Stack, TabButton, Text } from '@elytracms/studio/ui';
|
|
16
16
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
17
17
|
/**
|
|
18
18
|
* The component detail view (EC-050/052, overhauled in EC-320) — now on the CMS
|
|
@@ -56,9 +56,6 @@ function ComponentDetailBody({ projectId, studio, manifest, }) {
|
|
|
56
56
|
render: () => _jsx(SlotsInfoPane, { manifest: manifest }),
|
|
57
57
|
},
|
|
58
58
|
];
|
|
59
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: studioPath.components() }, className: "inline-flex items-center gap-1 px-4 pt-3 text-xs text-muted-foreground transition-colors hover:text-foreground md:hidden", children: [_jsx(ArrowLeft, { className: "size-3.5" }), " ", translate('components.detail.allComponents')] }), _jsxs("div", { className: "shrink-0 border-b border-border px-4 py-3 sm:px-6", children: [_jsxs(Row, { gap: "3", children: [_jsxs(Row, { gap: "2", align: "center", className: "min-w-0 flex-wrap", children: [_jsx(Heading, { level: 1, size: "h2", className: "truncate", children: title }), _jsx(Badge, { variant: namespace === 'project' ? 'primary' : 'outline', children: namespace }), manifest.category ? _jsx(Badge, { variant: "outline", children: manifest.category }) : null] }), _jsx(Spacer, {}), _jsx(ComponentStatusBadge, { status: status }), _jsxs(Button, { size: "sm", onClick: () => setTransferOpen(true), "data-testid": "use-in-document", children: [_jsx(FilePlus2, {}), " ", translate('components.transfer.action')] })] }), _jsx(Row, { gap: "2", align: "center", className: "mt-1", children: _jsx(Code, { tone: "muted", children: manifest.id }) }), manifest.description ? (_jsx(Text, { tone: "muted", size: "sm", className: "mt-1 max-w-prose", children: manifest.description })) : null, status !== 'valid' ? (_jsx(Text, { size: "sm", className: cn('mt-1', status === 'invalid' ? 'text-destructive' : 'text-warning'), children: statusDescription(status) })) : null, _jsxs(Row, { gap: "1", align: "center", className: "mt-3", children: [
|
|
60
|
-
}
|
|
61
|
-
function SurfaceTab({ label, icon, active, onClick, }) {
|
|
62
|
-
return (_jsxs("button", { type: "button", "aria-pressed": active, onClick: onClick, className: cn('inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors', active ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-muted'), children: [icon, label] }));
|
|
59
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: studioPath.components() }, className: "inline-flex items-center gap-1 px-4 pt-3 text-xs text-muted-foreground transition-colors hover:text-foreground md:hidden", children: [_jsx(ArrowLeft, { className: "size-3.5" }), " ", translate('components.detail.allComponents')] }), _jsxs("div", { className: "shrink-0 border-b border-border px-4 py-3 sm:px-6", children: [_jsxs(Row, { gap: "3", children: [_jsxs(Row, { gap: "2", align: "center", className: "min-w-0 flex-wrap", children: [_jsx(Heading, { level: 1, size: "h2", className: "truncate", children: title }), _jsx(Badge, { variant: namespace === 'project' ? 'primary' : 'outline', children: namespace }), manifest.category ? _jsx(Badge, { variant: "outline", children: manifest.category }) : null] }), _jsx(Spacer, {}), _jsx(ComponentStatusBadge, { status: status }), _jsxs(Button, { size: "sm", onClick: () => setTransferOpen(true), "data-testid": "use-in-document", children: [_jsx(FilePlus2, {}), " ", translate('components.transfer.action')] })] }), _jsx(Row, { gap: "2", align: "center", className: "mt-1", children: _jsx(Code, { tone: "muted", children: manifest.id }) }), manifest.description ? (_jsx(Text, { tone: "muted", size: "sm", className: "mt-1 max-w-prose", children: manifest.description })) : null, status !== 'valid' ? (_jsx(Text, { size: "sm", className: cn('mt-1', status === 'invalid' ? 'text-destructive' : 'text-warning'), children: statusDescription(status) })) : null, _jsxs(Row, { gap: "1", align: "center", className: "mt-3", children: [_jsxs(TabButton, { active: tab === 'preview', onClick: () => setTab('preview'), children: [_jsx(Play, { className: "size-3" }), translate('components.detail.tab.preview')] }), _jsxs(TabButton, { active: tab === 'manifest', onClick: () => setTab('manifest'), children: [_jsx(ListTree, { className: "size-3" }), translate('components.detail.tab.manifest')] }), _jsx("div", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": true }), _jsxs(TabButton, { active: tab === 'references', onClick: () => setTab('references'), children: [_jsx(Link2, { className: "size-3" }), translate('references.title')] })] })] }), tab === 'preview' ? (_jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: _jsx(ComponentPreview, { manifest: manifest, studio: studio, state: preview }) }), _jsx(InspectorSidebar, { panes: panes, className: "w-[320px] shrink-0 border-l border-border" })] })) : tab === 'manifest' ? (_jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4 sm:p-6", children: _jsx("div", { className: "mx-auto w-full max-w-4xl", children: _jsxs(Stack, { gap: "6", children: [_jsx(FieldInspector, { manifest: manifest }), manifest.designTokens && manifest.designTokens.length > 0 ? (_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", mono: true, children: translate('components.detail.designTokens') }), _jsx(Row, { gap: "1", wrap: true, children: manifest.designTokens.map((token) => (_jsx(Code, { tone: "muted", children: token }, token))) })] })) : null] }) }) })) : (_jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4 sm:p-6", children: _jsx("div", { className: "mx-auto w-full max-w-2xl", children: _jsx(ReferencesPanel, { projectId: projectId, target: { type: 'component', id: manifest.id } }) }) })), _jsx(TransferToDocumentDialog, { projectId: projectId, manifest: manifest, node: preview.node, open: transferOpen, onOpenChange: setTransferOpen })] }));
|
|
63
60
|
}
|
|
64
61
|
//# sourceMappingURL=component-detail.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-detail.js","sourceRoot":"","sources":["../../../src/components/components/component-detail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { ArrowLeft, FilePlus2, Link2, ListTree, Play, SlidersHorizontal, SquareStack } from 'lucide-react'\nimport { isPrimitive } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport {\n ComponentStatusBadge,\n statusDescription,\n} from '@elytracms/studio/components/components/component-status'\nimport { FieldInspector } from '@elytracms/studio/components/components/field-inspector'\nimport {\n ComponentPreview,\n PropsEditorPane,\n SlotsInfoPane,\n usePreviewState,\n} from '@elytracms/studio/components/components/component-preview'\nimport { TransferToDocumentDialog } from '@elytracms/studio/components/components/transfer-dialog'\nimport { ReferencesPanel } from '@elytracms/studio/components/references/references-panel'\nimport { InspectorSidebar } from '@elytracms/studio/components/inspector/inspector-sidebar'\nimport type { InspectorPane } from '@elytracms/studio/components/inspector/inspector-sidebar'\nimport { useSetBreadcrumb } from '@elytracms/studio/components/breadcrumb-context'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Badge, Button, Code, Heading, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * The component detail view (EC-050/052, overhauled in EC-320) — now on the CMS\n * shell: a header (title · namespace/category badges · id · status), a lens row of\n * tabs (Preview / Manifest / References — References-as-a-tab, EC-269), and, on the\n * Preview tab, the live editable preview beside the shared right {@link InspectorSidebar}\n * (Props / Slots). \"Use in document…\" transfers the current combination into a draft.\n */\nexport function ComponentDetail({\n projectId,\n componentId,\n}: {\n projectId: string\n componentId: string\n}) {\n const studio = getStudioRegistry()\n const manifest = studio.registry.getManifest(componentId)\n\n // EC-214: the component is the active record — name it in the global header\n // breadcrumb (under the Components section crumb). Unconditional (hooks rules).\n useSetBreadcrumb([{ label: manifest?.title ?? manifest?.id ?? componentId }])\n\n if (!manifest) {\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading level={2} size=\"h2\">\n {translate('components.detail.notFound.title')}\n </Heading>\n <Text tone=\"muted\">{translate('components.detail.notFound.body', { id: componentId })}</Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.components() }}\n className=\"text-sm text-primary hover:underline\"\n >\n {translate('components.detail.notFound.back')}\n </Link>\n </Stack>\n </div>\n )\n }\n\n return <ComponentDetailBody key={manifest.id} projectId={projectId} studio={studio} manifest={manifest} />\n}\n\ntype DetailTab = 'preview' | 'manifest' | 'references'\n\nfunction ComponentDetailBody({\n projectId,\n studio,\n manifest,\n}: {\n projectId: string\n studio: StudioRegistry\n manifest: ComponentManifest\n}) {\n const preview = usePreviewState(manifest)\n const [tab, setTab] = useState<DetailTab>('preview')\n const [transferOpen, setTransferOpen] = useState(false)\n\n const status = componentStatus(studio, manifest)\n const namespace = isPrimitive(manifest.id) ? 'base' : 'project'\n const title = manifest.title ?? manifest.id\n\n // The Preview tab's right inspector: editable Props + declared Slots. Shares the\n // exact frame the document editor uses (EC-224), so a prop edit re-renders the\n // preview live through the same control set documents use.\n const panes: InspectorPane[] = [\n {\n id: 'props',\n label: translate('components.fieldInspector.props'),\n icon: <SlidersHorizontal className=\"size-3.5\" />,\n render: () => <PropsEditorPane manifest={manifest} state={preview} />,\n },\n {\n id: 'slots',\n label: translate('components.fieldInspector.slots'),\n icon: <SquareStack className=\"size-3.5\" />,\n render: () => <SlotsInfoPane manifest={manifest} />,\n },\n ]\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n {/* Mobile-only back link (the registry sidebar is hidden under md). */}\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.components() }}\n className=\"inline-flex items-center gap-1 px-4 pt-3 text-xs text-muted-foreground transition-colors hover:text-foreground md:hidden\"\n >\n <ArrowLeft className=\"size-3.5\" /> {translate('components.detail.allComponents')}\n </Link>\n\n {/* Header */}\n <div className=\"shrink-0 border-b border-border px-4 py-3 sm:px-6\">\n <Row gap=\"3\">\n <Row gap=\"2\" align=\"center\" className=\"min-w-0 flex-wrap\">\n <Heading level={1} size=\"h2\" className=\"truncate\">\n {title}\n </Heading>\n <Badge variant={namespace === 'project' ? 'primary' : 'outline'}>{namespace}</Badge>\n {manifest.category ? <Badge variant=\"outline\">{manifest.category}</Badge> : null}\n </Row>\n <Spacer />\n <ComponentStatusBadge status={status} />\n <Button size=\"sm\" onClick={() => setTransferOpen(true)} data-testid=\"use-in-document\">\n <FilePlus2 /> {translate('components.transfer.action')}\n </Button>\n </Row>\n <Row gap=\"2\" align=\"center\" className=\"mt-1\">\n <Code tone=\"muted\">{manifest.id}</Code>\n </Row>\n {manifest.description ? (\n <Text tone=\"muted\" size=\"sm\" className=\"mt-1 max-w-prose\">\n {manifest.description}\n </Text>\n ) : null}\n {status !== 'valid' ? (\n <Text size=\"sm\" className={cn('mt-1', status === 'invalid' ? 'text-destructive' : 'text-warning')}>\n {statusDescription(status)}\n </Text>\n ) : null}\n\n {/* Lens row — the tab set (mirrors the document editor's lens row). */}\n <Row gap=\"1\" align=\"center\" className=\"mt-3\">\n <SurfaceTab\n label={translate('components.detail.tab.preview')}\n icon={<Play className=\"size-3\" />}\n active={tab === 'preview'}\n onClick={() => setTab('preview')}\n />\n <SurfaceTab\n label={translate('components.detail.tab.manifest')}\n icon={<ListTree className=\"size-3\" />}\n active={tab === 'manifest'}\n onClick={() => setTab('manifest')}\n />\n <div className=\"mx-1 h-4 w-px bg-border\" aria-hidden />\n <SurfaceTab\n label={translate('references.title')}\n icon={<Link2 className=\"size-3\" />}\n active={tab === 'references'}\n onClick={() => setTab('references')}\n />\n </Row>\n </div>\n\n {/* Body */}\n {tab === 'preview' ? (\n <div className=\"flex min-h-0 flex-1 overflow-hidden\">\n <div className=\"min-h-0 flex-1 overflow-hidden\">\n <ComponentPreview manifest={manifest} studio={studio} state={preview} />\n </div>\n <InspectorSidebar panes={panes} className=\"w-[320px] shrink-0 border-l border-border\" />\n </div>\n ) : tab === 'manifest' ? (\n <div className=\"min-h-0 flex-1 overflow-auto p-4 sm:p-6\">\n <div className=\"mx-auto w-full max-w-4xl\">\n <Stack gap=\"6\">\n <FieldInspector manifest={manifest} />\n {manifest.designTokens && manifest.designTokens.length > 0 ? (\n <Stack gap=\"1\">\n <Text size=\"xs\" tone=\"muted\" mono>\n {translate('components.detail.designTokens')}\n </Text>\n <Row gap=\"1\" wrap>\n {manifest.designTokens.map((token) => (\n <Code key={token} tone=\"muted\">\n {token}\n </Code>\n ))}\n </Row>\n </Stack>\n ) : null}\n </Stack>\n </div>\n </div>\n ) : (\n // EC-172/EC-269: inbound references over the reference index, as a tab.\n <div className=\"min-h-0 flex-1 overflow-auto p-4 sm:p-6\">\n <div className=\"mx-auto w-full max-w-2xl\">\n <ReferencesPanel projectId={projectId} target={{ type: 'component', id: manifest.id }} />\n </div>\n </div>\n )}\n\n <TransferToDocumentDialog\n projectId={projectId}\n manifest={manifest}\n node={preview.node}\n open={transferOpen}\n onOpenChange={setTransferOpen}\n />\n </div>\n )\n}\n\nfunction SurfaceTab({\n label,\n icon,\n active,\n onClick,\n}: {\n label: string\n icon: ReactNode\n active: boolean\n onClick: () => void\n}) {\n return (\n <button\n type=\"button\"\n aria-pressed={active}\n onClick={onClick}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors',\n active ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-muted',\n )}\n >\n {icon}\n {label}\n </button>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"component-detail.js","sourceRoot":"","sources":["../../../src/components/components/component-detail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport { ArrowLeft, FilePlus2, Link2, ListTree, Play, SlidersHorizontal, SquareStack } from 'lucide-react'\nimport { isPrimitive } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport {\n ComponentStatusBadge,\n statusDescription,\n} from '@elytracms/studio/components/components/component-status'\nimport { FieldInspector } from '@elytracms/studio/components/components/field-inspector'\nimport {\n ComponentPreview,\n PropsEditorPane,\n SlotsInfoPane,\n usePreviewState,\n} from '@elytracms/studio/components/components/component-preview'\nimport { TransferToDocumentDialog } from '@elytracms/studio/components/components/transfer-dialog'\nimport { ReferencesPanel } from '@elytracms/studio/components/references/references-panel'\nimport { InspectorSidebar } from '@elytracms/studio/components/inspector/inspector-sidebar'\nimport type { InspectorPane } from '@elytracms/studio/components/inspector/inspector-sidebar'\nimport { useSetBreadcrumb } from '@elytracms/studio/components/breadcrumb-context'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Badge, Button, Code, Heading, Row, Spacer, Stack, TabButton, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * The component detail view (EC-050/052, overhauled in EC-320) — now on the CMS\n * shell: a header (title · namespace/category badges · id · status), a lens row of\n * tabs (Preview / Manifest / References — References-as-a-tab, EC-269), and, on the\n * Preview tab, the live editable preview beside the shared right {@link InspectorSidebar}\n * (Props / Slots). \"Use in document…\" transfers the current combination into a draft.\n */\nexport function ComponentDetail({\n projectId,\n componentId,\n}: {\n projectId: string\n componentId: string\n}) {\n const studio = getStudioRegistry()\n const manifest = studio.registry.getManifest(componentId)\n\n // EC-214: the component is the active record — name it in the global header\n // breadcrumb (under the Components section crumb). Unconditional (hooks rules).\n useSetBreadcrumb([{ label: manifest?.title ?? manifest?.id ?? componentId }])\n\n if (!manifest) {\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading level={2} size=\"h2\">\n {translate('components.detail.notFound.title')}\n </Heading>\n <Text tone=\"muted\">{translate('components.detail.notFound.body', { id: componentId })}</Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.components() }}\n className=\"text-sm text-primary hover:underline\"\n >\n {translate('components.detail.notFound.back')}\n </Link>\n </Stack>\n </div>\n )\n }\n\n return <ComponentDetailBody key={manifest.id} projectId={projectId} studio={studio} manifest={manifest} />\n}\n\ntype DetailTab = 'preview' | 'manifest' | 'references'\n\nfunction ComponentDetailBody({\n projectId,\n studio,\n manifest,\n}: {\n projectId: string\n studio: StudioRegistry\n manifest: ComponentManifest\n}) {\n const preview = usePreviewState(manifest)\n const [tab, setTab] = useState<DetailTab>('preview')\n const [transferOpen, setTransferOpen] = useState(false)\n\n const status = componentStatus(studio, manifest)\n const namespace = isPrimitive(manifest.id) ? 'base' : 'project'\n const title = manifest.title ?? manifest.id\n\n // The Preview tab's right inspector: editable Props + declared Slots. Shares the\n // exact frame the document editor uses (EC-224), so a prop edit re-renders the\n // preview live through the same control set documents use.\n const panes: InspectorPane[] = [\n {\n id: 'props',\n label: translate('components.fieldInspector.props'),\n icon: <SlidersHorizontal className=\"size-3.5\" />,\n render: () => <PropsEditorPane manifest={manifest} state={preview} />,\n },\n {\n id: 'slots',\n label: translate('components.fieldInspector.slots'),\n icon: <SquareStack className=\"size-3.5\" />,\n render: () => <SlotsInfoPane manifest={manifest} />,\n },\n ]\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n {/* Mobile-only back link (the registry sidebar is hidden under md). */}\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.components() }}\n className=\"inline-flex items-center gap-1 px-4 pt-3 text-xs text-muted-foreground transition-colors hover:text-foreground md:hidden\"\n >\n <ArrowLeft className=\"size-3.5\" /> {translate('components.detail.allComponents')}\n </Link>\n\n {/* Header */}\n <div className=\"shrink-0 border-b border-border px-4 py-3 sm:px-6\">\n <Row gap=\"3\">\n <Row gap=\"2\" align=\"center\" className=\"min-w-0 flex-wrap\">\n <Heading level={1} size=\"h2\" className=\"truncate\">\n {title}\n </Heading>\n <Badge variant={namespace === 'project' ? 'primary' : 'outline'}>{namespace}</Badge>\n {manifest.category ? <Badge variant=\"outline\">{manifest.category}</Badge> : null}\n </Row>\n <Spacer />\n <ComponentStatusBadge status={status} />\n <Button size=\"sm\" onClick={() => setTransferOpen(true)} data-testid=\"use-in-document\">\n <FilePlus2 /> {translate('components.transfer.action')}\n </Button>\n </Row>\n <Row gap=\"2\" align=\"center\" className=\"mt-1\">\n <Code tone=\"muted\">{manifest.id}</Code>\n </Row>\n {manifest.description ? (\n <Text tone=\"muted\" size=\"sm\" className=\"mt-1 max-w-prose\">\n {manifest.description}\n </Text>\n ) : null}\n {status !== 'valid' ? (\n <Text size=\"sm\" className={cn('mt-1', status === 'invalid' ? 'text-destructive' : 'text-warning')}>\n {statusDescription(status)}\n </Text>\n ) : null}\n\n {/* Lens row — the tab set (mirrors the document editor's lens row). */}\n <Row gap=\"1\" align=\"center\" className=\"mt-3\">\n <TabButton active={tab === 'preview'} onClick={() => setTab('preview')}>\n <Play className=\"size-3\" />\n {translate('components.detail.tab.preview')}\n </TabButton>\n <TabButton active={tab === 'manifest'} onClick={() => setTab('manifest')}>\n <ListTree className=\"size-3\" />\n {translate('components.detail.tab.manifest')}\n </TabButton>\n <div className=\"mx-1 h-4 w-px bg-border\" aria-hidden />\n <TabButton active={tab === 'references'} onClick={() => setTab('references')}>\n <Link2 className=\"size-3\" />\n {translate('references.title')}\n </TabButton>\n </Row>\n </div>\n\n {/* Body */}\n {tab === 'preview' ? (\n <div className=\"flex min-h-0 flex-1 overflow-hidden\">\n <div className=\"min-h-0 flex-1 overflow-hidden\">\n <ComponentPreview manifest={manifest} studio={studio} state={preview} />\n </div>\n <InspectorSidebar panes={panes} className=\"w-[320px] shrink-0 border-l border-border\" />\n </div>\n ) : tab === 'manifest' ? (\n <div className=\"min-h-0 flex-1 overflow-auto p-4 sm:p-6\">\n <div className=\"mx-auto w-full max-w-4xl\">\n <Stack gap=\"6\">\n <FieldInspector manifest={manifest} />\n {manifest.designTokens && manifest.designTokens.length > 0 ? (\n <Stack gap=\"1\">\n <Text size=\"xs\" tone=\"muted\" mono>\n {translate('components.detail.designTokens')}\n </Text>\n <Row gap=\"1\" wrap>\n {manifest.designTokens.map((token) => (\n <Code key={token} tone=\"muted\">\n {token}\n </Code>\n ))}\n </Row>\n </Stack>\n ) : null}\n </Stack>\n </div>\n </div>\n ) : (\n // EC-172/EC-269: inbound references over the reference index, as a tab.\n <div className=\"min-h-0 flex-1 overflow-auto p-4 sm:p-6\">\n <div className=\"mx-auto w-full max-w-2xl\">\n <ReferencesPanel projectId={projectId} target={{ type: 'component', id: manifest.id }} />\n </div>\n </div>\n )}\n\n <TransferToDocumentDialog\n projectId={projectId}\n manifest={manifest}\n node={preview.node}\n open={transferOpen}\n onOpenChange={setTransferOpen}\n />\n </div>\n )\n}\n"]}
|
|
@@ -6,7 +6,7 @@ import { renderNode } from '@elytracms/core/runtime-renderer';
|
|
|
6
6
|
import { staticProp } from '@elytracms/core/project-graph';
|
|
7
7
|
import { componentStatus, previewContext } from '@elytracms/studio/lib/registry';
|
|
8
8
|
import { previewSpecs } from '@elytracms/studio/lib/registry/preview';
|
|
9
|
-
import { Badge, Button, Code, Field, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
9
|
+
import { Badge, Button, Code, Field, Row, Spacer, Stack, TabButton, Text } from '@elytracms/studio/ui';
|
|
10
10
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
11
11
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
12
12
|
import { getCanvasStylesheetUrl } from '../builder/canvas-stylesheet';
|
|
@@ -71,15 +71,19 @@ export function ComponentPreview({ manifest, studio, state, }) {
|
|
|
71
71
|
// Defaults to the always-present 'full' (responsive) option.
|
|
72
72
|
const { viewports } = useCanvasViewports();
|
|
73
73
|
const [viewport, setViewport] = useState(viewports[0].key);
|
|
74
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs(Row, { gap: "1", wrap: true, align: "center", className: "shrink-0 border-b border-border px-3 py-2", children: [state.specs.length > 1 ? (_jsx(Row, { gap: "1", wrap: true, role: "group", "aria-label": translate('components.fixtureRunner.statesAria'), children: state.specs.map((spec, i) => (_jsx("
|
|
75
|
-
? 'bg-accent text-accent-foreground'
|
|
76
|
-
: 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: spec.name }, spec.name))) })) : null, _jsx(Spacer, {}), _jsx(Row, { gap: "1", wrap: true, role: "group", "aria-label": translate('components.preview.breakpointAria'), children: viewports.map((option) => (_jsx(PreviewBreakpointButton, { option: option, active: option.key === viewport, onSelect: () => setViewport(option.key) }, option.key))) }), state.dirty ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: state.resetAll, "data-testid": "preview-reset", children: [_jsx(RotateCcw, {}), " ", translate('components.preview.reset')] })) : null] }), _jsxs("div", { className: "canvas-grid min-h-0 flex-1 overflow-auto p-6", "data-fixture-state": state.specs[state.index]?.name, children: [serverOnly ? (_jsx(ServerOnlyNotice, {})) : (_jsx(ComponentPreviewFrame, { node: state.node, ctx: ctx, displayName: displayName, width: viewportWidth(viewports, viewport) })), status === 'incompatible' && !serverOnly ? (_jsx(Text, { size: "xs", tone: "muted", className: "mx-auto mt-3 max-w-2xl", children: translate('components.preview.incompatibleNote') })) : null] })] }));
|
|
74
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs(Row, { gap: "1", wrap: true, align: "center", className: "shrink-0 border-b border-border px-3 py-2", children: [state.specs.length > 1 ? (_jsx(Row, { gap: "1", wrap: true, role: "group", "aria-label": translate('components.fixtureRunner.statesAria'), children: state.specs.map((spec, i) => (_jsx(TabButton, { active: i === state.index, onClick: () => state.setIndex(i), children: spec.name }, spec.name))) })) : null, _jsx(Spacer, {}), _jsx(Row, { gap: "1", wrap: true, role: "group", "aria-label": translate('components.preview.breakpointAria'), children: viewports.map((option) => (_jsx(PreviewBreakpointButton, { option: option, active: option.key === viewport, onSelect: () => setViewport(option.key) }, option.key))) }), state.dirty ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: state.resetAll, "data-testid": "preview-reset", children: [_jsx(RotateCcw, {}), " ", translate('components.preview.reset')] })) : null] }), _jsxs("div", { className: "canvas-grid min-h-0 flex-1 overflow-auto p-6", "data-fixture-state": state.specs[state.index]?.name, children: [serverOnly ? (_jsx(ServerOnlyNotice, {})) : (_jsx(ComponentPreviewFrame, { node: state.node, ctx: ctx, displayName: displayName, width: viewportWidth(viewports, viewport) })), status === 'incompatible' && !serverOnly ? (_jsx(Text, { size: "xs", tone: "muted", className: "mx-auto mt-3 max-w-2xl", children: translate('components.preview.incompatibleNote') })) : null] })] }));
|
|
77
75
|
}
|
|
78
76
|
/**
|
|
79
77
|
* One breakpoint option in the preview's width switcher. Mirrors the builder
|
|
80
78
|
* canvas toolbar's `ViewportButton` (`composition-field-editor.tsx`) — same
|
|
81
79
|
* `Button`/active-state styling — but drives LOCAL preview state instead of
|
|
82
80
|
* the builder store (this surface has no composition/undo stack).
|
|
81
|
+
*
|
|
82
|
+
* EC-328: evaluated against the shared `TabButton`/`SegmentedControl`
|
|
83
|
+
* primitives and kept separate — it's built on the shared `Button` (not a
|
|
84
|
+
* bare hand-rolled `<button>`, so it wasn't part of the duplication those
|
|
85
|
+
* primitives address), and carries an interpolated `aria-label` plus a
|
|
86
|
+
* conditional px-value `title` tooltip neither primitive's terse API models.
|
|
83
87
|
*/
|
|
84
88
|
function PreviewBreakpointButton({ option, active, onSelect, }) {
|
|
85
89
|
return (_jsx(Button, { size: "sm", variant: "ghost", "aria-label": translate('builder.canvas.viewportPreviewAria', { label: option.label }), "aria-pressed": active, title: option.px !== undefined
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-preview.js","sourceRoot":"","sources":["../../../src/components/components/component-preview.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { MonitorOff, RotateCcw } from 'lucide-react'\nimport { renderNode } from '@elytracms/core/runtime-renderer'\nimport type { RenderContext } from '@elytracms/core/runtime-renderer'\nimport { staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport { componentStatus, previewContext } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { previewSpecs } from '@elytracms/studio/lib/registry/preview'\nimport type { PreviewSpec } from '@elytracms/studio/lib/registry/preview'\nimport { Badge, Button, Code, Field, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { getCanvasStylesheetUrl } from '../builder/canvas-stylesheet'\nimport { useCanvasViewports, viewportWidth } from '../builder/canvas-breakpoints'\nimport type { ViewportOption } from '../builder/canvas-breakpoints'\nimport { PropValueControl } from '../cms/prop-fields'\n\n/**\n * EC-320 — the editable component preview. A component's declared preview states\n * (fixtures) select the SAMPLE STATE (slot content, base props); on top of that\n * the user edits individual prop values in the Props sidebar and the preview\n * re-renders live through the SAME runtime-renderer path the Builder and export\n * use (EC-054). Slots follow the selected fixture — full slot-tree editing is out\n * of scope for this reference surface (that's the composition editor's job); the\n * Slots pane documents what a component accepts and which state supplies content.\n */\n\nexport interface PreviewState {\n specs: PreviewSpec[]\n index: number\n setIndex: (i: number) => void\n /** Per-prop static-value overrides applied over the selected fixture. */\n overrides: Record<string, JsonValue>\n setOverride: (name: string, value: JsonValue) => void\n clearOverride: (name: string) => void\n resetAll: () => void\n dirty: boolean\n /** The live node: the selected fixture with prop overrides applied. */\n node: ComponentNode\n}\n\n/** Preview state for a manifest: the fixture selection + the live prop overrides. */\nexport function usePreviewState(manifest: ComponentManifest): PreviewState {\n const specs = useMemo(() => previewSpecs(manifest), [manifest])\n const [index, setIndexRaw] = useState(0)\n const [overrides, setOverrides] = useState<Record<string, JsonValue>>({})\n\n // Switching preview state resets prop edits — they were relative to the old state.\n const setIndex = (i: number) => {\n setIndexRaw(i)\n setOverrides({})\n }\n const setOverride = (name: string, value: JsonValue) =>\n setOverrides((o) => ({ ...o, [name]: value }))\n const clearOverride = (name: string) =>\n setOverrides((o) => {\n const next = { ...o }\n delete next[name]\n return next\n })\n const resetAll = () => setOverrides({})\n\n const base = specs[Math.min(index, specs.length - 1)]?.node\n const node = useMemo<ComponentNode>(() => {\n const fixture = base ?? { id: `preview-${manifest.id}`, componentId: manifest.id, props: {} }\n const props = { ...(fixture.props ?? {}) }\n for (const [name, value] of Object.entries(overrides)) props[name] = staticProp(value)\n return { ...fixture, props }\n }, [base, overrides, manifest.id])\n\n return {\n specs,\n index: Math.min(index, Math.max(specs.length - 1, 0)),\n setIndex,\n overrides,\n setOverride,\n clearOverride,\n resetAll,\n dirty: Object.keys(overrides).length > 0,\n node,\n }\n}\n\n/** Re-id a preview node into the canvas `node-<n>` namespace for embedding (transfer). */\nexport function embedNodeFrom(node: ComponentNode, id: string): ComponentNode {\n return { ...node, id }\n}\n\n/**\n * The live preview surface: the fixture-state selector (when a component declares\n * more than one) plus the rendered node. Server-only components (EC-191\n * `clientRenderable: false`) can't run on the client-rendered canvas, so they show\n * a documented fallback rather than risk a crash; a missing implementation falls\n * back through the renderer itself (the incompatible state).\n */\nexport function ComponentPreview({\n manifest,\n studio,\n state,\n}: {\n manifest: ComponentManifest\n studio: StudioRegistry\n state: PreviewState\n}) {\n const ctx = useMemo(() => previewContext(studio), [studio])\n const serverOnly = manifest.clientRenderable === false\n const status = componentStatus(studio, manifest)\n const displayName = manifest.title ?? manifest.id\n\n // EC-330: the same config-sourced breakpoint set the builder canvas toolbar\n // offers (`canvas-breakpoints.ts`) — reused, not redefined, so a preview and\n // the real canvas never disagree about the site's responsive widths.\n // Defaults to the always-present 'full' (responsive) option.\n const { viewports } = useCanvasViewports()\n const [viewport, setViewport] = useState(viewports[0]!.key)\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <Row gap=\"1\" wrap align=\"center\" className=\"shrink-0 border-b border-border px-3 py-2\">\n {state.specs.length > 1 ? (\n <Row\n gap=\"1\"\n wrap\n role=\"group\"\n aria-label={translate('components.fixtureRunner.statesAria')}\n >\n {state.specs.map((spec, i) => (\n <button\n key={spec.name}\n type=\"button\"\n aria-pressed={i === state.index}\n onClick={() => state.setIndex(i)}\n className={cn(\n 'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',\n i === state.index\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {spec.name}\n </button>\n ))}\n </Row>\n ) : null}\n <Spacer />\n <Row\n gap=\"1\"\n wrap\n role=\"group\"\n aria-label={translate('components.preview.breakpointAria')}\n >\n {viewports.map((option) => (\n <PreviewBreakpointButton\n key={option.key}\n option={option}\n active={option.key === viewport}\n onSelect={() => setViewport(option.key)}\n />\n ))}\n </Row>\n {state.dirty ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={state.resetAll} data-testid=\"preview-reset\">\n <RotateCcw /> {translate('components.preview.reset')}\n </Button>\n ) : null}\n </Row>\n\n <div\n className=\"canvas-grid min-h-0 flex-1 overflow-auto p-6\"\n data-fixture-state={state.specs[state.index]?.name}\n >\n {serverOnly ? (\n <ServerOnlyNotice />\n ) : (\n <ComponentPreviewFrame\n node={state.node}\n ctx={ctx}\n displayName={displayName}\n width={viewportWidth(viewports, viewport)}\n />\n )}\n {status === 'incompatible' && !serverOnly ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mx-auto mt-3 max-w-2xl\">\n {translate('components.preview.incompatibleNote')}\n </Text>\n ) : null}\n </div>\n </div>\n )\n}\n\n/**\n * One breakpoint option in the preview's width switcher. Mirrors the builder\n * canvas toolbar's `ViewportButton` (`composition-field-editor.tsx`) — same\n * `Button`/active-state styling — but drives LOCAL preview state instead of\n * the builder store (this surface has no composition/undo stack).\n */\nfunction PreviewBreakpointButton({\n option,\n active,\n onSelect,\n}: {\n option: ViewportOption\n active: boolean\n onSelect: () => void\n}) {\n return (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.viewportPreviewAria', { label: option.label })}\n aria-pressed={active}\n title={\n option.px !== undefined\n ? `${option.label} · ${option.px}px`\n : translate('builder.canvas.viewportWidthTitle', { label: option.label })\n }\n onClick={onSelect}\n className={cn('px-2 text-xs', active && 'bg-accent text-accent-foreground')}\n >\n {option.label}\n </Button>\n )\n}\n\nfunction ServerOnlyNotice() {\n return (\n <div className=\"mx-auto grid max-w-2xl place-items-center rounded-lg border border-dashed border-border bg-muted/30 p-8 text-center\">\n <Stack gap=\"2\" align=\"center\">\n <MonitorOff className=\"size-5 text-muted-foreground\" aria-hidden />\n <Text size=\"sm\" weight=\"medium\">\n {translate('components.preview.serverOnlyTitle')}\n </Text>\n <Text size=\"xs\" tone=\"muted\" className=\"max-w-sm\">\n {translate('components.preview.serverOnlyBody')}\n </Text>\n </Stack>\n </div>\n )\n}\n\n/** Floor for the auto-height iframe — never collapses to a sliver while empty/loading. */\nconst MIN_PREVIEW_HEIGHT = 360\n\n/**\n * The live preview render surface. Reuses the BuilderCanvas styling mechanism\n * (EC-061, `canvas.tsx`): the component renders inside an ISOLATED same-origin\n * iframe that `<link>`s the project's canvas stylesheet\n * (`getCanvasStylesheetUrl()` — the consuming app's delivery CSS + studio CSS), so\n * a component built with the site's Tailwind theme previews with the site's styles,\n * exactly as it does on the canvas and in delivery. Using the iframe (not the\n * studio DOM) is what keeps the delivery CSS scoped and off the studio chrome.\n *\n * Unlike the builder canvas, this iframe's document is deliberately left OUT of\n * the studio's own dark/light class: this is a standalone-component reference\n * surface (not a full page), so its own negative space around the component must\n * read as a light, site-like backdrop — never the studio chrome's current theme\n * (a black frame is exactly what leaks through when the studio is dark-themed and\n * the iframe root inherits that class). The canvas stylesheet's `:root` (light)\n * tokens supply that backdrop for free once the `.dark` class is never added here.\n *\n * Sizing: width is driven by the caller (the breakpoint selector); height is\n * measured live from the rendered content via `ResizeObserver` (EC-330) so a\n * preset switch or a prop edit — both of which change what's portaled into\n * `doc.body` — always show without clipping or dead space.\n *\n * The render is wrapped in {@link PreviewErrorBoundary} so a component that throws\n * shows the EC-191 fallback instead of taking the whole Components view down.\n */\nfunction ComponentPreviewFrame({\n node,\n ctx,\n displayName,\n width,\n}: {\n node: ComponentNode\n ctx: RenderContext\n displayName: string\n /** CSS width for the iframe — '100%' (responsive) or a fixed breakpoint px width. */\n width: string\n}) {\n const iframeRef = useRef<HTMLIFrameElement>(null)\n const [doc, setDoc] = useState<Document | null>(null)\n const [height, setHeight] = useState(MIN_PREVIEW_HEIGHT)\n\n const wireUp = useCallback(() => {\n const d = iframeRef.current?.contentDocument\n if (!d || !d.body) return\n // Deliberately no `.dark` here (see the surface note above) — the light\n // `:root` tokens from the injected stylesheet are the intended backdrop.\n // Unset stylesheet (tests / an app that never called\n // `setCanvasStylesheetUrl`) → skip the link.\n const appCss = getCanvasStylesheetUrl()\n if (appCss && !d.getElementById('ec-app-css')) {\n const link = d.createElement('link')\n link.id = 'ec-app-css'\n link.rel = 'stylesheet'\n link.href = appCss\n d.head.appendChild(link)\n }\n d.body.className = 'bg-background text-foreground font-sans'\n d.body.style.margin = '0'\n d.body.style.padding = '24px'\n setDoc(d)\n }, [])\n\n // Handle the already-loaded case (srcDoc can load before onLoad attaches).\n useEffect(() => {\n if (iframeRef.current?.contentDocument?.readyState === 'complete') wireUp()\n }, [wireUp])\n\n // Auto-height (EC-330): a same-origin iframe exposes its own document, so a\n // `ResizeObserver` on the BODY tracks its live (height: auto) content size —\n // re-firing on its own whenever the portaled render changes (preset switch,\n // prop edit), no manual re-measure wiring needed. Attached per `doc` (re-runs\n // whenever the iframe reloads), not just on mount. Loop-safety: the observed\n // element (the iframe's body) is never the element this effect resizes (the\n // IFRAME itself) — writing height never feeds back into the observed box, and\n // width is never derived from height, so there is no growth loop. Guarded for\n // environments without ResizeObserver (jsdom/tests, older embeds).\n useEffect(() => {\n const body = doc?.body\n if (!body || typeof ResizeObserver === 'undefined') return\n const measure = () => setHeight(Math.max(body.scrollHeight, MIN_PREVIEW_HEIGHT))\n measure()\n const observer = new ResizeObserver(measure)\n observer.observe(body)\n return () => observer.disconnect()\n }, [doc])\n\n return (\n <>\n <iframe\n ref={iframeRef}\n onLoad={wireUp}\n title={translate('components.detail.tab.preview')}\n style={{ width, height, maxWidth: '100%' }}\n className=\"mx-auto block rounded-lg border border-border bg-background shadow-sm\"\n srcDoc=\"<!doctype html><html><head></head><body></body></html>\"\n />\n {doc\n ? createPortal(\n <PreviewErrorBoundary displayName={displayName} resetKey={node}>\n <PreviewLiveRender node={node} ctx={ctx} />\n </PreviewErrorBoundary>,\n doc.body,\n )\n : null}\n </>\n )\n}\n\n/** Renders the node through the runtime renderer, INSIDE a component render so the\n * error boundary catches synchronous render throws (mirrors the blocks-panel\n * thumbnail's `LiveRender`). */\nfunction PreviewLiveRender({ node, ctx }: { node: ComponentNode; ctx: RenderContext }) {\n return <>{renderNode(node, ctx)}</>\n}\n\ninterface PreviewBoundaryState {\n error: Error | null\n}\n\n/**\n * EC-191 resilience for the Components view: a component whose render throws — a\n * missing provider/context (e.g. a prop control that needs a document's\n * `CollectionViewSource`), a runtime bug — shows the documented \"preview\n * unavailable\" fallback ({@link PreviewUnavailable}) instead of taking the whole\n * view down. It resets when the previewed `resetKey` (the node) changes, so\n * switching the fixture state or editing a prop retries the render.\n */\nexport class PreviewErrorBoundary extends Component<\n { children: ReactNode; displayName: string; resetKey: unknown },\n PreviewBoundaryState\n> {\n override state: PreviewBoundaryState = { error: null }\n\n static getDerivedStateFromError(error: Error): PreviewBoundaryState {\n return { error }\n }\n\n override componentDidUpdate(prev: Readonly<{ resetKey: unknown }>) {\n if (this.state.error && prev.resetKey !== this.props.resetKey) {\n this.setState({ error: null })\n }\n }\n\n override render(): ReactNode {\n if (this.state.error) {\n return <PreviewUnavailable displayName={this.props.displayName} error={this.state.error} />\n }\n return this.props.children\n }\n}\n\n/**\n * The EC-191 fallback panel — component name + \"preview unavailable\" + the error.\n * Rendered INTO the preview iframe (which carries only the project stylesheet, and\n * may carry none in tests), so it uses inline styles rather than studio UI classes\n * — matching the canvas's own in-iframe placeholder convention.\n */\nexport function PreviewUnavailable({ displayName, error }: { displayName: string; error: Error }) {\n return (\n <div\n data-preview-unavailable\n style={{\n boxSizing: 'border-box',\n maxWidth: '42rem',\n margin: '0 auto',\n border: '1px dashed rgba(120,120,120,0.5)',\n borderRadius: 8,\n padding: '24px 28px',\n background: 'rgba(127,127,127,0.06)',\n color: 'rgba(90,90,90,0.95)',\n font: '14px/1.5 system-ui, -apple-system, sans-serif',\n textAlign: 'center',\n }}\n >\n <div style={{ fontWeight: 600 }}>⚠ {translate('components.preview.unavailableTitle')}</div>\n <div style={{ marginTop: 4, fontWeight: 600, opacity: 0.9 }}>{displayName}</div>\n <div style={{ marginTop: 6, fontSize: 13, opacity: 0.85, wordBreak: 'break-word' }}>\n {error.message}\n </div>\n </div>\n )\n}\n\n/**\n * The editable Props pane (right inspector). Every non-`blocks` prop is edited\n * through the shared {@link PropValueControl} — the exact control set documents\n * use (EC-190/EC-314 phase 4) — so a change re-renders the preview immediately.\n * `blocks` props are a canvas (composition) concern, edited in a document, not here.\n */\nexport function PropsEditorPane({\n manifest,\n state,\n}: {\n manifest: ComponentManifest\n state: PreviewState\n}) {\n const editable = Object.entries(manifest.props).filter(([, spec]) => spec.type !== 'blocks')\n const fixture = state.specs[state.index]?.node\n if (editable.length === 0) {\n return (\n <Text size=\"sm\" tone=\"subtle\" className=\"p-4\">\n {translate('components.fieldInspector.noProps')}\n </Text>\n )\n }\n return (\n <Stack gap=\"4\" className=\"p-4\">\n {editable.map(([name, spec]) => {\n const id = `prop-${name}`\n const overridden = state.overrides[name] !== undefined\n return (\n <Field\n key={name}\n label={\n <Row gap=\"2\">\n <span>{localizedLabel((spec as PropField).form?.label, name)}</span>\n {overridden ? <Badge variant=\"info\">{translate('components.preview.edited')}</Badge> : null}\n </Row>\n }\n htmlFor={id}\n hint={localizedLabel((spec as PropField).form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec as PropField}\n value={\n overridden\n ? staticProp(state.overrides[name]!)\n : (fixture?.props?.[name] ?? undefined)\n }\n onSet={(v) => state.setOverride(name, v)}\n onClear={() => state.clearOverride(name)}\n />\n </Field>\n )\n })}\n {state.dirty ? (\n <Button variant=\"outline\" size=\"sm\" onClick={state.resetAll} className=\"self-start\">\n <RotateCcw /> {translate('components.preview.resetProps')}\n </Button>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The Slots pane (right inspector): the component's declared child regions and,\n * for a fixture that supplies slot content, a note that the preview's slot content\n * follows the selected state. Documents the pragmatic scope call — no slot-tree\n * editing on this reference surface.\n */\nexport function SlotsInfoPane({ manifest }: { manifest: ComponentManifest }) {\n if (manifest.slots.length === 0) {\n return (\n <Text size=\"sm\" tone=\"subtle\" className=\"p-4\">\n {translate('components.fieldInspector.noSlots')}\n </Text>\n )\n }\n return (\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('components.preview.slotsNote')}\n </Text>\n <div className=\"divide-y divide-border rounded-md border border-border\">\n {manifest.slots.map((slot) => (\n <div key={slot.name} className=\"flex flex-col gap-1 p-3\">\n <Row gap=\"2\">\n <Code tone=\"primary\">{slot.name}</Code>\n {slot.required ? (\n <Badge variant=\"warning\">{translate('common.required')}</Badge>\n ) : (\n <Badge variant=\"outline\">{translate('common.optional')}</Badge>\n )}\n </Row>\n {slot.allow && slot.allow.length > 0 ? (\n <Row gap=\"2\" className=\"text-xs\" wrap>\n <Text size=\"xs\" tone=\"subtle\">\n {translate('components.fieldInspector.allows')}\n </Text>\n {slot.allow.map((id) => (\n <Code key={id} tone=\"muted\">\n {id}\n </Code>\n ))}\n </Row>\n ) : null}\n </div>\n ))}\n </div>\n </Stack>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"component-preview.js","sourceRoot":"","sources":["../../../src/components/components/component-preview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEpF,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAE7D,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAG1D,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAEhF,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAErE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACtG,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AACrE,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAEjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AA0BrD,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAAC,QAA2B;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC/D,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACxC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAA4B,EAAE,CAAC,CAAA;IAEzE,mFAAmF;IACnF,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE;QAC7B,WAAW,CAAC,CAAC,CAAC,CAAA;QACd,YAAY,CAAC,EAAE,CAAC,CAAA;IAClB,CAAC,CAAA;IACD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,KAAgB,EAAE,EAAE,CACrD,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAChD,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE,CACrC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE;QACjB,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,CAAA;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAA;QACjB,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;IACJ,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;IAEvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAA;IAC3D,MAAM,IAAI,GAAG,OAAO,CAAgB,GAAG,EAAE;QACvC,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,EAAE,EAAE,WAAW,QAAQ,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QAC7F,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAA;QAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;QACtF,OAAO,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAA;IAC9B,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;IAElC,OAAO;QACL,KAAK;QACL,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACrD,QAAQ;QACR,SAAS;QACT,WAAW;QACX,aAAa;QACb,QAAQ;QACR,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC;QACxC,IAAI;KACL,CAAA;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,aAAa,CAAC,IAAmB,EAAE,EAAU;IAC3D,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAA;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,KAAK,GAKN;IACC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAC3D,MAAM,UAAU,GAAG,QAAQ,CAAC,gBAAgB,KAAK,KAAK,CAAA;IACtD,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAChD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,EAAE,CAAA;IAEjD,4EAA4E;IAC5E,6EAA6E;IAC7E,qEAAqE;IACrE,6DAA6D;IAC7D,MAAM,EAAE,SAAS,EAAE,GAAG,kBAAkB,EAAE,CAAA;IAC1C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,GAAG,CAAC,CAAA;IAE3D,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,aAC3C,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,QAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,2CAA2C,aACnF,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACxB,KAAC,GAAG,IACF,GAAG,EAAC,GAAG,EACP,IAAI,QACJ,IAAI,EAAC,OAAO,gBACA,SAAS,CAAC,qCAAqC,CAAC,YAE3D,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAC5B,KAAC,SAAS,IAAiB,MAAM,EAAE,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,YACnF,IAAI,CAAC,IAAI,IADI,IAAI,CAAC,IAAI,CAEb,CACb,CAAC,GACE,CACP,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,MAAM,KAAG,EACV,KAAC,GAAG,IACF,GAAG,EAAC,GAAG,EACP,IAAI,QACJ,IAAI,EAAC,OAAO,gBACA,SAAS,CAAC,mCAAmC,CAAC,YAEzD,SAAS,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CACzB,KAAC,uBAAuB,IAEtB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,QAAQ,EAC/B,QAAQ,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,IAHlC,MAAM,CAAC,GAAG,CAIf,CACH,CAAC,GACE,EACL,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CACb,MAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,iBAAc,eAAe,aACpF,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,0BAA0B,CAAC,IAC7C,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,EAEN,eACE,SAAS,EAAC,8CAA8C,wBACpC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,aAEjD,UAAU,CAAC,CAAC,CAAC,CACZ,KAAC,gBAAgB,KAAG,CACrB,CAAC,CAAC,CAAC,CACF,KAAC,qBAAqB,IACpB,IAAI,EAAE,KAAK,CAAC,IAAI,EAChB,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAC,GACzC,CACH,EACA,MAAM,KAAK,cAAc,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAC1C,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,wBAAwB,YAC5D,SAAS,CAAC,qCAAqC,CAAC,GAC5C,CACR,CAAC,CAAC,CAAC,IAAI,IACJ,IACF,CACP,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,uBAAuB,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,QAAQ,GAKT;IACC,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAC,OAAO,gBACH,SAAS,CAAC,oCAAoC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,kBACtE,MAAM,EACpB,KAAK,EACH,MAAM,CAAC,EAAE,KAAK,SAAS;YACrB,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,MAAM,CAAC,EAAE,IAAI;YACpC,CAAC,CAAC,SAAS,CAAC,mCAAmC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,EAE7E,OAAO,EAAE,QAAQ,EACjB,SAAS,EAAE,EAAE,CAAC,cAAc,EAAE,MAAM,IAAI,kCAAkC,CAAC,YAE1E,MAAM,CAAC,KAAK,GACN,CACV,CAAA;AACH,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO,CACL,cAAK,SAAS,EAAC,qHAAqH,YAClI,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aAC3B,KAAC,UAAU,IAAC,SAAS,EAAC,8BAA8B,wBAAe,EACnE,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,YAC5B,SAAS,CAAC,oCAAoC,CAAC,GAC3C,EACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YAC9C,SAAS,CAAC,mCAAmC,CAAC,GAC1C,IACD,GACJ,CACP,CAAA;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,SAAS,qBAAqB,CAAC,EAC7B,IAAI,EACJ,GAAG,EACH,WAAW,EACX,KAAK,GAON;IACC,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAkB,IAAI,CAAC,CAAA;IACrD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAAA;IAExD,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,eAAe,CAAA;QAC5C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;YAAE,OAAM;QACzB,wEAAwE;QACxE,yEAAyE;QACzE,qDAAqD;QACrD,6CAA6C;QAC7C,MAAM,MAAM,GAAG,sBAAsB,EAAE,CAAA;QACvC,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,GAAG,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;YACpC,IAAI,CAAC,EAAE,GAAG,YAAY,CAAA;YACtB,IAAI,CAAC,GAAG,GAAG,YAAY,CAAA;YACvB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAA;YAClB,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC;QACD,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,yCAAyC,CAAA;QAC5D,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAA;QACzB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;QAC7B,MAAM,CAAC,CAAC,CAAC,CAAA;IACX,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,2EAA2E;IAC3E,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,UAAU,KAAK,UAAU;YAAE,MAAM,EAAE,CAAA;IAC7E,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,8EAA8E;IAC9E,8EAA8E;IAC9E,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,GAAG,EAAE,IAAI,CAAA;QACtB,IAAI,CAAC,IAAI,IAAI,OAAO,cAAc,KAAK,WAAW;YAAE,OAAM;QAC1D,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC,CAAA;QAChF,OAAO,EAAE,CAAA;QACT,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAA;QAC5C,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACtB,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAA;IACpC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAET,OAAO,CACL,8BACE,iBACE,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,SAAS,CAAC,+BAA+B,CAAC,EACjD,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAC1C,SAAS,EAAC,uEAAuE,EACjF,MAAM,EAAC,wDAAwD,GAC/D,EACD,GAAG;gBACF,CAAC,CAAC,YAAY,CACV,KAAC,oBAAoB,IAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,YAC5D,KAAC,iBAAiB,IAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,GAAI,GACtB,EACvB,GAAG,CAAC,IAAI,CACT;gBACH,CAAC,CAAC,IAAI,IACP,CACJ,CAAA;AACH,CAAC;AAED;;iCAEiC;AACjC,SAAS,iBAAiB,CAAC,EAAE,IAAI,EAAE,GAAG,EAA+C;IACnF,OAAO,4BAAG,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,GAAI,CAAA;AACrC,CAAC;AAMD;;;;;;;GAOG;AACH,MAAM,OAAO,oBAAqB,SAAQ,SAGzC;IACU,KAAK,GAAyB,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAEtD,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,KAAK,EAAE,CAAA;IAClB,CAAC;IAEQ,kBAAkB,CAAC,IAAqC;QAC/D,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC9D,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAChC,CAAC;IACH,CAAC;IAEQ,MAAM;QACb,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO,KAAC,kBAAkB,IAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAI,CAAA;QAC7F,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;IAC5B,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAE,WAAW,EAAE,KAAK,EAAyC;IAC9F,OAAO,CACL,iDAEE,KAAK,EAAE;YACL,SAAS,EAAE,YAAY;YACvB,QAAQ,EAAE,OAAO;YACjB,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,kCAAkC;YAC1C,YAAY,EAAE,CAAC;YACf,OAAO,EAAE,WAAW;YACpB,UAAU,EAAE,wBAAwB;YACpC,KAAK,EAAE,qBAAqB;YAC5B,IAAI,EAAE,+CAA+C;YACrD,SAAS,EAAE,QAAQ;SACpB,aAED,eAAK,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,wBAAK,SAAS,CAAC,qCAAqC,CAAC,IAAO,EAC3F,cAAK,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,YAAG,WAAW,GAAO,EAChF,cAAK,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,YAC/E,KAAK,CAAC,OAAO,GACV,IACF,CACP,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,QAAQ,EACR,KAAK,GAIN;IACC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;IAC5F,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;IAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,KAAK,YAC1C,SAAS,CAAC,mCAAmC,CAAC,GAC1C,CACR,CAAA;IACH,CAAC;IACD,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,aAC3B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;gBAC7B,MAAM,EAAE,GAAG,QAAQ,IAAI,EAAE,CAAA;gBACzB,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,SAAS,CAAA;gBACtD,OAAO,CACL,KAAC,KAAK,IAEJ,KAAK,EACH,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,yBAAO,cAAc,CAAE,IAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,GAAQ,EACnE,UAAU,CAAC,CAAC,CAAC,KAAC,KAAK,IAAC,OAAO,EAAC,MAAM,YAAE,SAAS,CAAC,2BAA2B,CAAC,GAAS,CAAC,CAAC,CAAC,IAAI,IACvF,EAER,OAAO,EAAE,EAAE,EACX,IAAI,EAAE,cAAc,CAAE,IAAkB,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,SAAS,YAExE,KAAC,gBAAgB,IACf,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAiB,EACvB,KAAK,EACH,UAAU;4BACR,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAE,CAAC;4BACpC,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,EAE3C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,EACxC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,GACxC,IArBG,IAAI,CAsBH,CACT,CAAA;YACH,CAAC,CAAC,EACD,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CACb,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAC,YAAY,aACjF,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,+BAA+B,CAAC,IAClD,CACV,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAAmC;IACzE,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,KAAK,YAC1C,SAAS,CAAC,mCAAmC,CAAC,GAC1C,CACR,CAAA;IACH,CAAC;IACD,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,aAC5B,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,8BAA8B,CAAC,GACrC,EACP,cAAK,SAAS,EAAC,wDAAwD,YACpE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAC5B,eAAqB,SAAS,EAAC,yBAAyB,aACtD,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,IAAI,IAAC,IAAI,EAAC,SAAS,YAAE,IAAI,CAAC,IAAI,GAAQ,EACtC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CACf,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,YAAE,SAAS,CAAC,iBAAiB,CAAC,GAAS,CAChE,CAAC,CAAC,CAAC,CACF,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,YAAE,SAAS,CAAC,iBAAiB,CAAC,GAAS,CAChE,IACG,EACL,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACrC,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,SAAS,EAAC,IAAI,mBACnC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,kCAAkC,CAAC,GACzC,EACN,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CACtB,KAAC,IAAI,IAAU,IAAI,EAAC,OAAO,YACxB,EAAE,IADM,EAAE,CAEN,CACR,CAAC,IACE,CACP,CAAC,CAAC,CAAC,IAAI,KApBA,IAAI,CAAC,IAAI,CAqBb,CACP,CAAC,GACE,IACA,CACT,CAAA;AACH,CAAC","sourcesContent":["import { Component, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { MonitorOff, RotateCcw } from 'lucide-react'\nimport { renderNode } from '@elytracms/core/runtime-renderer'\nimport type { RenderContext } from '@elytracms/core/runtime-renderer'\nimport { staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport { componentStatus, previewContext } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { previewSpecs } from '@elytracms/studio/lib/registry/preview'\nimport type { PreviewSpec } from '@elytracms/studio/lib/registry/preview'\nimport { Badge, Button, Code, Field, Row, Spacer, Stack, TabButton, Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { getCanvasStylesheetUrl } from '../builder/canvas-stylesheet'\nimport { useCanvasViewports, viewportWidth } from '../builder/canvas-breakpoints'\nimport type { ViewportOption } from '../builder/canvas-breakpoints'\nimport { PropValueControl } from '../cms/prop-fields'\n\n/**\n * EC-320 — the editable component preview. A component's declared preview states\n * (fixtures) select the SAMPLE STATE (slot content, base props); on top of that\n * the user edits individual prop values in the Props sidebar and the preview\n * re-renders live through the SAME runtime-renderer path the Builder and export\n * use (EC-054). Slots follow the selected fixture — full slot-tree editing is out\n * of scope for this reference surface (that's the composition editor's job); the\n * Slots pane documents what a component accepts and which state supplies content.\n */\n\nexport interface PreviewState {\n specs: PreviewSpec[]\n index: number\n setIndex: (i: number) => void\n /** Per-prop static-value overrides applied over the selected fixture. */\n overrides: Record<string, JsonValue>\n setOverride: (name: string, value: JsonValue) => void\n clearOverride: (name: string) => void\n resetAll: () => void\n dirty: boolean\n /** The live node: the selected fixture with prop overrides applied. */\n node: ComponentNode\n}\n\n/** Preview state for a manifest: the fixture selection + the live prop overrides. */\nexport function usePreviewState(manifest: ComponentManifest): PreviewState {\n const specs = useMemo(() => previewSpecs(manifest), [manifest])\n const [index, setIndexRaw] = useState(0)\n const [overrides, setOverrides] = useState<Record<string, JsonValue>>({})\n\n // Switching preview state resets prop edits — they were relative to the old state.\n const setIndex = (i: number) => {\n setIndexRaw(i)\n setOverrides({})\n }\n const setOverride = (name: string, value: JsonValue) =>\n setOverrides((o) => ({ ...o, [name]: value }))\n const clearOverride = (name: string) =>\n setOverrides((o) => {\n const next = { ...o }\n delete next[name]\n return next\n })\n const resetAll = () => setOverrides({})\n\n const base = specs[Math.min(index, specs.length - 1)]?.node\n const node = useMemo<ComponentNode>(() => {\n const fixture = base ?? { id: `preview-${manifest.id}`, componentId: manifest.id, props: {} }\n const props = { ...(fixture.props ?? {}) }\n for (const [name, value] of Object.entries(overrides)) props[name] = staticProp(value)\n return { ...fixture, props }\n }, [base, overrides, manifest.id])\n\n return {\n specs,\n index: Math.min(index, Math.max(specs.length - 1, 0)),\n setIndex,\n overrides,\n setOverride,\n clearOverride,\n resetAll,\n dirty: Object.keys(overrides).length > 0,\n node,\n }\n}\n\n/** Re-id a preview node into the canvas `node-<n>` namespace for embedding (transfer). */\nexport function embedNodeFrom(node: ComponentNode, id: string): ComponentNode {\n return { ...node, id }\n}\n\n/**\n * The live preview surface: the fixture-state selector (when a component declares\n * more than one) plus the rendered node. Server-only components (EC-191\n * `clientRenderable: false`) can't run on the client-rendered canvas, so they show\n * a documented fallback rather than risk a crash; a missing implementation falls\n * back through the renderer itself (the incompatible state).\n */\nexport function ComponentPreview({\n manifest,\n studio,\n state,\n}: {\n manifest: ComponentManifest\n studio: StudioRegistry\n state: PreviewState\n}) {\n const ctx = useMemo(() => previewContext(studio), [studio])\n const serverOnly = manifest.clientRenderable === false\n const status = componentStatus(studio, manifest)\n const displayName = manifest.title ?? manifest.id\n\n // EC-330: the same config-sourced breakpoint set the builder canvas toolbar\n // offers (`canvas-breakpoints.ts`) — reused, not redefined, so a preview and\n // the real canvas never disagree about the site's responsive widths.\n // Defaults to the always-present 'full' (responsive) option.\n const { viewports } = useCanvasViewports()\n const [viewport, setViewport] = useState(viewports[0]!.key)\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <Row gap=\"1\" wrap align=\"center\" className=\"shrink-0 border-b border-border px-3 py-2\">\n {state.specs.length > 1 ? (\n <Row\n gap=\"1\"\n wrap\n role=\"group\"\n aria-label={translate('components.fixtureRunner.statesAria')}\n >\n {state.specs.map((spec, i) => (\n <TabButton key={spec.name} active={i === state.index} onClick={() => state.setIndex(i)}>\n {spec.name}\n </TabButton>\n ))}\n </Row>\n ) : null}\n <Spacer />\n <Row\n gap=\"1\"\n wrap\n role=\"group\"\n aria-label={translate('components.preview.breakpointAria')}\n >\n {viewports.map((option) => (\n <PreviewBreakpointButton\n key={option.key}\n option={option}\n active={option.key === viewport}\n onSelect={() => setViewport(option.key)}\n />\n ))}\n </Row>\n {state.dirty ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={state.resetAll} data-testid=\"preview-reset\">\n <RotateCcw /> {translate('components.preview.reset')}\n </Button>\n ) : null}\n </Row>\n\n <div\n className=\"canvas-grid min-h-0 flex-1 overflow-auto p-6\"\n data-fixture-state={state.specs[state.index]?.name}\n >\n {serverOnly ? (\n <ServerOnlyNotice />\n ) : (\n <ComponentPreviewFrame\n node={state.node}\n ctx={ctx}\n displayName={displayName}\n width={viewportWidth(viewports, viewport)}\n />\n )}\n {status === 'incompatible' && !serverOnly ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mx-auto mt-3 max-w-2xl\">\n {translate('components.preview.incompatibleNote')}\n </Text>\n ) : null}\n </div>\n </div>\n )\n}\n\n/**\n * One breakpoint option in the preview's width switcher. Mirrors the builder\n * canvas toolbar's `ViewportButton` (`composition-field-editor.tsx`) — same\n * `Button`/active-state styling — but drives LOCAL preview state instead of\n * the builder store (this surface has no composition/undo stack).\n *\n * EC-328: evaluated against the shared `TabButton`/`SegmentedControl`\n * primitives and kept separate — it's built on the shared `Button` (not a\n * bare hand-rolled `<button>`, so it wasn't part of the duplication those\n * primitives address), and carries an interpolated `aria-label` plus a\n * conditional px-value `title` tooltip neither primitive's terse API models.\n */\nfunction PreviewBreakpointButton({\n option,\n active,\n onSelect,\n}: {\n option: ViewportOption\n active: boolean\n onSelect: () => void\n}) {\n return (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.viewportPreviewAria', { label: option.label })}\n aria-pressed={active}\n title={\n option.px !== undefined\n ? `${option.label} · ${option.px}px`\n : translate('builder.canvas.viewportWidthTitle', { label: option.label })\n }\n onClick={onSelect}\n className={cn('px-2 text-xs', active && 'bg-accent text-accent-foreground')}\n >\n {option.label}\n </Button>\n )\n}\n\nfunction ServerOnlyNotice() {\n return (\n <div className=\"mx-auto grid max-w-2xl place-items-center rounded-lg border border-dashed border-border bg-muted/30 p-8 text-center\">\n <Stack gap=\"2\" align=\"center\">\n <MonitorOff className=\"size-5 text-muted-foreground\" aria-hidden />\n <Text size=\"sm\" weight=\"medium\">\n {translate('components.preview.serverOnlyTitle')}\n </Text>\n <Text size=\"xs\" tone=\"muted\" className=\"max-w-sm\">\n {translate('components.preview.serverOnlyBody')}\n </Text>\n </Stack>\n </div>\n )\n}\n\n/** Floor for the auto-height iframe — never collapses to a sliver while empty/loading. */\nconst MIN_PREVIEW_HEIGHT = 360\n\n/**\n * The live preview render surface. Reuses the BuilderCanvas styling mechanism\n * (EC-061, `canvas.tsx`): the component renders inside an ISOLATED same-origin\n * iframe that `<link>`s the project's canvas stylesheet\n * (`getCanvasStylesheetUrl()` — the consuming app's delivery CSS + studio CSS), so\n * a component built with the site's Tailwind theme previews with the site's styles,\n * exactly as it does on the canvas and in delivery. Using the iframe (not the\n * studio DOM) is what keeps the delivery CSS scoped and off the studio chrome.\n *\n * Unlike the builder canvas, this iframe's document is deliberately left OUT of\n * the studio's own dark/light class: this is a standalone-component reference\n * surface (not a full page), so its own negative space around the component must\n * read as a light, site-like backdrop — never the studio chrome's current theme\n * (a black frame is exactly what leaks through when the studio is dark-themed and\n * the iframe root inherits that class). The canvas stylesheet's `:root` (light)\n * tokens supply that backdrop for free once the `.dark` class is never added here.\n *\n * Sizing: width is driven by the caller (the breakpoint selector); height is\n * measured live from the rendered content via `ResizeObserver` (EC-330) so a\n * preset switch or a prop edit — both of which change what's portaled into\n * `doc.body` — always show without clipping or dead space.\n *\n * The render is wrapped in {@link PreviewErrorBoundary} so a component that throws\n * shows the EC-191 fallback instead of taking the whole Components view down.\n */\nfunction ComponentPreviewFrame({\n node,\n ctx,\n displayName,\n width,\n}: {\n node: ComponentNode\n ctx: RenderContext\n displayName: string\n /** CSS width for the iframe — '100%' (responsive) or a fixed breakpoint px width. */\n width: string\n}) {\n const iframeRef = useRef<HTMLIFrameElement>(null)\n const [doc, setDoc] = useState<Document | null>(null)\n const [height, setHeight] = useState(MIN_PREVIEW_HEIGHT)\n\n const wireUp = useCallback(() => {\n const d = iframeRef.current?.contentDocument\n if (!d || !d.body) return\n // Deliberately no `.dark` here (see the surface note above) — the light\n // `:root` tokens from the injected stylesheet are the intended backdrop.\n // Unset stylesheet (tests / an app that never called\n // `setCanvasStylesheetUrl`) → skip the link.\n const appCss = getCanvasStylesheetUrl()\n if (appCss && !d.getElementById('ec-app-css')) {\n const link = d.createElement('link')\n link.id = 'ec-app-css'\n link.rel = 'stylesheet'\n link.href = appCss\n d.head.appendChild(link)\n }\n d.body.className = 'bg-background text-foreground font-sans'\n d.body.style.margin = '0'\n d.body.style.padding = '24px'\n setDoc(d)\n }, [])\n\n // Handle the already-loaded case (srcDoc can load before onLoad attaches).\n useEffect(() => {\n if (iframeRef.current?.contentDocument?.readyState === 'complete') wireUp()\n }, [wireUp])\n\n // Auto-height (EC-330): a same-origin iframe exposes its own document, so a\n // `ResizeObserver` on the BODY tracks its live (height: auto) content size —\n // re-firing on its own whenever the portaled render changes (preset switch,\n // prop edit), no manual re-measure wiring needed. Attached per `doc` (re-runs\n // whenever the iframe reloads), not just on mount. Loop-safety: the observed\n // element (the iframe's body) is never the element this effect resizes (the\n // IFRAME itself) — writing height never feeds back into the observed box, and\n // width is never derived from height, so there is no growth loop. Guarded for\n // environments without ResizeObserver (jsdom/tests, older embeds).\n useEffect(() => {\n const body = doc?.body\n if (!body || typeof ResizeObserver === 'undefined') return\n const measure = () => setHeight(Math.max(body.scrollHeight, MIN_PREVIEW_HEIGHT))\n measure()\n const observer = new ResizeObserver(measure)\n observer.observe(body)\n return () => observer.disconnect()\n }, [doc])\n\n return (\n <>\n <iframe\n ref={iframeRef}\n onLoad={wireUp}\n title={translate('components.detail.tab.preview')}\n style={{ width, height, maxWidth: '100%' }}\n className=\"mx-auto block rounded-lg border border-border bg-background shadow-sm\"\n srcDoc=\"<!doctype html><html><head></head><body></body></html>\"\n />\n {doc\n ? createPortal(\n <PreviewErrorBoundary displayName={displayName} resetKey={node}>\n <PreviewLiveRender node={node} ctx={ctx} />\n </PreviewErrorBoundary>,\n doc.body,\n )\n : null}\n </>\n )\n}\n\n/** Renders the node through the runtime renderer, INSIDE a component render so the\n * error boundary catches synchronous render throws (mirrors the blocks-panel\n * thumbnail's `LiveRender`). */\nfunction PreviewLiveRender({ node, ctx }: { node: ComponentNode; ctx: RenderContext }) {\n return <>{renderNode(node, ctx)}</>\n}\n\ninterface PreviewBoundaryState {\n error: Error | null\n}\n\n/**\n * EC-191 resilience for the Components view: a component whose render throws — a\n * missing provider/context (e.g. a prop control that needs a document's\n * `CollectionViewSource`), a runtime bug — shows the documented \"preview\n * unavailable\" fallback ({@link PreviewUnavailable}) instead of taking the whole\n * view down. It resets when the previewed `resetKey` (the node) changes, so\n * switching the fixture state or editing a prop retries the render.\n */\nexport class PreviewErrorBoundary extends Component<\n { children: ReactNode; displayName: string; resetKey: unknown },\n PreviewBoundaryState\n> {\n override state: PreviewBoundaryState = { error: null }\n\n static getDerivedStateFromError(error: Error): PreviewBoundaryState {\n return { error }\n }\n\n override componentDidUpdate(prev: Readonly<{ resetKey: unknown }>) {\n if (this.state.error && prev.resetKey !== this.props.resetKey) {\n this.setState({ error: null })\n }\n }\n\n override render(): ReactNode {\n if (this.state.error) {\n return <PreviewUnavailable displayName={this.props.displayName} error={this.state.error} />\n }\n return this.props.children\n }\n}\n\n/**\n * The EC-191 fallback panel — component name + \"preview unavailable\" + the error.\n * Rendered INTO the preview iframe (which carries only the project stylesheet, and\n * may carry none in tests), so it uses inline styles rather than studio UI classes\n * — matching the canvas's own in-iframe placeholder convention.\n */\nexport function PreviewUnavailable({ displayName, error }: { displayName: string; error: Error }) {\n return (\n <div\n data-preview-unavailable\n style={{\n boxSizing: 'border-box',\n maxWidth: '42rem',\n margin: '0 auto',\n border: '1px dashed rgba(120,120,120,0.5)',\n borderRadius: 8,\n padding: '24px 28px',\n background: 'rgba(127,127,127,0.06)',\n color: 'rgba(90,90,90,0.95)',\n font: '14px/1.5 system-ui, -apple-system, sans-serif',\n textAlign: 'center',\n }}\n >\n <div style={{ fontWeight: 600 }}>⚠ {translate('components.preview.unavailableTitle')}</div>\n <div style={{ marginTop: 4, fontWeight: 600, opacity: 0.9 }}>{displayName}</div>\n <div style={{ marginTop: 6, fontSize: 13, opacity: 0.85, wordBreak: 'break-word' }}>\n {error.message}\n </div>\n </div>\n )\n}\n\n/**\n * The editable Props pane (right inspector). Every non-`blocks` prop is edited\n * through the shared {@link PropValueControl} — the exact control set documents\n * use (EC-190/EC-314 phase 4) — so a change re-renders the preview immediately.\n * `blocks` props are a canvas (composition) concern, edited in a document, not here.\n */\nexport function PropsEditorPane({\n manifest,\n state,\n}: {\n manifest: ComponentManifest\n state: PreviewState\n}) {\n const editable = Object.entries(manifest.props).filter(([, spec]) => spec.type !== 'blocks')\n const fixture = state.specs[state.index]?.node\n if (editable.length === 0) {\n return (\n <Text size=\"sm\" tone=\"subtle\" className=\"p-4\">\n {translate('components.fieldInspector.noProps')}\n </Text>\n )\n }\n return (\n <Stack gap=\"4\" className=\"p-4\">\n {editable.map(([name, spec]) => {\n const id = `prop-${name}`\n const overridden = state.overrides[name] !== undefined\n return (\n <Field\n key={name}\n label={\n <Row gap=\"2\">\n <span>{localizedLabel((spec as PropField).form?.label, name)}</span>\n {overridden ? <Badge variant=\"info\">{translate('components.preview.edited')}</Badge> : null}\n </Row>\n }\n htmlFor={id}\n hint={localizedLabel((spec as PropField).form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec as PropField}\n value={\n overridden\n ? staticProp(state.overrides[name]!)\n : (fixture?.props?.[name] ?? undefined)\n }\n onSet={(v) => state.setOverride(name, v)}\n onClear={() => state.clearOverride(name)}\n />\n </Field>\n )\n })}\n {state.dirty ? (\n <Button variant=\"outline\" size=\"sm\" onClick={state.resetAll} className=\"self-start\">\n <RotateCcw /> {translate('components.preview.resetProps')}\n </Button>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The Slots pane (right inspector): the component's declared child regions and,\n * for a fixture that supplies slot content, a note that the preview's slot content\n * follows the selected state. Documents the pragmatic scope call — no slot-tree\n * editing on this reference surface.\n */\nexport function SlotsInfoPane({ manifest }: { manifest: ComponentManifest }) {\n if (manifest.slots.length === 0) {\n return (\n <Text size=\"sm\" tone=\"subtle\" className=\"p-4\">\n {translate('components.fieldInspector.noSlots')}\n </Text>\n )\n }\n return (\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('components.preview.slotsNote')}\n </Text>\n <div className=\"divide-y divide-border rounded-md border border-border\">\n {manifest.slots.map((slot) => (\n <div key={slot.name} className=\"flex flex-col gap-1 p-3\">\n <Row gap=\"2\">\n <Code tone=\"primary\">{slot.name}</Code>\n {slot.required ? (\n <Badge variant=\"warning\">{translate('common.required')}</Badge>\n ) : (\n <Badge variant=\"outline\">{translate('common.optional')}</Badge>\n )}\n </Row>\n {slot.allow && slot.allow.length > 0 ? (\n <Row gap=\"2\" className=\"text-xs\" wrap>\n <Text size=\"xs\" tone=\"subtle\">\n {translate('components.fieldInspector.allows')}\n </Text>\n {slot.allow.map((id) => (\n <Code key={id} tone=\"muted\">\n {id}\n </Code>\n ))}\n </Row>\n ) : null}\n </div>\n ))}\n </div>\n </Stack>\n )\n}\n"]}
|
|
@@ -4,7 +4,7 @@ import { Search } from 'lucide-react';
|
|
|
4
4
|
import { useMemo, useState } from 'react';
|
|
5
5
|
import { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry';
|
|
6
6
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
7
|
-
import { Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
7
|
+
import { Code, Input, Row, SegmentedControl, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
8
8
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
9
9
|
import { ComponentStatusDot } from './component-status';
|
|
10
10
|
import { deriveTaxonomy } from './taxonomy';
|
|
@@ -26,13 +26,32 @@ export function RegistryBrowser({ projectId, activeComponentId, }) {
|
|
|
26
26
|
const taxonomy = useMemo(() => deriveTaxonomy(manifests, query), [manifests, query]);
|
|
27
27
|
const shown = taxonomy.filter((group) => namespace === 'all' || group.namespace === namespace);
|
|
28
28
|
const empty = shown.length === 0;
|
|
29
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-2 border-b border-border p-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: translate('components.browser.searchPlaceholder'), className: "pl-8", "aria-label": translate('components.browser.searchAria') })] }),
|
|
29
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-2 border-b border-border p-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: translate('components.browser.searchPlaceholder'), className: "pl-8", "aria-label": translate('components.browser.searchAria') })] }), _jsx(SegmentedControl, { equalWidth: true, ariaLabel: translate('components.browser.namespaceAria'), items: [
|
|
30
|
+
{
|
|
31
|
+
key: 'all',
|
|
32
|
+
label: translate('components.browser.all'),
|
|
33
|
+
count: primitiveCount + projectCount,
|
|
34
|
+
active: namespace === 'all',
|
|
35
|
+
onClick: () => setNamespace('all'),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'base',
|
|
39
|
+
label: translate('components.browser.primitives'),
|
|
40
|
+
count: primitiveCount,
|
|
41
|
+
active: namespace === 'base',
|
|
42
|
+
onClick: () => setNamespace('base'),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: 'project',
|
|
46
|
+
label: translate('components.browser.project'),
|
|
47
|
+
count: projectCount,
|
|
48
|
+
active: namespace === 'project',
|
|
49
|
+
onClick: () => setNamespace('project'),
|
|
50
|
+
},
|
|
51
|
+
] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-2", children: empty ? (_jsx(Text, { size: "sm", tone: "muted", className: "p-3", children: query.trim()
|
|
30
52
|
? translate('components.browser.noMatchQuery', { query })
|
|
31
53
|
: translate('components.browser.noMatchFilters') })) : (_jsx(Stack, { gap: "4", children: shown.map((group) => (_jsxs(Stack, { gap: "2", children: [_jsxs(Row, { gap: "2", className: "px-2", children: [_jsx(Code, { tone: "muted", children: `${group.namespace}.*` }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: group.count })] }), _jsx(Stack, { gap: "3", children: group.categories.map((category) => (_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", weight: "medium", tone: "subtle", className: cn('px-2 uppercase tracking-wide', category.key && 'capitalize'), children: category.key || translate('components.taxonomy.uncategorized') }), _jsx(Stack, { gap: "px", children: category.manifests.map((m) => (_jsx(ComponentRow, { manifest: m, projectId: projectId, active: m.id === activeComponentId }, m.id))) })] }, category.key || '__uncategorized__'))) })] }, group.namespace))) })) })] }));
|
|
32
54
|
}
|
|
33
|
-
function NamespaceTab({ label, count, active, onClick, }) {
|
|
34
|
-
return (_jsxs("button", { type: "button", "aria-pressed": active, onClick: onClick, className: cn('flex flex-1 items-center justify-center gap-1.5 rounded-[5px] px-2 py-1 text-xs font-medium transition-colors', active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'), children: [label, _jsx("span", { className: "font-mono text-[0.65rem] text-muted-foreground", children: count })] }));
|
|
35
|
-
}
|
|
36
55
|
function ComponentRow({ manifest, projectId, active, }) {
|
|
37
56
|
const studio = getStudioRegistry();
|
|
38
57
|
const status = componentStatus(studio, manifest);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-browser.js","sourceRoot":"","sources":["../../../src/components/components/registry-browser.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Search } from 'lucide-react'\nimport { useMemo, useState } from 'react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ComponentStatusDot } from './component-status'\nimport { deriveTaxonomy } from './taxonomy'\nimport type { ComponentNamespace } from './taxonomy'\n\ntype NamespaceFilter = 'all' | ComponentNamespace\n\n/**\n * The registry browser sidebar (EC-050, taxonomy overhaul EC-320). A searchable,\n * two-level tree: namespace (base / project) → category (the manifest's declared\n * `category`, uncategorized last). The segmented control narrows to a namespace;\n * search filters across id/title/category/description. Grouping + search are pure\n * ({@link deriveTaxonomy}), so what renders here is exactly what the tests pin.\n */\nexport function RegistryBrowser({\n projectId,\n activeComponentId,\n}: {\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n const [query, setQuery] = useState('')\n const [namespace, setNamespace] = useState<NamespaceFilter>('all')\n\n const manifests = useMemo(() => studio.registry.list(), [studio])\n const primitiveCount = studio.registry.primitives().length\n const projectCount = studio.registry.projectComponents().length\n\n // The full taxonomy for the query; the segmented control then narrows namespaces.\n const taxonomy = useMemo(() => deriveTaxonomy(manifests, query), [manifests, query])\n const shown = taxonomy.filter((group) => namespace === 'all' || group.namespace === namespace)\n const empty = shown.length === 0\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 space-y-2 border-b border-border p-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('components.browser.searchPlaceholder')}\n className=\"pl-8\"\n aria-label={translate('components.browser.searchAria')}\n />\n </div>\n <Row\n gap=\"1\"\n className=\"rounded-md bg-muted/50 p-0.5\"\n role=\"group\"\n aria-label={translate('components.browser.namespaceAria')}\n >\n <NamespaceTab\n label={translate('components.browser.all')}\n count={primitiveCount + projectCount}\n active={namespace === 'all'}\n onClick={() => setNamespace('all')}\n />\n <NamespaceTab\n label={translate('components.browser.primitives')}\n count={primitiveCount}\n active={namespace === 'base'}\n onClick={() => setNamespace('base')}\n />\n <NamespaceTab\n label={translate('components.browser.project')}\n count={projectCount}\n active={namespace === 'project'}\n onClick={() => setNamespace('project')}\n />\n </Row>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-2\">\n {empty ? (\n <Text size=\"sm\" tone=\"muted\" className=\"p-3\">\n {query.trim()\n ? translate('components.browser.noMatchQuery', { query })\n : translate('components.browser.noMatchFilters')}\n </Text>\n ) : (\n <Stack gap=\"4\">\n {shown.map((group) => (\n <Stack key={group.namespace} gap=\"2\">\n <Row gap=\"2\" className=\"px-2\">\n <Code tone=\"muted\">{`${group.namespace}.*`}</Code>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {group.count}\n </Text>\n </Row>\n <Stack gap=\"3\">\n {group.categories.map((category) => (\n <Stack key={category.key || '__uncategorized__'} gap=\"1\">\n <Text\n size=\"xs\"\n weight=\"medium\"\n tone=\"subtle\"\n className={cn('px-2 uppercase tracking-wide', category.key && 'capitalize')}\n >\n {category.key || translate('components.taxonomy.uncategorized')}\n </Text>\n <Stack gap=\"px\">\n {category.manifests.map((m) => (\n <ComponentRow\n key={m.id}\n manifest={m}\n projectId={projectId}\n active={m.id === activeComponentId}\n />\n ))}\n </Stack>\n </Stack>\n ))}\n </Stack>\n </Stack>\n ))}\n </Stack>\n )}\n </div>\n </div>\n )\n}\n\nfunction NamespaceTab({\n label,\n count,\n active,\n onClick,\n}: {\n label: string\n count: number\n active: boolean\n onClick: () => void\n}) {\n return (\n <button\n type=\"button\"\n aria-pressed={active}\n onClick={onClick}\n className={cn(\n 'flex flex-1 items-center justify-center gap-1.5 rounded-[5px] px-2 py-1 text-xs font-medium transition-colors',\n active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {label}\n <span className=\"font-mono text-[0.65rem] text-muted-foreground\">{count}</span>\n </button>\n )\n}\n\nfunction ComponentRow({\n manifest,\n projectId,\n active,\n}: {\n manifest: ComponentManifest\n projectId: string\n active: boolean\n}) {\n const studio = getStudioRegistry()\n const status = componentStatus(studio, manifest)\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.component(manifest.id) }}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n <ComponentStatusDot status={status} />\n <span className=\"min-w-0 flex-1 truncate font-medium text-foreground\">\n {manifest.title ?? manifest.id}\n </span>\n </Link>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"registry-browser.js","sourceRoot":"","sources":["../../../src/components/components/registry-browser.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Search } from 'lucide-react'\nimport { useMemo, useState } from 'react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Code, Input, Row, SegmentedControl, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ComponentStatusDot } from './component-status'\nimport { deriveTaxonomy } from './taxonomy'\nimport type { ComponentNamespace } from './taxonomy'\n\ntype NamespaceFilter = 'all' | ComponentNamespace\n\n/**\n * The registry browser sidebar (EC-050, taxonomy overhaul EC-320). A searchable,\n * two-level tree: namespace (base / project) → category (the manifest's declared\n * `category`, uncategorized last). The segmented control narrows to a namespace;\n * search filters across id/title/category/description. Grouping + search are pure\n * ({@link deriveTaxonomy}), so what renders here is exactly what the tests pin.\n */\nexport function RegistryBrowser({\n projectId,\n activeComponentId,\n}: {\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n const [query, setQuery] = useState('')\n const [namespace, setNamespace] = useState<NamespaceFilter>('all')\n\n const manifests = useMemo(() => studio.registry.list(), [studio])\n const primitiveCount = studio.registry.primitives().length\n const projectCount = studio.registry.projectComponents().length\n\n // The full taxonomy for the query; the segmented control then narrows namespaces.\n const taxonomy = useMemo(() => deriveTaxonomy(manifests, query), [manifests, query])\n const shown = taxonomy.filter((group) => namespace === 'all' || group.namespace === namespace)\n const empty = shown.length === 0\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 space-y-2 border-b border-border p-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('components.browser.searchPlaceholder')}\n className=\"pl-8\"\n aria-label={translate('components.browser.searchAria')}\n />\n </div>\n <SegmentedControl\n equalWidth\n ariaLabel={translate('components.browser.namespaceAria')}\n items={[\n {\n key: 'all',\n label: translate('components.browser.all'),\n count: primitiveCount + projectCount,\n active: namespace === 'all',\n onClick: () => setNamespace('all'),\n },\n {\n key: 'base',\n label: translate('components.browser.primitives'),\n count: primitiveCount,\n active: namespace === 'base',\n onClick: () => setNamespace('base'),\n },\n {\n key: 'project',\n label: translate('components.browser.project'),\n count: projectCount,\n active: namespace === 'project',\n onClick: () => setNamespace('project'),\n },\n ]}\n />\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-2\">\n {empty ? (\n <Text size=\"sm\" tone=\"muted\" className=\"p-3\">\n {query.trim()\n ? translate('components.browser.noMatchQuery', { query })\n : translate('components.browser.noMatchFilters')}\n </Text>\n ) : (\n <Stack gap=\"4\">\n {shown.map((group) => (\n <Stack key={group.namespace} gap=\"2\">\n <Row gap=\"2\" className=\"px-2\">\n <Code tone=\"muted\">{`${group.namespace}.*`}</Code>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {group.count}\n </Text>\n </Row>\n <Stack gap=\"3\">\n {group.categories.map((category) => (\n <Stack key={category.key || '__uncategorized__'} gap=\"1\">\n <Text\n size=\"xs\"\n weight=\"medium\"\n tone=\"subtle\"\n className={cn('px-2 uppercase tracking-wide', category.key && 'capitalize')}\n >\n {category.key || translate('components.taxonomy.uncategorized')}\n </Text>\n <Stack gap=\"px\">\n {category.manifests.map((m) => (\n <ComponentRow\n key={m.id}\n manifest={m}\n projectId={projectId}\n active={m.id === activeComponentId}\n />\n ))}\n </Stack>\n </Stack>\n ))}\n </Stack>\n </Stack>\n ))}\n </Stack>\n )}\n </div>\n </div>\n )\n}\n\nfunction ComponentRow({\n manifest,\n projectId,\n active,\n}: {\n manifest: ComponentManifest\n projectId: string\n active: boolean\n}) {\n const studio = getStudioRegistry()\n const status = componentStatus(studio, manifest)\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.component(manifest.id) }}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n <ComponentStatusDot status={status} />\n <span className=\"min-w-0 flex-1 truncate font-medium text-foreground\">\n {manifest.title ?? manifest.id}\n </span>\n </Link>\n )\n}\n"]}
|
|
@@ -18,3 +18,13 @@ export declare function TransferToDocumentDialog({ projectId, manifest, node, op
|
|
|
18
18
|
open: boolean;
|
|
19
19
|
onOpenChange: (open: boolean) => void;
|
|
20
20
|
}): import("react").JSX.Element;
|
|
21
|
+
/**
|
|
22
|
+
* Exported for the fresh-Convex-project regression test (bug fix,
|
|
23
|
+
* transfer-dialog.test.tsx) — `TransferToDocumentDialog` is otherwise its only
|
|
24
|
+
* caller.
|
|
25
|
+
*/
|
|
26
|
+
export declare function TransferPicker({ projectId, node, onClose, }: {
|
|
27
|
+
projectId: string;
|
|
28
|
+
node: ComponentNode;
|
|
29
|
+
onClose: () => void;
|
|
30
|
+
}): import("react").JSX.Element;
|