@elytracms/studio 0.0.32 → 0.0.34
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/app-views/default-modules.js +20 -11
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/assets/asset-library.js +21 -13
- package/dist/components/assets/asset-library.js.map +1 -1
- package/dist/components/assets/asset-thumb.js +3 -2
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/backend-gate.js +4 -3
- package/dist/components/backend-gate.js.map +1 -1
- package/dist/components/builder/blocks-panel.js +11 -2
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/canvas.js +6 -5
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.js +12 -6
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.js +7 -4
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/fixture-thumbnail.js +2 -1
- package/dist/components/builder/fixture-thumbnail.js.map +1 -1
- package/dist/components/builder/layout-inspector.d.ts +5 -5
- package/dist/components/builder/layout-inspector.js +21 -20
- package/dist/components/builder/layout-inspector.js.map +1 -1
- package/dist/components/builder/page-tree.js +5 -4
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/property-inspector.js +10 -10
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +9 -9
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/rich-text-toolbar.js +4 -3
- package/dist/components/builder/rich-text-toolbar.js.map +1 -1
- package/dist/components/builder/spacing-handles.js +4 -3
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-picker.js +8 -7
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-bar.js +8 -5
- package/dist/components/cms/bulk-bar.js.map +1 -1
- package/dist/components/cms/cell.js +9 -5
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +7 -6
- package/dist/components/cms/cms-store.js +21 -12
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +18 -7
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +45 -29
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/details-pane.js +4 -3
- package/dist/components/cms/details-pane.js.map +1 -1
- package/dist/components/cms/editable-cell.js +4 -4
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.js +29 -21
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.js +2 -1
- package/dist/components/cms/field-renderer.js.map +1 -1
- package/dist/components/cms/filter-builder.js +9 -8
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/cms/import-export.js +3 -2
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/issues-pane.js +3 -2
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/reference-confirm.js +24 -10
- package/dist/components/cms/reference-confirm.js.map +1 -1
- package/dist/components/cms/relation-picker.js +5 -4
- package/dist/components/cms/relation-picker.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.js +5 -5
- package/dist/components/cms/scalar-value-editor.js.map +1 -1
- package/dist/components/cms/version-compare.js +36 -21
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-pane.js +8 -5
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/cms/view-bar.js +3 -2
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/components/component-detail.js +3 -2
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-status.js +8 -13
- package/dist/components/components/component-status.js.map +1 -1
- package/dist/components/components/components-index.js +5 -1
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/components/components/field-inspector.js +4 -4
- package/dist/components/components/field-inspector.js.map +1 -1
- package/dist/components/components/fixture-runner.js +4 -1
- package/dist/components/components/fixture-runner.js.map +1 -1
- package/dist/components/components/registry-browser.js +4 -3
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/delivery-health-banner.js +2 -1
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/dev-login-badge.js +2 -1
- package/dist/components/dev-login-badge.js.map +1 -1
- package/dist/components/extensions/extensions-view.js +22 -21
- package/dist/components/extensions/extensions-view.js.map +1 -1
- package/dist/components/guards.js +3 -2
- package/dist/components/guards.js.map +1 -1
- package/dist/components/operation-error.js +3 -2
- package/dist/components/operation-error.js.map +1 -1
- package/dist/components/playground-badge.js +2 -1
- package/dist/components/playground-badge.js.map +1 -1
- package/dist/components/project-list.js +5 -4
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.js +3 -2
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js +4 -3
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/redirects/redirects-module.js +3 -2
- package/dist/components/redirects/redirects-module.js.map +1 -1
- package/dist/components/references/references-panel.js +7 -6
- package/dist/components/references/references-panel.js.map +1 -1
- package/dist/components/section-nav.js +8 -5
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/section-state.js +13 -32
- package/dist/components/section-state.js.map +1 -1
- package/dist/components/settings-form.js +2 -1
- package/dist/components/settings-form.js.map +1 -1
- package/dist/components/studio-project-screen.js +3 -2
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/components/studio-shell.js +3 -2
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/components/users/users-module.js +5 -4
- package/dist/components/users/users-module.js.map +1 -1
- package/dist/components/workspace-gate.js +2 -1
- package/dist/components/workspace-gate.js.map +1 -1
- package/dist/lib/cms-views/view-model.js +3 -2
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/i18n/i18n.d.ts +19 -1
- package/dist/lib/i18n/i18n.js +45 -6
- package/dist/lib/i18n/i18n.js.map +1 -1
- package/dist/lib/i18n/index.d.ts +1 -1
- package/dist/lib/i18n/index.js +1 -1
- package/dist/lib/i18n/index.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +11 -4
- package/dist/lib/i18n/messages.en.js +660 -6
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/references/reference-model.js +6 -4
- package/dist/lib/references/reference-model.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"references-panel.js","sourceRoot":"","sources":["../../../src/components/references/references-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { AlertTriangle, ArrowDownLeft, ArrowUpRight, Link2, RotateCcw } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { ReferenceSource, ReferenceTarget } from '@elytracms/core/persistence'\nimport { useReferencePanel } from '@elytracms/studio/lib/references'\nimport type { ReferenceDeepLink, ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { Badge, Button, Panel, PanelHeader, Row, RowItem, Spacer, Stack, Text } from '@elytracms/studio/ui'\n\n/**\n * The shared \"References / Used by\" panel (EC-172) — one generic component,\n * three hosts (CMS document detail, asset library, component detail). Entries\n * are served entirely by `@elytracms/core/operations` reference queries over the\n * EC-155 index; every entry deep-links to the exact field control or graph\n * node its index path identifies, and draft-only entries are visibly\n * distinguished from published (live) ones.\n */\nexport function ReferencesPanel({\n projectId,\n source,\n target,\n className,\n}: {\n projectId: string\n /** The holding entity — enables the outbound \"References\" section. */\n source?: ReferenceSource\n /** The referenced entity — enables the inbound \"Used by\" section. */\n target?: ReferenceTarget\n className?: string\n}) {\n const panel = useReferencePanel({ projectId, source, target })\n const total =\n panel.data === null ? null : panel.data.outbound.length + panel.data.inbound.length\n\n return (\n <Panel className={className} data-testid=\"references-panel\">\n <PanelHeader>\n <Link2 className=\"size-4 text-primary\" />\n <Text size=\"sm\" weight=\"semibold\">\n References\n </Text>\n {total !== null ? <Badge variant=\"outline\">{total}</Badge> : null}\n <Spacer />\n </PanelHeader>\n <Stack gap=\"4\" className=\"p-4\">\n {panel.status === 'loading' ? (\n <ReferencesLoading />\n ) : panel.status === 'error' ? (\n <ReferencesError message={panel.errorMessage} onRetry={panel.refresh} />\n ) : (\n <>\n {source ? (\n <ReferenceSection\n title=\"References\"\n icon={<ArrowUpRight className=\"size-3.5\" />}\n hint=\"What this entry points at — click to jump to the target, or to the exact field.\"\n direction=\"outbound\"\n projectId={projectId}\n entries={panel.data?.outbound ?? []}\n error={panel.data?.outboundError ?? null}\n emptyLabel=\"No outbound references.\"\n />\n ) : null}\n {target ? (\n <ReferenceSection\n title=\"Used by\"\n icon={<ArrowDownLeft className=\"size-3.5\" />}\n hint=\"Everything referencing this entry — click to jump to the exact field or node.\"\n direction=\"inbound\"\n projectId={projectId}\n entries={panel.data?.inbound ?? []}\n error={panel.data?.inboundError ?? null}\n emptyLabel=\"Not referenced anywhere.\"\n />\n ) : null}\n </>\n )}\n </Stack>\n </Panel>\n )\n}\n\n/** Named loading state, consistent with existing studio panels. */\nfunction ReferencesLoading() {\n return (\n <Text size=\"sm\" tone=\"muted\" data-testid=\"references-loading\">\n Loading references…\n </Text>\n )\n}\n\n/** Named error state with retry — never a silent failure. */\nfunction ReferencesError({\n message,\n onRetry,\n}: {\n message: string | null\n onRetry: () => void\n}) {\n return (\n <Stack gap=\"2\" data-testid=\"references-error\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text size=\"sm\" weight=\"semibold\">\n References failed to load\n </Text>\n </Row>\n <Text size=\"xs\" tone=\"muted\">\n {message ?? 'The reference index is unreachable.'}\n </Text>\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n <RotateCcw /> Retry\n </Button>\n </Row>\n </Stack>\n )\n}\n\nfunction ReferenceSection({\n title,\n icon,\n hint,\n direction,\n projectId,\n entries,\n error,\n emptyLabel,\n}: {\n title: string\n icon: ReactNode\n hint: string\n direction: 'outbound' | 'inbound'\n projectId: string\n entries: readonly ReferenceEntryView[]\n /** EC-205: this direction's read failed — shown inline, the panel stays usable. */\n error?: string | null\n emptyLabel: string\n}) {\n return (\n <Stack gap=\"2\" data-testid={`references-${direction}`}>\n <Row gap=\"2\">\n <span className=\"text-muted-foreground\">{icon}</span>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {title}\n </Text>\n <Badge variant=\"outline\">{entries.length}</Badge>\n </Row>\n {error ? (\n <Row gap=\"2\" data-testid={`references-${direction}-error`}>\n <AlertTriangle className=\"size-3.5 shrink-0 text-destructive\" />\n <Text size=\"xs\" tone=\"muted\">\n Couldn’t load this section: {error}\n </Text>\n </Row>\n ) : entries.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" data-testid={`references-${direction}-empty`}>\n {emptyLabel}\n </Text>\n ) : (\n <Stack gap=\"2\" title={hint}>\n {entries.map((entry) => (\n <ReferenceEntryRow\n key={entry.key}\n entry={entry}\n direction={direction}\n projectId={projectId}\n />\n ))}\n </Stack>\n )}\n </Stack>\n )\n}\n\n/**\n * One entry row. Inbound rows lead with the *source* (where it is used) and\n * deep-link to the exact location; outbound rows lead with the *target* and\n * keep the exact-field link on the location line.\n */\nfunction ReferenceEntryRow({\n entry,\n direction,\n projectId,\n}: {\n entry: ReferenceEntryView\n direction: 'outbound' | 'inbound'\n projectId: string\n}) {\n const primary =\n direction === 'inbound'\n ? { label: entry.sourceLabel, link: entry.sourceLink }\n : { label: entry.targetLabel, link: entry.targetLink }\n return (\n <RowItem\n data-testid=\"reference-entry\"\n actions={\n <>\n <Badge variant=\"outline\">{entry.kindLabel}</Badge>\n <VariantBadges draft={entry.draft} published={entry.published} />\n </>\n }\n >\n <Stack gap=\"1\" className=\"min-w-0\">\n <Row gap=\"2\" className=\"min-w-0\">\n <DeepLinkText projectId={projectId} link={primary.link} label={primary.label} primary />\n </Row>\n <Row gap=\"1\" className=\"min-w-0\">\n <Text size=\"xs\" tone=\"muted\" className=\"shrink-0\">\n at\n </Text>\n <DeepLinkText\n projectId={projectId}\n link={entry.sourceLink}\n label={entry.locationLabel}\n />\n </Row>\n </Stack>\n </RowItem>\n )\n}\n\n/** Draft/published visibility (the Sanity hidden-draft-reference case, AD-6). */\nfunction VariantBadges({ draft, published }: { draft: boolean; published: boolean }) {\n return (\n <Row gap=\"1\" className=\"shrink-0\" data-testid=\"reference-variants\">\n {draft ? <Badge variant=\"warning\">draft</Badge> : null}\n {published ? <Badge variant=\"success\">published</Badge> : null}\n </Row>\n )\n}\n\n/** Render a deep-link descriptor as a router link (plain text when unresolvable). */\nfunction DeepLinkText({\n projectId,\n link,\n label,\n primary = false,\n}: {\n projectId: string\n link: ReferenceDeepLink | null\n label: string\n primary?: boolean\n}) {\n const className = primary\n ? 'min-w-0 truncate text-sm font-medium text-foreground hover:text-primary hover:underline'\n : 'min-w-0 truncate text-xs text-muted-foreground hover:text-primary hover:underline'\n if (link === null) {\n return primary ? (\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 truncate\">\n {label}\n </Text>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 truncate\">\n {label}\n </Text>\n )\n }\n switch (link.kind) {\n case 'cms-field':\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.cmsDocument(link.collectionId, link.documentId) }}\n search={link.fieldPath !== undefined ? { field: link.fieldPath } : {}}\n className={className}\n data-testid=\"reference-link-cms\"\n >\n {label}\n </Link>\n )\n case 'builder-node':\n // The builder redirect (EC-187) only reads `?page=`; the page-graph-only\n // `node` has no home in the document editor and is dropped.\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.builder() }}\n search={link.pageId !== undefined ? { page: link.pageId } : {}}\n className={className}\n data-testid=\"reference-link-builder\"\n >\n {label}\n </Link>\n )\n case 'component':\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.component(link.componentId) }}\n className={className}\n data-testid=\"reference-link-component\"\n >\n {label}\n </Link>\n )\n case 'asset':\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.assets() }}\n search={{ asset: link.assetId }}\n className={className}\n data-testid=\"reference-link-asset\"\n >\n {label}\n </Link>\n )\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"references-panel.js","sourceRoot":"","sources":["../../../src/components/references/references-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { AlertTriangle, ArrowDownLeft, ArrowUpRight, Link2, RotateCcw } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { ReferenceSource, ReferenceTarget } from '@elytracms/core/persistence'\nimport { useReferencePanel } from '@elytracms/studio/lib/references'\nimport type { ReferenceDeepLink, ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { Badge, Button, Panel, PanelHeader, Row, RowItem, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * The shared \"References / Used by\" panel (EC-172) — one generic component,\n * three hosts (CMS document detail, asset library, component detail). Entries\n * are served entirely by `@elytracms/core/operations` reference queries over the\n * EC-155 index; every entry deep-links to the exact field control or graph\n * node its index path identifies, and draft-only entries are visibly\n * distinguished from published (live) ones.\n */\nexport function ReferencesPanel({\n projectId,\n source,\n target,\n className,\n}: {\n projectId: string\n /** The holding entity — enables the outbound \"References\" section. */\n source?: ReferenceSource\n /** The referenced entity — enables the inbound \"Used by\" section. */\n target?: ReferenceTarget\n className?: string\n}) {\n const panel = useReferencePanel({ projectId, source, target })\n const total =\n panel.data === null ? null : panel.data.outbound.length + panel.data.inbound.length\n\n return (\n <Panel className={className} data-testid=\"references-panel\">\n <PanelHeader>\n <Link2 className=\"size-4 text-primary\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('references.title')}\n </Text>\n {total !== null ? <Badge variant=\"outline\">{total}</Badge> : null}\n <Spacer />\n </PanelHeader>\n <Stack gap=\"4\" className=\"p-4\">\n {panel.status === 'loading' ? (\n <ReferencesLoading />\n ) : panel.status === 'error' ? (\n <ReferencesError message={panel.errorMessage} onRetry={panel.refresh} />\n ) : (\n <>\n {source ? (\n <ReferenceSection\n title={translate('references.title')}\n icon={<ArrowUpRight className=\"size-3.5\" />}\n hint={translate('references.hintOutbound')}\n direction=\"outbound\"\n projectId={projectId}\n entries={panel.data?.outbound ?? []}\n error={panel.data?.outboundError ?? null}\n emptyLabel={translate('references.emptyOutbound')}\n />\n ) : null}\n {target ? (\n <ReferenceSection\n title={translate('references.usedBy')}\n icon={<ArrowDownLeft className=\"size-3.5\" />}\n hint={translate('references.hintInbound')}\n direction=\"inbound\"\n projectId={projectId}\n entries={panel.data?.inbound ?? []}\n error={panel.data?.inboundError ?? null}\n emptyLabel={translate('references.emptyInbound')}\n />\n ) : null}\n </>\n )}\n </Stack>\n </Panel>\n )\n}\n\n/** Named loading state, consistent with existing studio panels. */\nfunction ReferencesLoading() {\n return (\n <Text size=\"sm\" tone=\"muted\" data-testid=\"references-loading\">\n {translate('references.loading')}\n </Text>\n )\n}\n\n/** Named error state with retry — never a silent failure. */\nfunction ReferencesError({\n message,\n onRetry,\n}: {\n message: string | null\n onRetry: () => void\n}) {\n return (\n <Stack gap=\"2\" data-testid=\"references-error\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('references.loadFailed')}\n </Text>\n </Row>\n <Text size=\"xs\" tone=\"muted\">\n {message ?? translate('references.indexUnreachable')}\n </Text>\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n <RotateCcw /> {translate('common.retry')}\n </Button>\n </Row>\n </Stack>\n )\n}\n\nfunction ReferenceSection({\n title,\n icon,\n hint,\n direction,\n projectId,\n entries,\n error,\n emptyLabel,\n}: {\n title: string\n icon: ReactNode\n hint: string\n direction: 'outbound' | 'inbound'\n projectId: string\n entries: readonly ReferenceEntryView[]\n /** EC-205: this direction's read failed — shown inline, the panel stays usable. */\n error?: string | null\n emptyLabel: string\n}) {\n return (\n <Stack gap=\"2\" data-testid={`references-${direction}`}>\n <Row gap=\"2\">\n <span className=\"text-muted-foreground\">{icon}</span>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {title}\n </Text>\n <Badge variant=\"outline\">{entries.length}</Badge>\n </Row>\n {error ? (\n <Row gap=\"2\" data-testid={`references-${direction}-error`}>\n <AlertTriangle className=\"size-3.5 shrink-0 text-destructive\" />\n <Text size=\"xs\" tone=\"muted\">\n {translate('references.sectionLoadError', { error })}\n </Text>\n </Row>\n ) : entries.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" data-testid={`references-${direction}-empty`}>\n {emptyLabel}\n </Text>\n ) : (\n <Stack gap=\"2\" title={hint}>\n {entries.map((entry) => (\n <ReferenceEntryRow\n key={entry.key}\n entry={entry}\n direction={direction}\n projectId={projectId}\n />\n ))}\n </Stack>\n )}\n </Stack>\n )\n}\n\n/**\n * One entry row. Inbound rows lead with the *source* (where it is used) and\n * deep-link to the exact location; outbound rows lead with the *target* and\n * keep the exact-field link on the location line.\n */\nfunction ReferenceEntryRow({\n entry,\n direction,\n projectId,\n}: {\n entry: ReferenceEntryView\n direction: 'outbound' | 'inbound'\n projectId: string\n}) {\n const primary =\n direction === 'inbound'\n ? { label: entry.sourceLabel, link: entry.sourceLink }\n : { label: entry.targetLabel, link: entry.targetLink }\n return (\n <RowItem\n data-testid=\"reference-entry\"\n actions={\n <>\n <Badge variant=\"outline\">{entry.kindLabel}</Badge>\n <VariantBadges draft={entry.draft} published={entry.published} />\n </>\n }\n >\n <Stack gap=\"1\" className=\"min-w-0\">\n <Row gap=\"2\" className=\"min-w-0\">\n <DeepLinkText projectId={projectId} link={primary.link} label={primary.label} primary />\n </Row>\n <Row gap=\"1\" className=\"min-w-0\">\n <Text size=\"xs\" tone=\"muted\" className=\"shrink-0\">\n {translate('references.locationPrefix')}\n </Text>\n <DeepLinkText\n projectId={projectId}\n link={entry.sourceLink}\n label={entry.locationLabel}\n />\n </Row>\n </Stack>\n </RowItem>\n )\n}\n\n/** Draft/published visibility (the Sanity hidden-draft-reference case, AD-6). */\nfunction VariantBadges({ draft, published }: { draft: boolean; published: boolean }) {\n return (\n <Row gap=\"1\" className=\"shrink-0\" data-testid=\"reference-variants\">\n {draft ? <Badge variant=\"warning\">{translate('references.badgeDraft')}</Badge> : null}\n {published ? <Badge variant=\"success\">{translate('references.badgePublished')}</Badge> : null}\n </Row>\n )\n}\n\n/** Render a deep-link descriptor as a router link (plain text when unresolvable). */\nfunction DeepLinkText({\n projectId,\n link,\n label,\n primary = false,\n}: {\n projectId: string\n link: ReferenceDeepLink | null\n label: string\n primary?: boolean\n}) {\n const className = primary\n ? 'min-w-0 truncate text-sm font-medium text-foreground hover:text-primary hover:underline'\n : 'min-w-0 truncate text-xs text-muted-foreground hover:text-primary hover:underline'\n if (link === null) {\n return primary ? (\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 truncate\">\n {label}\n </Text>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 truncate\">\n {label}\n </Text>\n )\n }\n switch (link.kind) {\n case 'cms-field':\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.cmsDocument(link.collectionId, link.documentId) }}\n search={link.fieldPath !== undefined ? { field: link.fieldPath } : {}}\n className={className}\n data-testid=\"reference-link-cms\"\n >\n {label}\n </Link>\n )\n case 'builder-node':\n // The builder redirect (EC-187) only reads `?page=`; the page-graph-only\n // `node` has no home in the document editor and is dropped.\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.builder() }}\n search={link.pageId !== undefined ? { page: link.pageId } : {}}\n className={className}\n data-testid=\"reference-link-builder\"\n >\n {label}\n </Link>\n )\n case 'component':\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.component(link.componentId) }}\n className={className}\n data-testid=\"reference-link-component\"\n >\n {label}\n </Link>\n )\n case 'asset':\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.assets() }}\n search={{ asset: link.assetId }}\n className={className}\n data-testid=\"reference-link-asset\"\n >\n {label}\n </Link>\n )\n }\n}\n"]}
|
|
@@ -4,6 +4,7 @@ import { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/route
|
|
|
4
4
|
import { AlertTriangle, ChevronDown, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
|
|
5
5
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
6
6
|
import { readCollapsedViews, storeCollapsedViews } from '@elytracms/studio/lib/rail';
|
|
7
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
7
8
|
import { Text } from '@elytracms/studio/ui';
|
|
8
9
|
import { activeViewIdFromPath, isNavViewVisible, useStudioNav } from './app-views/sidebar';
|
|
9
10
|
import { useFieldRenderers } from './cms/field-renderer';
|
|
@@ -50,7 +51,7 @@ export function SectionNav({ project, collapsed, onToggleCollapsed }) {
|
|
|
50
51
|
return next;
|
|
51
52
|
});
|
|
52
53
|
}, []);
|
|
53
|
-
return (_jsxs("nav", { "aria-label":
|
|
54
|
+
return (_jsxs("nav", { "aria-label": translate('shell.nav.label'), className: cn('flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150', collapsed ? 'w-[3.5rem]' : 'w-56'), children: [nodes.map((node, index) => {
|
|
54
55
|
if (node.kind === 'separator') {
|
|
55
56
|
return _jsx("div", { className: "my-1.5 border-t border-border/60", "aria-hidden": true }, `sep-${index}`);
|
|
56
57
|
}
|
|
@@ -75,7 +76,7 @@ export function SectionNav({ project, collapsed, onToggleCollapsed }) {
|
|
|
75
76
|
return (_jsxs(Fragment, { children: [_jsx(NavViewLink, { projectId: projectId, view: node, activeViewId: activeViewId, collapsed: collapsed, hasActiveChild: hasActiveChild, collapsible: hasChildren && !collapsed, expanded: !viewCollapsed, onToggleExpanded: () => toggleViewCollapsed(node.id) }), collapsed || viewCollapsed
|
|
76
77
|
? null
|
|
77
78
|
: node.children?.filter(isVisible).map((child) => (_jsx(NavViewLink, { projectId: projectId, view: child, activeViewId: activeViewId, collapsed: collapsed, nested: true }, child.id)))] }, node.id));
|
|
78
|
-
}), _jsxs("div", { className: "mt-auto flex flex-col gap-0.5", children: [issues.length > 0 ? _jsx(NavIssues, { issues: issues, collapsed: collapsed }) : null, _jsx("button", { type: "button", onClick: onToggleCollapsed, "aria-label": collapsed ? '
|
|
79
|
+
}), _jsxs("div", { className: "mt-auto flex flex-col gap-0.5", children: [issues.length > 0 ? _jsx(NavIssues, { issues: issues, collapsed: collapsed }) : null, _jsx("button", { type: "button", onClick: onToggleCollapsed, "aria-label": collapsed ? translate('shell.sidebar.expand') : translate('shell.sidebar.collapse'), "aria-pressed": collapsed, title: collapsed ? translate('shell.sidebar.expand') : translate('shell.sidebar.collapse'), className: cn('flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground', collapsed ? 'justify-center px-0' : 'px-3'), children: collapsed ? (_jsx(PanelLeftOpen, { className: "size-4 shrink-0" })) : (_jsxs(_Fragment, { children: [_jsx(PanelLeftClose, { className: "size-4 shrink-0" }), _jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: translate('shell.sidebar.collapseLabel') })] })) })] })] }));
|
|
79
80
|
}
|
|
80
81
|
/** One rail view (top-level or nested) — a Link to its home route, active by view id. */
|
|
81
82
|
function NavViewLink({ projectId, view, activeViewId, collapsed, nested = false, hasActiveChild = false, collapsible = false, expanded = true, onToggleExpanded, }) {
|
|
@@ -87,18 +88,20 @@ function NavViewLink({ projectId, view, activeViewId, collapsed, nested = false,
|
|
|
87
88
|
? 'bg-accent font-medium text-accent-foreground'
|
|
88
89
|
: hasActiveChild
|
|
89
90
|
? 'text-foreground hover:bg-muted'
|
|
90
|
-
: 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: [isActive ? (_jsx("span", { className: "absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary", "aria-hidden": true })) : null, _jsx(Icon, { className: "size-4 shrink-0" }), collapsed ? null : _jsx("span", { className: "min-w-0 flex-1 truncate", children: view.label }), view.availability === 'planned' && !collapsed ? (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-warning", "aria-hidden": true, title:
|
|
91
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: [isActive ? (_jsx("span", { className: "absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary", "aria-hidden": true })) : null, _jsx(Icon, { className: "size-4 shrink-0" }), collapsed ? null : _jsx("span", { className: "min-w-0 flex-1 truncate", children: view.label }), view.availability === 'planned' && !collapsed ? (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-warning", "aria-hidden": true, title: translate('shell.nav.planned') })) : null] }));
|
|
91
92
|
// A leaf (or the collapsed icons-only rail) is just the link.
|
|
92
93
|
if (!collapsible)
|
|
93
94
|
return link;
|
|
94
95
|
// A parent pairs the link with a sibling chevron toggle (so the chevron collapses
|
|
95
96
|
// without navigating, and the link still navigates to the parent surface).
|
|
96
|
-
return (_jsxs("div", { className: "relative", children: [link, _jsx("button", { type: "button", onClick: onToggleExpanded, "aria-label":
|
|
97
|
+
return (_jsxs("div", { className: "relative", children: [link, _jsx("button", { type: "button", onClick: onToggleExpanded, "aria-label": translate(expanded ? 'shell.nav.collapseView' : 'shell.nav.expandView', {
|
|
98
|
+
name: view.label,
|
|
99
|
+
}), "aria-expanded": expanded, className: "absolute right-1 top-1/2 grid size-6 -translate-y-1/2 place-items-center rounded text-muted-foreground/70 transition-colors hover:bg-background hover:text-foreground", children: _jsx(ChevronDown, { className: cn('size-3.5 transition-transform', expanded ? '' : '-rotate-90'), "aria-hidden": true }) })] }));
|
|
97
100
|
}
|
|
98
101
|
/** Visible customization issues at the rail foot — the seam's "never silent" (EC-236). */
|
|
99
102
|
function NavIssues({ issues, collapsed }) {
|
|
100
103
|
if (collapsed) {
|
|
101
|
-
return (_jsx("div", { className: "grid h-9 place-items-center text-amber-600 dark:text-amber-400", title: issues.map((issue) => issue.message).join('\n'), children: _jsx(AlertTriangle, { className: "size-4", "aria-label":
|
|
104
|
+
return (_jsx("div", { className: "grid h-9 place-items-center text-amber-600 dark:text-amber-400", title: issues.map((issue) => issue.message).join('\n'), children: _jsx(AlertTriangle, { className: "size-4", "aria-label": translate('shell.nav.issuesCount', { count: issues.length }) }) }));
|
|
102
105
|
}
|
|
103
106
|
return (_jsx("div", { className: "mb-1 flex flex-col gap-1 rounded-md border border-amber-500/30 bg-amber-500/5 p-2", children: issues.map((issue, index) => (_jsxs("div", { className: "flex items-start gap-1.5 text-amber-600 dark:text-amber-400", title: issue.message, children: [_jsx(AlertTriangle, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": true }), _jsx(Text, { size: "xs", className: "min-w-0 leading-snug", children: issue.message })] }, `${issue.point}:${issue.id ?? index}`))) }));
|
|
104
107
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-nav.js","sourceRoot":"","sources":["../../src/components/section-nav.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Fragment, useCallback, useEffect, useState } from 'react'\nimport { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router'\nimport { AlertTriangle, ChevronDown, PanelLeftClose, PanelLeftOpen } from 'lucide-react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { readCollapsedViews, storeCollapsedViews } from '@elytracms/studio/lib/rail'\nimport { Text } from '@elytracms/studio/ui'\nimport { activeViewIdFromPath, isNavViewVisible, useStudioNav } from './app-views/sidebar'\nimport type { ResolvedRailView } from './app-views/sidebar'\nimport { useFieldRenderers } from './cms/field-renderer'\nimport { useProjectRole } from './auth-context'\nimport type { NavPermissionContext } from './app-views/module'\n\nexport interface SectionNavProps {\n project: Project\n /** User-toggled collapse (EC-214) — icons only when collapsed, persisted by the shell. */\n collapsed: boolean\n onToggleCollapsed: () => void\n}\n\n/**\n * Vertical section rail (brief §8.1), driven by the configurable sidebar (EC-237):\n * a consumer-extendable tree of views / groups / separators resolved over the\n * built-in views (EC-239). Availability-aware (`unavailable` items are absent per\n * EC-221); the active item is matched by the VIEW ID in the URL, so two views\n * backed by the same surface (Content vs. a focused Categories) highlight\n * independently. The preference-persisted collapse and the strong active marker\n * (EC-214) are preserved. Customization issues (duplicate view id, invalid view,\n * non-component renderers) render at the rail foot — visible, never silent.\n */\nexport function SectionNav({ project, collapsed, onToggleCollapsed }: SectionNavProps) {\n const { pathname } = useLocation()\n const activeViewId = activeViewIdFromPath(pathname)\n const { nodes, issues: navIssues } = useStudioNav(project)\n const { issues: rendererIssues } = useFieldRenderers()\n const issues = [...navIssues, ...rendererIssues]\n const projectId = project.id\n\n // EC-240 (AD-18): per-nav-item, default-open permission gating. The signed-in\n // user's role drives an OPTIONAL `visible` predicate on any view; absent →\n // visible. UI reflection only — the server chokepoint stays the real boundary,\n // and a permission-hidden view is still reachable by URL (EC-221).\n const role = useProjectRole(projectId)\n const navCtx: NavPermissionContext = { project, role }\n const isVisible = (view: ResolvedRailView) => isNavViewVisible(view, navCtx)\n\n // EC-293: per-parent collapse of nested sub-views, persisted (read after mount,\n // like the rail-collapse pref — no flash script needed). A collapsed parent hides\n // its children but stays a navigable link.\n const [collapsedViews, setCollapsedViews] = useState<Set<string>>(() => new Set())\n useEffect(() => {\n setCollapsedViews(readCollapsedViews())\n }, [])\n const toggleViewCollapsed = useCallback((id: string) => {\n setCollapsedViews((prev) => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n storeCollapsedViews(next)\n return next\n })\n }, [])\n\n return (\n <nav\n aria-label=\"Sections\"\n className={cn(\n 'flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150',\n collapsed ? 'w-[3.5rem]' : 'w-56',\n )}\n >\n {nodes.map((node, index) => {\n if (node.kind === 'separator') {\n return <div key={`sep-${index}`} className=\"my-1.5 border-t border-border/60\" aria-hidden />\n }\n if (node.kind === 'group') {\n const views = node.views.filter(\n (view) => view.availability !== 'unavailable' && isVisible(view),\n )\n if (views.length === 0) return null\n return (\n <div key={`group-${node.label}-${index}`} className=\"mt-2 first:mt-0\">\n {collapsed ? (\n <div className=\"my-1 border-t border-border/60\" aria-hidden />\n ) : (\n <Text\n size=\"xs\"\n tone=\"subtle\"\n className=\"px-3 pb-1 pt-1 font-medium uppercase tracking-wide\"\n >\n {node.label}\n </Text>\n )}\n {views.map((view) => (\n <NavViewLink\n key={view.id}\n projectId={projectId}\n view={view}\n activeViewId={activeViewId}\n collapsed={collapsed}\n />\n ))}\n </div>\n )\n }\n // EC-221: an unavailable view is absent from the rail (not greyed) — it\n // stays reachable by URL, the rail simply doesn't advertise it. EC-240: a\n // permission-hidden view is likewise absent-but-addressable.\n if (node.availability === 'unavailable' || !isVisible(node)) return null\n // EC-293: a view may nest its sub-views (CMS collections). The parent renders\n // as a link with a disclosure chevron; its children render indented below,\n // hidden when the rail is collapsed OR this parent is collapsed. The parent\n // gets a subtle active hint when one of its children is the active view.\n const hasChildren = (node.children?.length ?? 0) > 0\n const hasActiveChild = node.children?.some((child) => child.id === activeViewId) ?? false\n const viewCollapsed = collapsedViews.has(node.id)\n return (\n <Fragment key={node.id}>\n <NavViewLink\n projectId={projectId}\n view={node}\n activeViewId={activeViewId}\n collapsed={collapsed}\n hasActiveChild={hasActiveChild}\n collapsible={hasChildren && !collapsed}\n expanded={!viewCollapsed}\n onToggleExpanded={() => toggleViewCollapsed(node.id)}\n />\n {collapsed || viewCollapsed\n ? null\n : node.children?.filter(isVisible).map((child) => (\n <NavViewLink\n key={child.id}\n projectId={projectId}\n view={child}\n activeViewId={activeViewId}\n collapsed={collapsed}\n nested\n />\n ))}\n </Fragment>\n )\n })}\n\n <div className=\"mt-auto flex flex-col gap-0.5\">\n {issues.length > 0 ? <NavIssues issues={issues} collapsed={collapsed} /> : null}\n\n {/* Collapse toggle pinned to the rail foot (EC-214). */}\n <button\n type=\"button\"\n onClick={onToggleCollapsed}\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n aria-pressed={collapsed}\n title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n className={cn(\n 'flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',\n collapsed ? 'justify-center px-0' : 'px-3',\n )}\n >\n {collapsed ? (\n <PanelLeftOpen className=\"size-4 shrink-0\" />\n ) : (\n <>\n <PanelLeftClose className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate text-left\">Collapse</span>\n </>\n )}\n </button>\n </div>\n </nav>\n )\n}\n\n/** One rail view (top-level or nested) — a Link to its home route, active by view id. */\nfunction NavViewLink({\n projectId,\n view,\n activeViewId,\n collapsed,\n nested = false,\n hasActiveChild = false,\n collapsible = false,\n expanded = true,\n onToggleExpanded,\n}: {\n projectId: string\n view: ResolvedRailView\n activeViewId: string | null\n collapsed: boolean\n nested?: boolean\n /** EC-293: this parent's a child is the active view → a subtle (non-accent) hint. */\n hasActiveChild?: boolean\n /** EC-293: this parent has nested children → render a disclosure chevron. */\n collapsible?: boolean\n /** Whether the nested children are shown (chevron points down) or hidden. */\n expanded?: boolean\n /** Toggle this parent's collapse (the chevron click — does not navigate). */\n onToggleExpanded?: () => void\n}) {\n const isActive = view.id === activeViewId\n const Icon = view.icon\n const link = (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: view.path }}\n title={view.label}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'group relative flex h-9 items-center gap-2.5 rounded-md text-sm transition-colors',\n // Collapsible parents reserve room on the right for the chevron toggle.\n collapsed ? 'justify-center px-0' : nested ? 'pl-7 pr-3' : collapsible ? 'pl-3 pr-9' : 'px-3',\n isActive\n ? 'bg-accent font-medium text-accent-foreground'\n : hasActiveChild\n ? 'text-foreground hover:bg-muted'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {/* Strong active marker: a leading accent bar (EC-214). */}\n {isActive ? (\n <span className=\"absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary\" aria-hidden />\n ) : null}\n <Icon className=\"size-4 shrink-0\" />\n {collapsed ? null : <span className=\"min-w-0 flex-1 truncate\">{view.label}</span>}\n {view.availability === 'planned' && !collapsed ? (\n <span className=\"size-1.5 shrink-0 rounded-full bg-warning\" aria-hidden title=\"Planned\" />\n ) : null}\n </Link>\n )\n // A leaf (or the collapsed icons-only rail) is just the link.\n if (!collapsible) return link\n // A parent pairs the link with a sibling chevron toggle (so the chevron collapses\n // without navigating, and the link still navigates to the parent surface).\n return (\n <div className=\"relative\">\n {link}\n <button\n type=\"button\"\n onClick={onToggleExpanded}\n aria-label={`${expanded ? 'Collapse' : 'Expand'} ${view.label}`}\n aria-expanded={expanded}\n className=\"absolute right-1 top-1/2 grid size-6 -translate-y-1/2 place-items-center rounded text-muted-foreground/70 transition-colors hover:bg-background hover:text-foreground\"\n >\n <ChevronDown\n className={cn('size-3.5 transition-transform', expanded ? '' : '-rotate-90')}\n aria-hidden\n />\n </button>\n </div>\n )\n}\n\n/** Visible customization issues at the rail foot — the seam's \"never silent\" (EC-236). */\nfunction NavIssues({ issues, collapsed }: { issues: CustomizationIssue[]; collapsed: boolean }) {\n if (collapsed) {\n return (\n <div\n className=\"grid h-9 place-items-center text-amber-600 dark:text-amber-400\"\n title={issues.map((issue) => issue.message).join('\\n')}\n >\n <AlertTriangle className=\"size-4\" aria-label={`${issues.length} sidebar issues`} />\n </div>\n )\n }\n return (\n <div className=\"mb-1 flex flex-col gap-1 rounded-md border border-amber-500/30 bg-amber-500/5 p-2\">\n {issues.map((issue, index) => (\n <div\n key={`${issue.point}:${issue.id ?? index}`}\n className=\"flex items-start gap-1.5 text-amber-600 dark:text-amber-400\"\n title={issue.message}\n >\n <AlertTriangle className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden />\n <Text size=\"xs\" className=\"min-w-0 leading-snug\">\n {issue.message}\n </Text>\n </div>\n ))}\n </div>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"section-nav.js","sourceRoot":"","sources":["../../src/components/section-nav.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Fragment, useCallback, useEffect, useState } from 'react'\nimport { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router'\nimport { AlertTriangle, ChevronDown, PanelLeftClose, PanelLeftOpen } from 'lucide-react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { readCollapsedViews, storeCollapsedViews } from '@elytracms/studio/lib/rail'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Text } from '@elytracms/studio/ui'\nimport { activeViewIdFromPath, isNavViewVisible, useStudioNav } from './app-views/sidebar'\nimport type { ResolvedRailView } from './app-views/sidebar'\nimport { useFieldRenderers } from './cms/field-renderer'\nimport { useProjectRole } from './auth-context'\nimport type { NavPermissionContext } from './app-views/module'\n\nexport interface SectionNavProps {\n project: Project\n /** User-toggled collapse (EC-214) — icons only when collapsed, persisted by the shell. */\n collapsed: boolean\n onToggleCollapsed: () => void\n}\n\n/**\n * Vertical section rail (brief §8.1), driven by the configurable sidebar (EC-237):\n * a consumer-extendable tree of views / groups / separators resolved over the\n * built-in views (EC-239). Availability-aware (`unavailable` items are absent per\n * EC-221); the active item is matched by the VIEW ID in the URL, so two views\n * backed by the same surface (Content vs. a focused Categories) highlight\n * independently. The preference-persisted collapse and the strong active marker\n * (EC-214) are preserved. Customization issues (duplicate view id, invalid view,\n * non-component renderers) render at the rail foot — visible, never silent.\n */\nexport function SectionNav({ project, collapsed, onToggleCollapsed }: SectionNavProps) {\n const { pathname } = useLocation()\n const activeViewId = activeViewIdFromPath(pathname)\n const { nodes, issues: navIssues } = useStudioNav(project)\n const { issues: rendererIssues } = useFieldRenderers()\n const issues = [...navIssues, ...rendererIssues]\n const projectId = project.id\n\n // EC-240 (AD-18): per-nav-item, default-open permission gating. The signed-in\n // user's role drives an OPTIONAL `visible` predicate on any view; absent →\n // visible. UI reflection only — the server chokepoint stays the real boundary,\n // and a permission-hidden view is still reachable by URL (EC-221).\n const role = useProjectRole(projectId)\n const navCtx: NavPermissionContext = { project, role }\n const isVisible = (view: ResolvedRailView) => isNavViewVisible(view, navCtx)\n\n // EC-293: per-parent collapse of nested sub-views, persisted (read after mount,\n // like the rail-collapse pref — no flash script needed). A collapsed parent hides\n // its children but stays a navigable link.\n const [collapsedViews, setCollapsedViews] = useState<Set<string>>(() => new Set())\n useEffect(() => {\n setCollapsedViews(readCollapsedViews())\n }, [])\n const toggleViewCollapsed = useCallback((id: string) => {\n setCollapsedViews((prev) => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n storeCollapsedViews(next)\n return next\n })\n }, [])\n\n return (\n <nav\n aria-label={translate('shell.nav.label')}\n className={cn(\n 'flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150',\n collapsed ? 'w-[3.5rem]' : 'w-56',\n )}\n >\n {nodes.map((node, index) => {\n if (node.kind === 'separator') {\n return <div key={`sep-${index}`} className=\"my-1.5 border-t border-border/60\" aria-hidden />\n }\n if (node.kind === 'group') {\n const views = node.views.filter(\n (view) => view.availability !== 'unavailable' && isVisible(view),\n )\n if (views.length === 0) return null\n return (\n <div key={`group-${node.label}-${index}`} className=\"mt-2 first:mt-0\">\n {collapsed ? (\n <div className=\"my-1 border-t border-border/60\" aria-hidden />\n ) : (\n <Text\n size=\"xs\"\n tone=\"subtle\"\n className=\"px-3 pb-1 pt-1 font-medium uppercase tracking-wide\"\n >\n {node.label}\n </Text>\n )}\n {views.map((view) => (\n <NavViewLink\n key={view.id}\n projectId={projectId}\n view={view}\n activeViewId={activeViewId}\n collapsed={collapsed}\n />\n ))}\n </div>\n )\n }\n // EC-221: an unavailable view is absent from the rail (not greyed) — it\n // stays reachable by URL, the rail simply doesn't advertise it. EC-240: a\n // permission-hidden view is likewise absent-but-addressable.\n if (node.availability === 'unavailable' || !isVisible(node)) return null\n // EC-293: a view may nest its sub-views (CMS collections). The parent renders\n // as a link with a disclosure chevron; its children render indented below,\n // hidden when the rail is collapsed OR this parent is collapsed. The parent\n // gets a subtle active hint when one of its children is the active view.\n const hasChildren = (node.children?.length ?? 0) > 0\n const hasActiveChild = node.children?.some((child) => child.id === activeViewId) ?? false\n const viewCollapsed = collapsedViews.has(node.id)\n return (\n <Fragment key={node.id}>\n <NavViewLink\n projectId={projectId}\n view={node}\n activeViewId={activeViewId}\n collapsed={collapsed}\n hasActiveChild={hasActiveChild}\n collapsible={hasChildren && !collapsed}\n expanded={!viewCollapsed}\n onToggleExpanded={() => toggleViewCollapsed(node.id)}\n />\n {collapsed || viewCollapsed\n ? null\n : node.children?.filter(isVisible).map((child) => (\n <NavViewLink\n key={child.id}\n projectId={projectId}\n view={child}\n activeViewId={activeViewId}\n collapsed={collapsed}\n nested\n />\n ))}\n </Fragment>\n )\n })}\n\n <div className=\"mt-auto flex flex-col gap-0.5\">\n {issues.length > 0 ? <NavIssues issues={issues} collapsed={collapsed} /> : null}\n\n {/* Collapse toggle pinned to the rail foot (EC-214). */}\n <button\n type=\"button\"\n onClick={onToggleCollapsed}\n aria-label={collapsed ? translate('shell.sidebar.expand') : translate('shell.sidebar.collapse')}\n aria-pressed={collapsed}\n title={collapsed ? translate('shell.sidebar.expand') : translate('shell.sidebar.collapse')}\n className={cn(\n 'flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',\n collapsed ? 'justify-center px-0' : 'px-3',\n )}\n >\n {collapsed ? (\n <PanelLeftOpen className=\"size-4 shrink-0\" />\n ) : (\n <>\n <PanelLeftClose className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate text-left\">{translate('shell.sidebar.collapseLabel')}</span>\n </>\n )}\n </button>\n </div>\n </nav>\n )\n}\n\n/** One rail view (top-level or nested) — a Link to its home route, active by view id. */\nfunction NavViewLink({\n projectId,\n view,\n activeViewId,\n collapsed,\n nested = false,\n hasActiveChild = false,\n collapsible = false,\n expanded = true,\n onToggleExpanded,\n}: {\n projectId: string\n view: ResolvedRailView\n activeViewId: string | null\n collapsed: boolean\n nested?: boolean\n /** EC-293: this parent's a child is the active view → a subtle (non-accent) hint. */\n hasActiveChild?: boolean\n /** EC-293: this parent has nested children → render a disclosure chevron. */\n collapsible?: boolean\n /** Whether the nested children are shown (chevron points down) or hidden. */\n expanded?: boolean\n /** Toggle this parent's collapse (the chevron click — does not navigate). */\n onToggleExpanded?: () => void\n}) {\n const isActive = view.id === activeViewId\n const Icon = view.icon\n const link = (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: view.path }}\n title={view.label}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'group relative flex h-9 items-center gap-2.5 rounded-md text-sm transition-colors',\n // Collapsible parents reserve room on the right for the chevron toggle.\n collapsed ? 'justify-center px-0' : nested ? 'pl-7 pr-3' : collapsible ? 'pl-3 pr-9' : 'px-3',\n isActive\n ? 'bg-accent font-medium text-accent-foreground'\n : hasActiveChild\n ? 'text-foreground hover:bg-muted'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {/* Strong active marker: a leading accent bar (EC-214). */}\n {isActive ? (\n <span className=\"absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary\" aria-hidden />\n ) : null}\n <Icon className=\"size-4 shrink-0\" />\n {collapsed ? null : <span className=\"min-w-0 flex-1 truncate\">{view.label}</span>}\n {view.availability === 'planned' && !collapsed ? (\n <span className=\"size-1.5 shrink-0 rounded-full bg-warning\" aria-hidden title={translate('shell.nav.planned')} />\n ) : null}\n </Link>\n )\n // A leaf (or the collapsed icons-only rail) is just the link.\n if (!collapsible) return link\n // A parent pairs the link with a sibling chevron toggle (so the chevron collapses\n // without navigating, and the link still navigates to the parent surface).\n return (\n <div className=\"relative\">\n {link}\n <button\n type=\"button\"\n onClick={onToggleExpanded}\n aria-label={translate(expanded ? 'shell.nav.collapseView' : 'shell.nav.expandView', {\n name: view.label,\n })}\n aria-expanded={expanded}\n className=\"absolute right-1 top-1/2 grid size-6 -translate-y-1/2 place-items-center rounded text-muted-foreground/70 transition-colors hover:bg-background hover:text-foreground\"\n >\n <ChevronDown\n className={cn('size-3.5 transition-transform', expanded ? '' : '-rotate-90')}\n aria-hidden\n />\n </button>\n </div>\n )\n}\n\n/** Visible customization issues at the rail foot — the seam's \"never silent\" (EC-236). */\nfunction NavIssues({ issues, collapsed }: { issues: CustomizationIssue[]; collapsed: boolean }) {\n if (collapsed) {\n return (\n <div\n className=\"grid h-9 place-items-center text-amber-600 dark:text-amber-400\"\n title={issues.map((issue) => issue.message).join('\\n')}\n >\n <AlertTriangle className=\"size-4\" aria-label={translate('shell.nav.issuesCount', { count: issues.length })} />\n </div>\n )\n }\n return (\n <div className=\"mb-1 flex flex-col gap-1 rounded-md border border-amber-500/30 bg-amber-500/5 p-2\">\n {issues.map((issue, index) => (\n <div\n key={`${issue.point}:${issue.id ?? index}`}\n className=\"flex items-start gap-1.5 text-amber-600 dark:text-amber-400\"\n title={issue.message}\n >\n <AlertTriangle className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden />\n <Text size=\"xs\" className=\"min-w-0 leading-snug\">\n {issue.message}\n </Text>\n </div>\n ))}\n </div>\n )\n}\n"]}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { CircleAlert, Clock, Inbox, Loader2, Lock } from 'lucide-react';
|
|
3
3
|
import { Badge, Heading, Stack, Text } from '@elytracms/studio/ui';
|
|
4
4
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
5
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
5
6
|
/**
|
|
6
7
|
* Resolve the runtime state of a section (brief §8.1, §11). A `forced` value
|
|
7
8
|
* (e.g. from a `?state=` param) wins, making every state reachable
|
|
@@ -16,38 +17,14 @@ export function resolveSectionState(availability, forced) {
|
|
|
16
17
|
return 'unavailable';
|
|
17
18
|
return 'working';
|
|
18
19
|
}
|
|
20
|
+
// Structural (icon/variant) only; the title/description/badge copy is resolved at
|
|
21
|
+
// the render site via `translate` so it reflects the active UI locale (EC-241).
|
|
19
22
|
const PRESETS = {
|
|
20
|
-
empty: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
loading: {
|
|
27
|
-
icon: Loader2,
|
|
28
|
-
title: 'Loading…',
|
|
29
|
-
description: 'Fetching the latest state for this section.',
|
|
30
|
-
badge: { label: 'loading', variant: 'info' },
|
|
31
|
-
spin: true,
|
|
32
|
-
},
|
|
33
|
-
error: {
|
|
34
|
-
icon: CircleAlert,
|
|
35
|
-
title: 'Something went wrong',
|
|
36
|
-
description: 'This section could not be loaded. Try again in a moment.',
|
|
37
|
-
badge: { label: 'error', variant: 'error' },
|
|
38
|
-
},
|
|
39
|
-
unavailable: {
|
|
40
|
-
icon: Lock,
|
|
41
|
-
title: 'Not available',
|
|
42
|
-
description: 'This section is not available for the active project.',
|
|
43
|
-
badge: { label: 'unavailable', variant: 'outline' },
|
|
44
|
-
},
|
|
45
|
-
planned: {
|
|
46
|
-
icon: Clock,
|
|
47
|
-
title: 'Planned',
|
|
48
|
-
description: 'This surface is planned and not yet enabled for this project.',
|
|
49
|
-
badge: { label: 'planned', variant: 'warning' },
|
|
50
|
-
},
|
|
23
|
+
empty: { icon: Inbox, badgeVariant: 'outline' },
|
|
24
|
+
loading: { icon: Loader2, badgeVariant: 'info', spin: true },
|
|
25
|
+
error: { icon: CircleAlert, badgeVariant: 'error' },
|
|
26
|
+
unavailable: { icon: Lock, badgeVariant: 'outline' },
|
|
27
|
+
planned: { icon: Clock, badgeVariant: 'warning' },
|
|
51
28
|
};
|
|
52
29
|
/** Loading uses a skeleton; the rest use a centered placeholder. Consistent across sections. */
|
|
53
30
|
export function SectionStateView({ state, title, description, action, className, }) {
|
|
@@ -55,12 +32,16 @@ export function SectionStateView({ state, title, description, action, className,
|
|
|
55
32
|
return _jsx(SectionSkeleton, { className: className });
|
|
56
33
|
const preset = PRESETS[state];
|
|
57
34
|
const Icon = preset.icon;
|
|
35
|
+
// `state` is narrowed to a non-loading state here, so each has a title/description/badge key.
|
|
36
|
+
const presetTitle = translate(`shell.sectionState.${state}.title`);
|
|
37
|
+
const presetDescription = translate(`shell.sectionState.${state}.description`);
|
|
38
|
+
const badgeLabel = translate(`shell.sectionState.badge.${state}`);
|
|
58
39
|
const tone = state === 'error'
|
|
59
40
|
? 'border-destructive/30 bg-destructive/5'
|
|
60
41
|
: state === 'planned'
|
|
61
42
|
? 'border-warning/30 bg-warning/5'
|
|
62
43
|
: 'border-border bg-card/40';
|
|
63
|
-
return (_jsx("div", { "data-section-state": state, className: cn('grid min-h-[18rem] place-items-center p-8', className), children: _jsxs(Stack, { gap: "3", align: "center", className: cn('max-w-sm rounded-xl border border-dashed px-8 py-10 text-center', tone), children: [_jsx("span", { className: "grid size-11 place-items-center rounded-lg border border-border bg-background text-muted-foreground", children: _jsx(Icon, { className: "size-5" }) }), _jsxs(Stack, { gap: "1", align: "center", children: [_jsx(Heading, { level: 2, size: "h3", children: title ??
|
|
44
|
+
return (_jsx("div", { "data-section-state": state, className: cn('grid min-h-[18rem] place-items-center p-8', className), children: _jsxs(Stack, { gap: "3", align: "center", className: cn('max-w-sm rounded-xl border border-dashed px-8 py-10 text-center', tone), children: [_jsx("span", { className: "grid size-11 place-items-center rounded-lg border border-border bg-background text-muted-foreground", children: _jsx(Icon, { className: "size-5" }) }), _jsxs(Stack, { gap: "1", align: "center", children: [_jsx(Heading, { level: 2, size: "h3", children: title ?? presetTitle }), _jsx(Text, { size: "sm", tone: "muted", children: description ?? presetDescription })] }), _jsx(Badge, { variant: preset.badgeVariant, children: badgeLabel }), action ? _jsx("div", { className: "pt-1", children: action }) : null] }) }));
|
|
64
45
|
}
|
|
65
46
|
/** Shimmer skeleton standing in for an in-flight section load. */
|
|
66
47
|
export function SectionSkeleton({ className }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-state.js","sourceRoot":"","sources":["../../src/components/section-state.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAA;AAGxF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAClE,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"section-state.js","sourceRoot":"","sources":["../../src/components/section-state.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAA;AAGxF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAClE,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,YAAiC,EACjC,MAA4B;IAE5B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAA;IACzB,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAChD,IAAI,YAAY,KAAK,aAAa;QAAE,OAAO,aAAa,CAAA;IACxD,OAAO,SAAS,CAAA;AAClB,CAAC;AAQD,kFAAkF;AAClF,gFAAgF;AAChF,MAAM,OAAO,GAA0D;IACrE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE;IAC/C,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE;IAC5D,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE;IACnD,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE;IACpD,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE;CAClD,CAAA;AAWD,gGAAgG;AAChG,MAAM,UAAU,gBAAgB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,WAAW,EACX,MAAM,EACN,SAAS,GACa;IACtB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,GAAI,CAAA;IAEzE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;IACxB,8FAA8F;IAC9F,MAAM,WAAW,GAAG,SAAS,CAAC,sBAAsB,KAAK,QAAQ,CAAC,CAAA;IAClE,MAAM,iBAAiB,GAAG,SAAS,CAAC,sBAAsB,KAAK,cAAc,CAAC,CAAA;IAC9E,MAAM,UAAU,GAAG,SAAS,CAAC,4BAA4B,KAAK,EAAE,CAAC,CAAA;IACjE,MAAM,IAAI,GACR,KAAK,KAAK,OAAO;QACf,CAAC,CAAC,wCAAwC;QAC1C,CAAC,CAAC,KAAK,KAAK,SAAS;YACnB,CAAC,CAAC,gCAAgC;YAClC,CAAC,CAAC,0BAA0B,CAAA;IAElC,OAAO,CACL,oCACsB,KAAK,EACzB,SAAS,EAAE,EAAE,CAAC,2CAA2C,EAAE,SAAS,CAAC,YAErE,MAAC,KAAK,IACJ,GAAG,EAAC,GAAG,EACP,KAAK,EAAC,QAAQ,EACd,SAAS,EAAE,EAAE,CAAC,iEAAiE,EAAE,IAAI,CAAC,aAEtF,eAAM,SAAS,EAAC,qGAAqG,YACnH,KAAC,IAAI,IAAC,SAAS,EAAC,QAAQ,GAAG,GACtB,EACP,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aAC3B,KAAC,OAAO,IAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAC,IAAI,YACzB,KAAK,IAAI,WAAW,GACb,EACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,WAAW,IAAI,iBAAiB,GAC5B,IACD,EACR,KAAC,KAAK,IAAC,OAAO,EAAE,MAAM,CAAC,YAAY,YAAG,UAAU,GAAS,EACxD,MAAM,CAAC,CAAC,CAAC,cAAK,SAAS,EAAC,MAAM,YAAE,MAAM,GAAO,CAAC,CAAC,CAAC,IAAI,IAC/C,GACJ,CACP,CAAA;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAAC,EAAE,SAAS,EAA0B;IACnE,OAAO,CACL,qCAAwB,SAAS,EAAC,SAAS,EAAE,EAAE,CAAC,eAAe,EAAE,SAAS,CAAC,gCACzE,cAAK,SAAS,EAAC,4CAA4C,GAAG,EAC9D,cAAK,SAAS,EAAC,0CAA0C,YACtD,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CACvC,cAAa,SAAS,EAAC,2CAA2C,IAAxD,CAAC,CAA0D,CACtE,CAAC,GACE,IACF,CACP,CAAA;AACH,CAAC","sourcesContent":["import { CircleAlert, Clock, Inbox, Loader2, Lock, type LucideIcon } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { SectionAvailability, SectionState } from '@elytracms/core/studio-core'\nimport { Badge, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Resolve the runtime state of a section (brief §8.1, §11). A `forced` value\n * (e.g. from a `?state=` param) wins, making every state reachable\n * deterministically; otherwise availability decides.\n */\nexport function resolveSectionState(\n availability: SectionAvailability,\n forced?: SectionState | null,\n): SectionState {\n if (forced) return forced\n if (availability === 'planned') return 'planned'\n if (availability === 'unavailable') return 'unavailable'\n return 'working'\n}\n\ninterface StatePreset {\n icon: LucideIcon\n badgeVariant: 'outline' | 'warning' | 'error' | 'info'\n spin?: boolean\n}\n\n// Structural (icon/variant) only; the title/description/badge copy is resolved at\n// the render site via `translate` so it reflects the active UI locale (EC-241).\nconst PRESETS: Record<Exclude<SectionState, 'working'>, StatePreset> = {\n empty: { icon: Inbox, badgeVariant: 'outline' },\n loading: { icon: Loader2, badgeVariant: 'info', spin: true },\n error: { icon: CircleAlert, badgeVariant: 'error' },\n unavailable: { icon: Lock, badgeVariant: 'outline' },\n planned: { icon: Clock, badgeVariant: 'warning' },\n}\n\nexport interface SectionStateViewProps {\n state: Exclude<SectionState, 'working'>\n /** Section-specific heading, e.g. \"CMS\" — overrides the generic preset title. */\n title?: string\n description?: ReactNode\n action?: ReactNode\n className?: string\n}\n\n/** Loading uses a skeleton; the rest use a centered placeholder. Consistent across sections. */\nexport function SectionStateView({\n state,\n title,\n description,\n action,\n className,\n}: SectionStateViewProps) {\n if (state === 'loading') return <SectionSkeleton className={className} />\n\n const preset = PRESETS[state]\n const Icon = preset.icon\n // `state` is narrowed to a non-loading state here, so each has a title/description/badge key.\n const presetTitle = translate(`shell.sectionState.${state}.title`)\n const presetDescription = translate(`shell.sectionState.${state}.description`)\n const badgeLabel = translate(`shell.sectionState.badge.${state}`)\n const tone =\n state === 'error'\n ? 'border-destructive/30 bg-destructive/5'\n : state === 'planned'\n ? 'border-warning/30 bg-warning/5'\n : 'border-border bg-card/40'\n\n return (\n <div\n data-section-state={state}\n className={cn('grid min-h-[18rem] place-items-center p-8', className)}\n >\n <Stack\n gap=\"3\"\n align=\"center\"\n className={cn('max-w-sm rounded-xl border border-dashed px-8 py-10 text-center', tone)}\n >\n <span className=\"grid size-11 place-items-center rounded-lg border border-border bg-background text-muted-foreground\">\n <Icon className=\"size-5\" />\n </span>\n <Stack gap=\"1\" align=\"center\">\n <Heading level={2} size=\"h3\">\n {title ?? presetTitle}\n </Heading>\n <Text size=\"sm\" tone=\"muted\">\n {description ?? presetDescription}\n </Text>\n </Stack>\n <Badge variant={preset.badgeVariant}>{badgeLabel}</Badge>\n {action ? <div className=\"pt-1\">{action}</div> : null}\n </Stack>\n </div>\n )\n}\n\n/** Shimmer skeleton standing in for an in-flight section load. */\nexport function SectionSkeleton({ className }: { className?: string }) {\n return (\n <div data-section-state=\"loading\" className={cn('space-y-4 p-6', className)} aria-busy>\n <div className=\"h-7 w-48 animate-pulse rounded-md bg-muted\" />\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-28 animate-pulse rounded-xl bg-muted/70\" />\n ))}\n </div>\n </div>\n )\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Badge, Code, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
3
3
|
import { exportTargetLabel } from '@elytracms/studio/lib/persona/labels';
|
|
4
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
4
5
|
/**
|
|
5
6
|
* Project settings — read-only (vision AD-8). Identity, locales, and
|
|
6
7
|
* environments are owned by `elytra.config.ts` and read at runtime; the studio
|
|
@@ -9,7 +10,7 @@ import { exportTargetLabel } from '@elytracms/studio/lib/persona/labels';
|
|
|
9
10
|
*/
|
|
10
11
|
export function SettingsForm({ project }) {
|
|
11
12
|
const { settings } = project;
|
|
12
|
-
return (_jsx("div", { className: "mx-auto w-full max-w-3xl p-4 sm:p-6", children: _jsxs(Stack, { gap: "6", children: [_jsxs(Text, { size: "sm", tone: "muted", children: ["Project identity and settings are defined in code \u2014 ", _jsx(Code, { tone: "muted", children: "elytra.config.ts" }), ". Edit that file to change the name, locales, or environments; the studio reads them at runtime."] }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "sm", weight: "semibold", children:
|
|
13
|
+
return (_jsx("div", { className: "mx-auto w-full max-w-3xl p-4 sm:p-6", children: _jsxs(Stack, { gap: "6", children: [_jsxs(Text, { size: "sm", tone: "muted", children: ["Project identity and settings are defined in code \u2014 ", _jsx(Code, { tone: "muted", children: "elytra.config.ts" }), ". Edit that file to change the name, locales, or environments; the studio reads them at runtime."] }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.identity.title') }) }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(ReadField, { label: translate('settings.identity.name'), children: _jsx(Text, { size: "sm", children: project.name }) }), _jsx(ReadField, { label: translate('settings.identity.urlKey'), children: _jsx(Code, { tone: "muted", children: project.slug }) })] })] }), _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.locales.title') }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "muted", mono: true, children: translate('settings.locales.configuredCount', { count: settings.locales.length }) })] }), _jsx(Stack, { gap: "3", className: "p-4", children: _jsx(ReadField, { label: translate('settings.locales.configured'), children: _jsx(Row, { gap: "2", wrap: true, children: settings.locales.map((locale) => (_jsxs(Badge, { variant: locale === settings.defaultLocale ? 'primary' : 'outline', children: [locale, locale === settings.defaultLocale ? translate('settings.locales.defaultSuffix') : ''] }, locale))) }) }) })] }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.environments.title') }) }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(ReadField, { label: translate('settings.environments.label'), children: _jsx(Stack, { gap: "2", children: settings.environments.map((env) => (_jsx(Row, { gap: "2", className: "rounded-md border border-border px-3 py-2", title: env.key, children: _jsx(Text, { size: "sm", children: env.label }) }, env.key))) }) }), _jsx(ReadField, { label: translate('settings.exportTarget'), children: _jsx(Text, { size: "sm", children: exportTargetLabel(settings.exportTarget) }) })] })] })] }) }));
|
|
13
14
|
}
|
|
14
15
|
function ReadField({ label, children }) {
|
|
15
16
|
return (_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", weight: "medium", children: label }), children] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-form.js","sourceRoot":"","sources":["../../src/components/settings-form.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAChG,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;
|
|
1
|
+
{"version":3,"file":"settings-form.js","sourceRoot":"","sources":["../../src/components/settings-form.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { Badge, Code, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { exportTargetLabel } from '@elytracms/studio/lib/persona/labels'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Project settings — read-only (vision AD-8). Identity, locales, and\n * environments are owned by `elytra.config.ts` and read at runtime; the studio\n * never writes them back. This surface displays the resolved config so editors\n * can see how the project is wired without exposing a (fake) edit path.\n */\nexport function SettingsForm({ project }: { project: Project }) {\n const { settings } = project\n return (\n <div className=\"mx-auto w-full max-w-3xl p-4 sm:p-6\">\n <Stack gap=\"6\">\n <Text size=\"sm\" tone=\"muted\">\n Project identity and settings are defined in code — <Code tone=\"muted\">elytra.config.ts</Code>\n . Edit that file to change the name, locales, or environments; the studio reads them at\n runtime.\n </Text>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.identity.title')}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.identity.name')}>\n <Text size=\"sm\">{project.name}</Text>\n </ReadField>\n <ReadField label={translate('settings.identity.urlKey')}>\n <Code tone=\"muted\">{project.slug}</Code>\n </ReadField>\n </Stack>\n </Panel>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.locales.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"muted\" mono>\n {translate('settings.locales.configuredCount', { count: settings.locales.length })}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.locales.configured')}>\n <Row gap=\"2\" wrap>\n {settings.locales.map((locale) => (\n <Badge\n key={locale}\n variant={locale === settings.defaultLocale ? 'primary' : 'outline'}\n >\n {locale}\n {locale === settings.defaultLocale ? translate('settings.locales.defaultSuffix') : ''}\n </Badge>\n ))}\n </Row>\n </ReadField>\n </Stack>\n </Panel>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.environments.title')}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.environments.label')}>\n <Stack gap=\"2\">\n {settings.environments.map((env) => (\n <Row\n key={env.key}\n gap=\"2\"\n className=\"rounded-md border border-border px-3 py-2\"\n title={env.key}\n >\n <Text size=\"sm\">{env.label}</Text>\n </Row>\n ))}\n </Stack>\n </ReadField>\n <ReadField label={translate('settings.exportTarget')}>\n <Text size=\"sm\">{exportTargetLabel(settings.exportTarget)}</Text>\n </ReadField>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction ReadField({ label, children }: { label: string; children: ReactNode }) {\n return (\n <Stack gap=\"1\">\n <Text size=\"xs\" tone=\"muted\" weight=\"medium\">\n {label}\n </Text>\n {children}\n </Stack>\n )\n}\n"]}
|
|
@@ -5,6 +5,7 @@ import { matchStudioRoute } from '@elytracms/studio/router/match';
|
|
|
5
5
|
import { studioSearch } from '@elytracms/studio/router/search';
|
|
6
6
|
import { Button, Heading, Stack, Text } from '@elytracms/studio/ui';
|
|
7
7
|
import { DataProvider } from '@elytracms/studio/lib/data';
|
|
8
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
8
9
|
import { useStudio } from './studio-context';
|
|
9
10
|
import { StudioShell } from './studio-shell';
|
|
10
11
|
import { AssetsProvider } from './assets/assets-store';
|
|
@@ -95,9 +96,9 @@ function StudioSectionResolver({ project, match, search, }) {
|
|
|
95
96
|
return _jsx(_Fragment, { children: view.render({ project, projectId, deepLink, search }) });
|
|
96
97
|
}
|
|
97
98
|
function ProjectNotFound() {
|
|
98
|
-
return (_jsx("div", { className: "grid min-h-dvh place-items-center p-8", children: _jsxs(Stack, { gap: "3", align: "center", className: "max-w-sm text-center", children: [_jsx(Heading, { size: "display", children:
|
|
99
|
+
return (_jsx("div", { className: "grid min-h-dvh place-items-center p-8", children: _jsxs(Stack, { gap: "3", align: "center", className: "max-w-sm text-center", children: [_jsx(Heading, { size: "display", children: translate('shell.projectNotFound.title') }), _jsx(Text, { tone: "muted", children: translate('shell.projectNotFound.body') }), _jsx(Button, { asChild: true, children: _jsx(Link, { to: "/", children: translate('shell.backToProjects') }) })] }) }));
|
|
99
100
|
}
|
|
100
101
|
function StudioRouteNotFound() {
|
|
101
|
-
return (_jsx("div", { className: "grid h-full place-items-center p-8", children: _jsxs(Stack, { gap: "2", align: "center", className: "max-w-sm text-center", children: [_jsx(Heading, { level: 2, size: "h2", children:
|
|
102
|
+
return (_jsx("div", { className: "grid h-full place-items-center p-8", children: _jsxs(Stack, { gap: "2", align: "center", className: "max-w-sm text-center", children: [_jsx(Heading, { level: 2, size: "h2", children: translate('shell.routeNotFound.title') }), _jsx(Text, { tone: "muted", children: translate('shell.routeNotFound.body') })] }) }));
|
|
102
103
|
}
|
|
103
104
|
//# sourceMappingURL=studio-project-screen.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"studio-project-screen.js","sourceRoot":"","sources":["../../src/components/studio-project-screen.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACrG,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAE9D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEjE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAA4C,CAAA;IACtF,6EAA6E;IAC7E,gFAAgF;IAChF,gFAAgF;IAChF,gFAAgF;IAChF,2EAA2E;IAC3E,iDAAiD;IACjD,MAAM,SAAS,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAA4B,CAAA;IACzE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE,CAAA;IACxC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;IAE5C,kFAAkF;IAClF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC1C,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;IAErB,IAAI,CAAC,OAAO;QAAE,OAAO,KAAC,eAAe,KAAG,CAAA;IAExC,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;IACnD,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,CAAA;IAEtC,OAAO,CACL,KAAC,WAAW,IAAC,OAAO,EAAE,OAAO,YAM3B,KAAC,sBAAsB,IAAkB,SAAS,EAAE,OAAO,CAAC,EAAE,YAC5D,KAAC,gBAAgB,IAAC,OAAO,EAAE,OAAO,YAChC,KAAC,qBAAqB,IAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAI,GACxD,IAHQ,OAAO,CAAC,EAAE,CAId,GACb,CACf,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,gBAAgB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAA6C;IACxF,OAAO;IACL,+EAA+E;IAC/E,8EAA8E;IAC9E,KAAC,YAAY,IAAkB,SAAS,EAAE,OAAO,CAAC,EAAE,YAIlD,KAAC,cAAc,IAAkB,SAAS,EAAE,OAAO,CAAC,EAAE,YACpD,KAAC,cAAc,IAAkB,SAAS,EAAE,OAAO,CAAC,EAAE,YACnD,QAAQ,IADU,OAAO,CAAC,EAAE,CAEd,IAHE,OAAO,CAAC,EAAE,CAId,IARA,OAAO,CAAC,EAAE,CASd,CAChB,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,qBAAqB,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,MAAM,GAKP;IACC,MAAM,SAAS,GAAG,OAAO,CAAC,EAAE,CAAA;IAC5B,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;IAEjC,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QAC9B,gEAAgE;QAChE,OAAO,CACL,KAAC,QAAQ,IACP,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAC1D,OAAO,SACP,CACH,CAAA;IACH,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,KAAC,mBAAmB,KAAG,CAAA;IAEzC,wEAAwE;IACxE,oFAAoF;IACpF,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAC7D,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,KAAC,mBAAmB,KAAG,CAAA;IAErD,OAAO,4BAAG,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAI,CAAA;AACrE,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,CACL,cAAK,SAAS,EAAC,uCAAuC,YACpD,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,sBAAsB,aAC5D,KAAC,OAAO,IAAC,IAAI,EAAC,SAAS,kCAA4B,EACnD,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,0GAEX,EACP,KAAC,MAAM,IAAC,OAAO,kBACb,KAAC,IAAI,IAAC,EAAE,EAAC,GAAG,iCAAwB,GAC7B,IACH,GACJ,CACP,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB;IAC1B,OAAO,CACL,cAAK,SAAS,EAAC,oCAAoC,YACjD,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,sBAAsB,aAC5D,KAAC,OAAO,IAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAC,IAAI,+BAElB,EACV,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,mEAAqD,IACjE,GACJ,CACP,CAAA;AACH,CAAC","sourcesContent":["import { useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { Link, Navigate, STUDIO_PROJECT_ROUTE, useParams, useSearch } from '@elytracms/studio/router'\nimport { matchStudioRoute } from '@elytracms/studio/router/match'\nimport type { StudioRouteMatch } from '@elytracms/studio/router/match'\nimport { studioSearch } from '@elytracms/studio/router/search'\nimport type { StudioSearch } from '@elytracms/studio/router/search'\nimport { Button, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { DataProvider } from '@elytracms/studio/lib/data'\nimport { useStudio } from './studio-context'\nimport { StudioShell } from './studio-shell'\nimport { AssetsProvider } from './assets/assets-store'\nimport { CollabProvider } from './collab/collab-context'\nimport { ProjectSessionProvider } from './project-session'\nimport { flatPath } from './app-views/module'\nimport { landingViewId, useStudioNav } from './app-views/sidebar'\n\n/**\n * EC-223 Stage 6 (catch-all routing): the studio's per-project route component.\n *\n * The consumer mounts ONE splat route (`/$projectId/$`) whose component is this.\n * Everything that was a per-project file route — the provider stack, the section\n * rail shell, and every section screen — now lives inside the package and is\n * resolved here from the URL. The consumer's route tree shrinks to a single\n * ~10-line mount, which is the whole point of Stage 6: the engine owns its\n * navigation, so an external repo consumes the studio without rebuilding a route\n * file per screen.\n *\n * `projectId` stays a real route param (the `$projectId` segment); the trailing\n * pathname (`_splat`) is matched against the engine route table\n * (`matchStudioRoute`) into a module id + segments, and the active module (EC-239)\n * resolves the screen. Every surface already takes its ids as props, so the\n * resolver just hands the module its parsed deep-link.\n */\nexport function StudioProjectScreen() {\n const params = useParams({ strict: false }) as { projectId?: string; _splat?: string }\n // The route's VALIDATED search (the splat's `validateSearch: studioSearch`).\n // Read it through `useSearch`, not `useLocation().search`: the validated search\n // is resolved at route-match time and stable from the component's first render,\n // whereas `location.search` can lag a tick during hydration — long enough for a\n // child `<Navigate>` (the builder redirect) to fire on a momentarily-empty\n // search and drop its `?page=` deep-link target.\n const rawSearch = useSearch({ strict: false }) as Record<string, unknown>\n const studio = useStudio()\n const projectId = params.projectId ?? ''\n const project = studio.getProject(projectId)\n\n // Record the active project once it resolves (preserves recents across sections).\n useEffect(() => {\n if (project) studio.activate(project.id)\n }, [project, studio])\n\n if (!project) return <ProjectNotFound />\n\n const match = matchStudioRoute(params._splat ?? '')\n const search = studioSearch(rawSearch)\n\n return (\n <StudioShell project={project}>\n {/* EC-177 gap 1: in Convex mode the session HYDRATES from persistence\n before any provider mounts — the builder boots from the latest\n persisted revision, publishing reflects the persisted state, and the\n preview content engine serves live documents. Playground renders\n synchronously from fixtures (session: null), exactly as before. */}\n <ProjectSessionProvider key={project.id} projectId={project.id}>\n <ProjectProviders project={project}>\n <StudioSectionResolver project={project} match={match} search={search} />\n </ProjectProviders>\n </ProjectSessionProvider>\n </StudioShell>\n )\n}\n\n/**\n * The per-project provider stack, mounted once the session source is known.\n *\n * EC-287: there is no project-level builder graph anymore — layouts are code and\n * the DB holds content only. The block-field canvas mounts its OWN scoped\n * `BuilderProvider` per document (`composition-field-editor`), so no graph is\n * threaded here. `CollabProvider` stays as the canvas's session-scoped\n * presence/perspective store, seeded graph-lessly.\n */\nfunction ProjectProviders({ project, children }: { project: Project; children: ReactNode }) {\n return (\n // Key by project id so the session data sources reset when switching projects.\n // The data store hydrates/persists this project's `cms-data-sources` records.\n <DataProvider key={project.id} projectId={project.id}>\n {/* EC-195: project-level so the canvas resolves `asset`-typed Image props\n and the inspector picks from the live library — not just the Assets\n section. Asset METADATA only (small); blobs stay lazy. */}\n <AssetsProvider key={project.id} projectId={project.id}>\n <CollabProvider key={project.id} projectId={project.id}>\n {children}\n </CollabProvider>\n </AssetsProvider>\n </DataProvider>\n )\n}\n\n/**\n * The URL→screen switch (EC-223 Stage 6), driven by the view registry DERIVED from\n * the section rail (EC-237/239). The bare project URL redirects to the first\n * navigable rail view; otherwise the matched view id is resolved from the registry\n * (rail views + always-on built-ins) and its `parsePath` interprets the trailing\n * segments into a deep-link the view renders. An unknown view id or an unparseable\n * path is a not-found; rail CONFIG problems (duplicate/invalid views) surface as\n * visible issues in the shell, never silently. Renders inside the shell + provider\n * stack, so every surface sees the same live CMS store and breadcrumb context.\n */\nfunction StudioSectionResolver({\n project,\n match,\n search,\n}: {\n project: Project\n match: StudioRouteMatch\n search: StudioSearch\n}) {\n const projectId = project.id\n const nav = useStudioNav(project)\n\n if (match.section === 'index') {\n // Bare `/$projectId` → the project's first navigable rail view.\n return (\n <Navigate\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: landingViewId(nav, project) }}\n replace\n />\n )\n }\n\n const view = nav.registry.get(match.viewId)\n if (!view) return <StudioRouteNotFound />\n\n // A flat view (no `parsePath`) accepts only the bare path; a nested one\n // (CMS/components) interprets its segments. `null` → an over-deep path → not-found.\n const deepLink = (view.parsePath ?? flatPath)(match.segments)\n if (deepLink === null) return <StudioRouteNotFound />\n\n return <>{view.render({ project, projectId, deepLink, search })}</>\n}\n\nfunction ProjectNotFound() {\n return (\n <div className=\"grid min-h-dvh place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading size=\"display\">Project not found</Heading>\n <Text tone=\"muted\">\n This project doesn’t exist or was deleted. It may only have existed in this session.\n </Text>\n <Button asChild>\n <Link to=\"/\">Back to projects</Link>\n </Button>\n </Stack>\n </div>\n )\n}\n\nfunction StudioRouteNotFound() {\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"2\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading level={2} size=\"h2\">\n Page not found\n </Heading>\n <Text tone=\"muted\">This studio URL doesn’t resolve to a section.</Text>\n </Stack>\n </div>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"studio-project-screen.js","sourceRoot":"","sources":["../../src/components/studio-project-screen.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { Link, Navigate, STUDIO_PROJECT_ROUTE, useParams, useSearch } from '@elytracms/studio/router'\nimport { matchStudioRoute } from '@elytracms/studio/router/match'\nimport type { StudioRouteMatch } from '@elytracms/studio/router/match'\nimport { studioSearch } from '@elytracms/studio/router/search'\nimport type { StudioSearch } from '@elytracms/studio/router/search'\nimport { Button, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { DataProvider } from '@elytracms/studio/lib/data'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { useStudio } from './studio-context'\nimport { StudioShell } from './studio-shell'\nimport { AssetsProvider } from './assets/assets-store'\nimport { CollabProvider } from './collab/collab-context'\nimport { ProjectSessionProvider } from './project-session'\nimport { flatPath } from './app-views/module'\nimport { landingViewId, useStudioNav } from './app-views/sidebar'\n\n/**\n * EC-223 Stage 6 (catch-all routing): the studio's per-project route component.\n *\n * The consumer mounts ONE splat route (`/$projectId/$`) whose component is this.\n * Everything that was a per-project file route — the provider stack, the section\n * rail shell, and every section screen — now lives inside the package and is\n * resolved here from the URL. The consumer's route tree shrinks to a single\n * ~10-line mount, which is the whole point of Stage 6: the engine owns its\n * navigation, so an external repo consumes the studio without rebuilding a route\n * file per screen.\n *\n * `projectId` stays a real route param (the `$projectId` segment); the trailing\n * pathname (`_splat`) is matched against the engine route table\n * (`matchStudioRoute`) into a module id + segments, and the active module (EC-239)\n * resolves the screen. Every surface already takes its ids as props, so the\n * resolver just hands the module its parsed deep-link.\n */\nexport function StudioProjectScreen() {\n const params = useParams({ strict: false }) as { projectId?: string; _splat?: string }\n // The route's VALIDATED search (the splat's `validateSearch: studioSearch`).\n // Read it through `useSearch`, not `useLocation().search`: the validated search\n // is resolved at route-match time and stable from the component's first render,\n // whereas `location.search` can lag a tick during hydration — long enough for a\n // child `<Navigate>` (the builder redirect) to fire on a momentarily-empty\n // search and drop its `?page=` deep-link target.\n const rawSearch = useSearch({ strict: false }) as Record<string, unknown>\n const studio = useStudio()\n const projectId = params.projectId ?? ''\n const project = studio.getProject(projectId)\n\n // Record the active project once it resolves (preserves recents across sections).\n useEffect(() => {\n if (project) studio.activate(project.id)\n }, [project, studio])\n\n if (!project) return <ProjectNotFound />\n\n const match = matchStudioRoute(params._splat ?? '')\n const search = studioSearch(rawSearch)\n\n return (\n <StudioShell project={project}>\n {/* EC-177 gap 1: in Convex mode the session HYDRATES from persistence\n before any provider mounts — the builder boots from the latest\n persisted revision, publishing reflects the persisted state, and the\n preview content engine serves live documents. Playground renders\n synchronously from fixtures (session: null), exactly as before. */}\n <ProjectSessionProvider key={project.id} projectId={project.id}>\n <ProjectProviders project={project}>\n <StudioSectionResolver project={project} match={match} search={search} />\n </ProjectProviders>\n </ProjectSessionProvider>\n </StudioShell>\n )\n}\n\n/**\n * The per-project provider stack, mounted once the session source is known.\n *\n * EC-287: there is no project-level builder graph anymore — layouts are code and\n * the DB holds content only. The block-field canvas mounts its OWN scoped\n * `BuilderProvider` per document (`composition-field-editor`), so no graph is\n * threaded here. `CollabProvider` stays as the canvas's session-scoped\n * presence/perspective store, seeded graph-lessly.\n */\nfunction ProjectProviders({ project, children }: { project: Project; children: ReactNode }) {\n return (\n // Key by project id so the session data sources reset when switching projects.\n // The data store hydrates/persists this project's `cms-data-sources` records.\n <DataProvider key={project.id} projectId={project.id}>\n {/* EC-195: project-level so the canvas resolves `asset`-typed Image props\n and the inspector picks from the live library — not just the Assets\n section. Asset METADATA only (small); blobs stay lazy. */}\n <AssetsProvider key={project.id} projectId={project.id}>\n <CollabProvider key={project.id} projectId={project.id}>\n {children}\n </CollabProvider>\n </AssetsProvider>\n </DataProvider>\n )\n}\n\n/**\n * The URL→screen switch (EC-223 Stage 6), driven by the view registry DERIVED from\n * the section rail (EC-237/239). The bare project URL redirects to the first\n * navigable rail view; otherwise the matched view id is resolved from the registry\n * (rail views + always-on built-ins) and its `parsePath` interprets the trailing\n * segments into a deep-link the view renders. An unknown view id or an unparseable\n * path is a not-found; rail CONFIG problems (duplicate/invalid views) surface as\n * visible issues in the shell, never silently. Renders inside the shell + provider\n * stack, so every surface sees the same live CMS store and breadcrumb context.\n */\nfunction StudioSectionResolver({\n project,\n match,\n search,\n}: {\n project: Project\n match: StudioRouteMatch\n search: StudioSearch\n}) {\n const projectId = project.id\n const nav = useStudioNav(project)\n\n if (match.section === 'index') {\n // Bare `/$projectId` → the project's first navigable rail view.\n return (\n <Navigate\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: landingViewId(nav, project) }}\n replace\n />\n )\n }\n\n const view = nav.registry.get(match.viewId)\n if (!view) return <StudioRouteNotFound />\n\n // A flat view (no `parsePath`) accepts only the bare path; a nested one\n // (CMS/components) interprets its segments. `null` → an over-deep path → not-found.\n const deepLink = (view.parsePath ?? flatPath)(match.segments)\n if (deepLink === null) return <StudioRouteNotFound />\n\n return <>{view.render({ project, projectId, deepLink, search })}</>\n}\n\nfunction ProjectNotFound() {\n return (\n <div className=\"grid min-h-dvh place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading size=\"display\">{translate('shell.projectNotFound.title')}</Heading>\n <Text tone=\"muted\">{translate('shell.projectNotFound.body')}</Text>\n <Button asChild>\n <Link to=\"/\">{translate('shell.backToProjects')}</Link>\n </Button>\n </Stack>\n </div>\n )\n}\n\nfunction StudioRouteNotFound() {\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"2\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading level={2} size=\"h2\">\n {translate('shell.routeNotFound.title')}\n </Heading>\n <Text tone=\"muted\">{translate('shell.routeNotFound.body')}</Text>\n </Stack>\n </div>\n )\n}\n"]}
|
|
@@ -4,6 +4,7 @@ import { ChevronRight } from 'lucide-react';
|
|
|
4
4
|
import { useCallback, useEffect, useState } from 'react';
|
|
5
5
|
import { Row, Spacer, Toaster } from '@elytracms/studio/ui';
|
|
6
6
|
import { readRailCollapsed, storeRailCollapsed } from '@elytracms/studio/lib/rail';
|
|
7
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
7
8
|
import { CurrentUserMenu } from './current-user-menu';
|
|
8
9
|
import { DeliveryHealthBanner } from './delivery-health-banner';
|
|
9
10
|
import { PlaygroundBadge } from './playground-badge';
|
|
@@ -35,7 +36,7 @@ export function StudioShell({ project, children }) {
|
|
|
35
36
|
return next;
|
|
36
37
|
});
|
|
37
38
|
}, []);
|
|
38
|
-
return (_jsxs(BreadcrumbProvider, { children: [_jsxs("div", { className: "flex h-dvh flex-col bg-background", children: [_jsxs("header", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border px-3", children: [_jsx(Link, { to: "/", className: "grid size-6 shrink-0 place-items-center", "aria-label":
|
|
39
|
+
return (_jsxs(BreadcrumbProvider, { children: [_jsxs("div", { className: "flex h-dvh flex-col bg-background", children: [_jsxs("header", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border px-3", children: [_jsx(Link, { to: "/", className: "grid size-6 shrink-0 place-items-center", "aria-label": translate('shell.allProjects'), children: _jsx("span", { className: "size-5 rounded-[5px] bg-primary shadow-sm" }) }), _jsx(ProjectSwitcher, { activeProject: project }), _jsx(HeaderBreadcrumb, { project: project, activeViewId: activeViewId }), _jsx(Spacer, {}), _jsx(PlaygroundBadge, {}), _jsx(DevLoginBadge, {}), _jsx(CurrentUserMenu, { projectId: project.id })] }), _jsx(DeliveryHealthBanner, {}), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsx(SectionNav, { project: project, collapsed: railCollapsed, onToggleCollapsed: toggleRail }), _jsx("main", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: children }) })] })] }), _jsx(Toaster, {})] }));
|
|
39
40
|
}
|
|
40
41
|
const CRUMB_LINK = 'max-w-[12rem] truncate text-muted-foreground transition-colors hover:text-foreground';
|
|
41
42
|
const CRUMB_CURRENT = 'max-w-[16rem] truncate font-medium text-foreground';
|
|
@@ -54,7 +55,7 @@ function HeaderBreadcrumb({ project, activeViewId, }) {
|
|
|
54
55
|
if (!view)
|
|
55
56
|
return null;
|
|
56
57
|
const sectionIsLast = extra.length === 0;
|
|
57
|
-
return (_jsxs(Row, { gap: "1", className: "min-w-0 text-sm", "aria-label":
|
|
58
|
+
return (_jsxs(Row, { gap: "1", className: "min-w-0 text-sm", "aria-label": translate('shell.breadcrumb.label'), children: [_jsx(ChevronRight, { className: "size-3.5 shrink-0 text-muted-foreground/50", "aria-hidden": true }), sectionIsLast ? (_jsx("span", { className: CRUMB_CURRENT, children: view.label })) : (_jsx(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: view.id }, className: CRUMB_LINK, children: view.label })), extra.map((item, index) => {
|
|
58
59
|
const isLast = index === extra.length - 1;
|
|
59
60
|
return (_jsxs(Row, { gap: "1", className: "min-w-0", children: [_jsx(ChevronRight, { className: "size-3.5 shrink-0 text-muted-foreground/50", "aria-hidden": true }), !isLast && item.to ? (
|
|
60
61
|
// `to` and `params` are always contributed as a unit (the type
|