@morphika/andami 0.1.2
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/LICENSE +21 -0
- package/README.md +50 -0
- package/admin/assets.ts +4 -0
- package/admin/database.ts +4 -0
- package/admin/index.ts +6 -0
- package/admin/login.ts +4 -0
- package/admin/navigation.ts +4 -0
- package/admin/pages-editor.ts +4 -0
- package/admin/pages.ts +4 -0
- package/admin/projects-editor.ts +4 -0
- package/admin/projects.ts +4 -0
- package/admin/settings.ts +4 -0
- package/admin/setup.ts +4 -0
- package/admin/storage.ts +4 -0
- package/admin/styles.ts +4 -0
- package/app/(site)/[slug]/loading.tsx +20 -0
- package/app/(site)/[slug]/page.tsx +83 -0
- package/app/(site)/error.tsx +32 -0
- package/app/(site)/layout.tsx +53 -0
- package/app/(site)/loading.tsx +20 -0
- package/app/(site)/not-found.tsx +41 -0
- package/app/(site)/page.tsx +43 -0
- package/app/(site)/preview/page.tsx +99 -0
- package/app/(site)/work/[slug]/loading.tsx +23 -0
- package/app/(site)/work/[slug]/page.tsx +84 -0
- package/app/admin/assets/page.tsx +573 -0
- package/app/admin/database/page.tsx +302 -0
- package/app/admin/error.tsx +53 -0
- package/app/admin/layout.tsx +273 -0
- package/app/admin/login/page.tsx +88 -0
- package/app/admin/navigation/page.tsx +157 -0
- package/app/admin/page.tsx +17 -0
- package/app/admin/pages/[slug]/page.tsx +849 -0
- package/app/admin/pages/page.tsx +588 -0
- package/app/admin/projects/[slug]/page.tsx +3 -0
- package/app/admin/projects/page.tsx +669 -0
- package/app/admin/settings/page.tsx +132 -0
- package/app/admin/setup/page.tsx +64 -0
- package/app/admin/storage/page.tsx +518 -0
- package/app/admin/styles/page.tsx +243 -0
- package/app/api/admin/assets/file/route.ts +81 -0
- package/app/api/admin/assets/health/route.ts +170 -0
- package/app/api/admin/assets/register/route.ts +163 -0
- package/app/api/admin/assets/registry/route.ts +98 -0
- package/app/api/admin/assets/relink/confirm/route.ts +242 -0
- package/app/api/admin/assets/relink/route.ts +202 -0
- package/app/api/admin/assets/scan/route.ts +271 -0
- package/app/api/admin/auth/route.ts +160 -0
- package/app/api/admin/custom-sections/[slug]/route.ts +159 -0
- package/app/api/admin/custom-sections/route.ts +127 -0
- package/app/api/admin/database/route.ts +53 -0
- package/app/api/admin/pages/[slug]/duplicate/route.ts +91 -0
- package/app/api/admin/pages/[slug]/route.ts +617 -0
- package/app/api/admin/pages/[slug]/set-home/route.ts +76 -0
- package/app/api/admin/pages/route.ts +129 -0
- package/app/api/admin/preview/route.ts +53 -0
- package/app/api/admin/r2/connect/route.ts +181 -0
- package/app/api/admin/r2/delete/route.ts +198 -0
- package/app/api/admin/r2/disconnect/route.ts +42 -0
- package/app/api/admin/r2/rename/route.ts +265 -0
- package/app/api/admin/r2/status/route.ts +106 -0
- package/app/api/admin/r2/upload-url/route.ts +148 -0
- package/app/api/admin/revalidate/route.ts +55 -0
- package/app/api/admin/settings/route.ts +279 -0
- package/app/api/admin/setup/complete/route.ts +51 -0
- package/app/api/admin/setup/route.ts +118 -0
- package/app/api/admin/storage/switch/route.ts +117 -0
- package/app/api/admin/styles/fonts/route.ts +97 -0
- package/app/api/admin/styles/route.ts +304 -0
- package/app/api/assets/[...path]/route.ts +98 -0
- package/app/api/custom-sections/[id]/route.ts +43 -0
- package/app/api/draft-mode/disable/route.ts +10 -0
- package/app/api/draft-mode/enable/route.ts +26 -0
- package/app/api/projects/route.ts +42 -0
- package/app/api/styles/route.ts +88 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +7 -0
- package/app/layout.tsx +53 -0
- package/app/robots.ts +17 -0
- package/app/sitemap.ts +48 -0
- package/app/studio/[[...index]]/page.tsx +8 -0
- package/components/admin/MetadataEditor.tsx +173 -0
- package/components/admin/PublishToggle.tsx +130 -0
- package/components/admin/icons.tsx +40 -0
- package/components/admin/nav-builder/NavBuilder.tsx +182 -0
- package/components/admin/nav-builder/NavBuilderGrid.tsx +326 -0
- package/components/admin/nav-builder/NavGeneralSettings.tsx +275 -0
- package/components/admin/nav-builder/NavGridCell.tsx +48 -0
- package/components/admin/nav-builder/NavGridItem.tsx +189 -0
- package/components/admin/nav-builder/NavItemSettings.tsx +288 -0
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -0
- package/components/admin/nav-builder/NavLivePreview.tsx +125 -0
- package/components/admin/nav-builder/NavSettingsFields.tsx +248 -0
- package/components/admin/nav-builder/NavSettingsPanel.tsx +127 -0
- package/components/admin/nav-builder/index.ts +10 -0
- package/components/admin/nav-builder/nav-builder-utils.ts +238 -0
- package/components/admin/setup-wizard/BrandingStep.tsx +218 -0
- package/components/admin/setup-wizard/DatabaseStep.tsx +331 -0
- package/components/admin/setup-wizard/DoneStep.tsx +187 -0
- package/components/admin/setup-wizard/SetupWizard.tsx +166 -0
- package/components/admin/setup-wizard/StorageStep.tsx +308 -0
- package/components/admin/setup-wizard/WelcomeStep.tsx +96 -0
- package/components/admin/setup-wizard/index.ts +9 -0
- package/components/admin/styles/ColorsEditor.tsx +214 -0
- package/components/admin/styles/FontsEditor.tsx +258 -0
- package/components/admin/styles/GridLayoutEditor.tsx +292 -0
- package/components/admin/styles/LinksButtonsEditor.tsx +120 -0
- package/components/admin/styles/TypographyEditor.tsx +266 -0
- package/components/admin/styles/index.ts +9 -0
- package/components/admin/styles/shared.tsx +68 -0
- package/components/blocks/BlockRenderer.tsx +404 -0
- package/components/blocks/ButtonBlockRenderer.tsx +52 -0
- package/components/blocks/CoverBlockRenderer.tsx +239 -0
- package/components/blocks/CustomSectionInstanceRenderer.tsx +82 -0
- package/components/blocks/EnterAnimationWrapper.tsx +140 -0
- package/components/blocks/HoverAnimationWrapper.tsx +308 -0
- package/components/blocks/ImageBlockRenderer.tsx +61 -0
- package/components/blocks/ImageGridBlockRenderer.tsx +545 -0
- package/components/blocks/PageBackground.tsx +28 -0
- package/components/blocks/PageNavAnimation.tsx +35 -0
- package/components/blocks/PageNavColor.tsx +24 -0
- package/components/blocks/PageRenderer.tsx +142 -0
- package/components/blocks/ParallaxGroupRenderer.tsx +448 -0
- package/components/blocks/ParallaxSlideRenderer.tsx +175 -0
- package/components/blocks/ProjectGridBlockRenderer.tsx +556 -0
- package/components/blocks/SectionRenderer.tsx +170 -0
- package/components/blocks/SectionV2Renderer.tsx +330 -0
- package/components/blocks/ShaderCanvas.tsx +392 -0
- package/components/blocks/SpacerBlockRenderer.tsx +17 -0
- package/components/blocks/TextBlockRenderer.tsx +87 -0
- package/components/blocks/TypewriterRichText.tsx +464 -0
- package/components/blocks/TypewriterWrapper.tsx +149 -0
- package/components/blocks/VideoBlockRenderer.tsx +304 -0
- package/components/blocks/index.ts +2 -0
- package/components/builder/AssetBrowser.tsx +2 -0
- package/components/builder/BlockLivePreview.tsx +101 -0
- package/components/builder/BlockTypePicker.tsx +178 -0
- package/components/builder/BuilderCanvas.tsx +354 -0
- package/components/builder/CanvasMinimap.tsx +200 -0
- package/components/builder/CanvasToolbar.tsx +202 -0
- package/components/builder/ColorPicker.tsx +243 -0
- package/components/builder/ColorSwatchPicker.tsx +274 -0
- package/components/builder/ColumnDragContext.tsx +51 -0
- package/components/builder/ColumnDragOverlay.tsx +110 -0
- package/components/builder/CustomSectionInstanceCard.tsx +97 -0
- package/components/builder/DeviceFrame.tsx +123 -0
- package/components/builder/DndWrapper.tsx +337 -0
- package/components/builder/InsertionLines.tsx +186 -0
- package/components/builder/ParallaxGroupCanvas.tsx +228 -0
- package/components/builder/ParallaxSlideHeader.tsx +113 -0
- package/components/builder/ReadOnlyFrame.tsx +417 -0
- package/components/builder/SectionEditorBar.tsx +288 -0
- package/components/builder/SectionTypePicker.tsx +422 -0
- package/components/builder/SectionV2Canvas.tsx +297 -0
- package/components/builder/SectionV2Column.tsx +488 -0
- package/components/builder/SettingsPanel.tsx +911 -0
- package/components/builder/SortableBlock.tsx +230 -0
- package/components/builder/SortableRow.tsx +362 -0
- package/components/builder/VirtualAssetGrid.tsx +397 -0
- package/components/builder/asset-browser/AssetBrowser.tsx +178 -0
- package/components/builder/asset-browser/FileLightbox.tsx +116 -0
- package/components/builder/asset-browser/FolderTreeItem.tsx +55 -0
- package/components/builder/asset-browser/R2BrowserContent.tsx +436 -0
- package/components/builder/asset-browser/R2ContextMenu.tsx +98 -0
- package/components/builder/asset-browser/VideoThumbnail.tsx +63 -0
- package/components/builder/asset-browser/helpers.ts +88 -0
- package/components/builder/asset-browser/index.ts +1 -0
- package/components/builder/asset-browser/types.ts +49 -0
- package/components/builder/asset-browser/useAssetBrowser.ts +344 -0
- package/components/builder/asset-browser/useR2DragDrop.ts +116 -0
- package/components/builder/asset-browser/useR2Operations.ts +189 -0
- package/components/builder/blockStyles.tsx +295 -0
- package/components/builder/editors/ButtonBlockEditor.tsx +184 -0
- package/components/builder/editors/CoverBlockEditor.tsx +488 -0
- package/components/builder/editors/EnterAnimationPicker.tsx +297 -0
- package/components/builder/editors/HoverEffectPicker.tsx +209 -0
- package/components/builder/editors/ImageBlockEditor.tsx +206 -0
- package/components/builder/editors/ImageGridBlockEditor.tsx +386 -0
- package/components/builder/editors/ProjectGridEditor.tsx +648 -0
- package/components/builder/editors/SpacerBlockEditor.tsx +167 -0
- package/components/builder/editors/StaggerSettings.tsx +108 -0
- package/components/builder/editors/TextAlignmentIcons.tsx +39 -0
- package/components/builder/editors/TextBlockEditor.tsx +462 -0
- package/components/builder/editors/TextStylePicker.tsx +183 -0
- package/components/builder/editors/VideoBlockEditor.tsx +278 -0
- package/components/builder/editors/index.ts +10 -0
- package/components/builder/editors/shared.tsx +345 -0
- package/components/builder/hooks/useColumnDrag.ts +472 -0
- package/components/builder/hooks/useColumnResize.ts +221 -0
- package/components/builder/index.ts +12 -0
- package/components/builder/live-preview/LiveButtonPreview.tsx +38 -0
- package/components/builder/live-preview/LiveCoverPreview.tsx +146 -0
- package/components/builder/live-preview/LiveImageGridPreview.tsx +123 -0
- package/components/builder/live-preview/LiveImagePreview.tsx +107 -0
- package/components/builder/live-preview/LiveProjectGridPreview.tsx +1010 -0
- package/components/builder/live-preview/LiveSpacerPreview.tsx +9 -0
- package/components/builder/live-preview/LiveTextEditor.tsx +198 -0
- package/components/builder/live-preview/LiveVideoPreview.tsx +98 -0
- package/components/builder/live-preview/index.ts +10 -0
- package/components/builder/live-preview/shared.tsx +153 -0
- package/components/builder/settings-panel/BlockLayoutTab.tsx +532 -0
- package/components/builder/settings-panel/BlockSettings.tsx +94 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +160 -0
- package/components/builder/settings-panel/LayoutTab.tsx +310 -0
- package/components/builder/settings-panel/PageSettings.tsx +200 -0
- package/components/builder/settings-panel/ParallaxGroupSettings.tsx +118 -0
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +178 -0
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +103 -0
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +312 -0
- package/components/builder/settings-panel/SectionV2Settings.tsx +323 -0
- package/components/builder/settings-panel/TRBLInputs.tsx +51 -0
- package/components/builder/settings-panel/index.ts +19 -0
- package/components/builder/settings-panel/responsive-helpers.ts +524 -0
- package/components/ui/CustomCursor.tsx +118 -0
- package/components/ui/NavContentLightbox.tsx +152 -0
- package/components/ui/Navbar.tsx +582 -0
- package/components/ui/PortfolioTracker.tsx +87 -0
- package/components/ui/ScrollToTop.tsx +47 -0
- package/lib/animation/enter-presets.ts +147 -0
- package/lib/animation/enter-resolve.ts +90 -0
- package/lib/animation/enter-types.ts +128 -0
- package/lib/animation/hover-effect-presets.ts +210 -0
- package/lib/animation/hover-effect-types.ts +126 -0
- package/lib/asset-retry.ts +111 -0
- package/lib/assets.ts +92 -0
- package/lib/audit.ts +35 -0
- package/lib/auth-token.ts +94 -0
- package/lib/auth.ts +13 -0
- package/lib/builder/cascade-helpers.ts +51 -0
- package/lib/builder/cascade.ts +533 -0
- package/lib/builder/constants.ts +103 -0
- package/lib/builder/defaults.ts +182 -0
- package/lib/builder/history.ts +48 -0
- package/lib/builder/index.ts +21 -0
- package/lib/builder/layout-styles.ts +344 -0
- package/lib/builder/masonry.ts +166 -0
- package/lib/builder/responsive.ts +156 -0
- package/lib/builder/serializer.ts +845 -0
- package/lib/builder/store-blocks.ts +193 -0
- package/lib/builder/store-canvas.ts +319 -0
- package/lib/builder/store-helpers.ts +490 -0
- package/lib/builder/store-sections.ts +709 -0
- package/lib/builder/store.ts +333 -0
- package/lib/builder/templates.ts +297 -0
- package/lib/builder/types.ts +374 -0
- package/lib/builder/utils.ts +37 -0
- package/lib/color-utils.ts +116 -0
- package/lib/config/index.ts +57 -0
- package/lib/config/types.ts +122 -0
- package/lib/contexts/AssetContext.tsx +79 -0
- package/lib/contexts/NavAnimationContext.tsx +44 -0
- package/lib/contexts/NavColorContext.tsx +38 -0
- package/lib/contexts/PageExitContext.tsx +194 -0
- package/lib/contexts/ThumbStatusContext.tsx +83 -0
- package/lib/csrf-client.ts +34 -0
- package/lib/csrf.ts +68 -0
- package/lib/format-utils.ts +24 -0
- package/lib/hooks/useViewport.ts +42 -0
- package/lib/logger.ts +81 -0
- package/lib/revalidate.ts +23 -0
- package/lib/sanitize.ts +91 -0
- package/lib/sanity/client.ts +8 -0
- package/lib/sanity/queries.ts +486 -0
- package/lib/sanity/types.ts +869 -0
- package/lib/sanity/writeClient.ts +24 -0
- package/lib/security.ts +402 -0
- package/lib/setup/detect.ts +156 -0
- package/lib/shader/glsl/index.ts +27 -0
- package/lib/shader/glsl/pixelate.ts +51 -0
- package/lib/shader/glsl/rgb-shift.ts +45 -0
- package/lib/shader/glsl/ripple.ts +46 -0
- package/lib/shader/glsl/vertex.ts +14 -0
- package/lib/storage/index.ts +211 -0
- package/lib/storage/r2-adapter.ts +286 -0
- package/lib/storage/types.ts +125 -0
- package/lib/styles/provider.tsx +267 -0
- package/lib/thumbnails/generate.ts +151 -0
- package/lib/utils.ts +6 -0
- package/package.json +212 -0
- package/sanity/compose.ts +65 -0
- package/sanity/sanity.config.ts +126 -0
- package/sanity/schemas/assetRegistry.ts +301 -0
- package/sanity/schemas/blocks/blockLayout.ts +90 -0
- package/sanity/schemas/blocks/buttonBlock.ts +82 -0
- package/sanity/schemas/blocks/coverBlock.ts +229 -0
- package/sanity/schemas/blocks/imageBlock.ts +58 -0
- package/sanity/schemas/blocks/imageGridBlock.ts +112 -0
- package/sanity/schemas/blocks/index.ts +9 -0
- package/sanity/schemas/blocks/projectGridBlock.ts +251 -0
- package/sanity/schemas/blocks/spacerBlock.ts +41 -0
- package/sanity/schemas/blocks/textBlock.ts +139 -0
- package/sanity/schemas/blocks/videoBlock.ts +80 -0
- package/sanity/schemas/customSection.ts +69 -0
- package/sanity/schemas/customSectionInstance.ts +163 -0
- package/sanity/schemas/index.ts +111 -0
- package/sanity/schemas/objects/enterAnimationConfig.ts +72 -0
- package/sanity/schemas/objects/hoverEffectConfig.ts +90 -0
- package/sanity/schemas/objects/parallaxGroup.ts +66 -0
- package/sanity/schemas/objects/parallaxSlide.ts +217 -0
- package/sanity/schemas/objects/typewriterConfig.ts +38 -0
- package/sanity/schemas/page.ts +162 -0
- package/sanity/schemas/pageSection.ts +157 -0
- package/sanity/schemas/pageSectionV2.ts +269 -0
- package/sanity/schemas/siteSettings.ts +256 -0
- package/sanity/schemas/siteStyles.ts +212 -0
- package/site/error.ts +4 -0
- package/site/index.ts +8 -0
- package/site/not-found.ts +4 -0
- package/site/page.ts +4 -0
- package/site/preview.ts +4 -0
- package/site/robots.ts +4 -0
- package/site/sitemap.ts +4 -0
- package/site/work.ts +4 -0
- package/studio/index.ts +4 -0
- package/styles/admin.css +85 -0
- package/styles/animations.css +237 -0
- package/styles/base.css +148 -0
- package/styles/globals.css +10 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ColumnV2Settings — Settings panel for a selected column within a V2 section.
|
|
5
|
+
*
|
|
6
|
+
* Settings tab only — no Layout or Animation sub-tabs.
|
|
7
|
+
* - Size: numeric input (1 to grid_columns), triggers cascade on change
|
|
8
|
+
* - Desktop: modifies base column data via cascade engine
|
|
9
|
+
* - Tablet/Phone: writes to responsive column overrides
|
|
10
|
+
* - Block count info
|
|
11
|
+
* - Delete column button
|
|
12
|
+
*
|
|
13
|
+
* Session 83: Phase 4 of V2 Grid System.
|
|
14
|
+
* Session 84: Phase 7 — viewport-aware column editing.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
18
|
+
import type { PageSectionV2, SectionColumn } from "../../../lib/sanity/types";
|
|
19
|
+
import {
|
|
20
|
+
SettingsField,
|
|
21
|
+
SettingsSection,
|
|
22
|
+
} from "../editors/shared";
|
|
23
|
+
import {
|
|
24
|
+
getColumnV2Override,
|
|
25
|
+
buildSingleColumnV2Override,
|
|
26
|
+
hasAnyColumnV2Overrides,
|
|
27
|
+
} from "./responsive-helpers";
|
|
28
|
+
|
|
29
|
+
export default function ColumnV2Settings({
|
|
30
|
+
section,
|
|
31
|
+
column,
|
|
32
|
+
}: {
|
|
33
|
+
section: PageSectionV2;
|
|
34
|
+
column: SectionColumn;
|
|
35
|
+
}) {
|
|
36
|
+
const store = useBuilderStore();
|
|
37
|
+
const activeViewport = store.activeViewport;
|
|
38
|
+
const isResponsive = activeViewport !== "desktop";
|
|
39
|
+
const gridColumns = section.settings.grid_columns;
|
|
40
|
+
|
|
41
|
+
// Effective values: responsive override or base
|
|
42
|
+
const effectiveSpan = (isResponsive
|
|
43
|
+
? getColumnV2Override(section, column._key, activeViewport, "span")
|
|
44
|
+
: undefined) ?? column.span;
|
|
45
|
+
|
|
46
|
+
const effectiveGridColumn = (isResponsive
|
|
47
|
+
? getColumnV2Override(section, column._key, activeViewport, "grid_column")
|
|
48
|
+
: undefined) ?? column.grid_column;
|
|
49
|
+
|
|
50
|
+
const effectiveGridRow = (isResponsive
|
|
51
|
+
? getColumnV2Override(section, column._key, activeViewport, "grid_row")
|
|
52
|
+
: undefined) ?? column.grid_row;
|
|
53
|
+
|
|
54
|
+
const hasSpanOverride = isResponsive &&
|
|
55
|
+
getColumnV2Override(section, column._key, activeViewport, "span") !== undefined;
|
|
56
|
+
|
|
57
|
+
const handleSpanChange = (newSpan: number) => {
|
|
58
|
+
const clamped = Math.max(1, Math.min(gridColumns, newSpan));
|
|
59
|
+
if (clamped === effectiveSpan) return;
|
|
60
|
+
|
|
61
|
+
if (isResponsive) {
|
|
62
|
+
// Write span override to responsive
|
|
63
|
+
const responsive = buildSingleColumnV2Override(section, activeViewport, column._key, {
|
|
64
|
+
span: clamped,
|
|
65
|
+
});
|
|
66
|
+
store.updateSectionV2Responsive(section._key, responsive ?? undefined);
|
|
67
|
+
} else {
|
|
68
|
+
// Desktop: use cascade engine
|
|
69
|
+
store.resizeColumnV2(section._key, column._key, clamped);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleResetSpan = () => {
|
|
74
|
+
// Remove span override for this column
|
|
75
|
+
const responsive = buildSingleColumnV2Override(section, activeViewport, column._key, {
|
|
76
|
+
span: undefined,
|
|
77
|
+
});
|
|
78
|
+
store.updateSectionV2Responsive(section._key, responsive ?? undefined);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<>
|
|
83
|
+
{isResponsive && (
|
|
84
|
+
<div className="px-4 pt-3">
|
|
85
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#076bff]/8 border border-[#076bff]/15">
|
|
86
|
+
<span className="text-[11px] font-medium text-[#076bff]">
|
|
87
|
+
Editing {activeViewport === "tablet" ? "Tablet" : "Phone"} overrides
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
<SettingsSection title="Column Size" defaultOpen>
|
|
94
|
+
<SettingsField label={
|
|
95
|
+
<span>
|
|
96
|
+
Span
|
|
97
|
+
{hasSpanOverride && (
|
|
98
|
+
<span className="ml-1 text-[9px] text-[#076bff]">overridden</span>
|
|
99
|
+
)}
|
|
100
|
+
</span>
|
|
101
|
+
}>
|
|
102
|
+
<div className="flex items-center gap-2">
|
|
103
|
+
<input
|
|
104
|
+
type="range"
|
|
105
|
+
min={1}
|
|
106
|
+
max={gridColumns}
|
|
107
|
+
value={effectiveSpan}
|
|
108
|
+
onChange={(e) => handleSpanChange(parseInt(e.target.value))}
|
|
109
|
+
className="flex-1 accent-[#076bff]"
|
|
110
|
+
/>
|
|
111
|
+
<span className="text-xs text-neutral-900 w-12 text-right font-medium">
|
|
112
|
+
{effectiveSpan}/{gridColumns}
|
|
113
|
+
</span>
|
|
114
|
+
{hasSpanOverride && (
|
|
115
|
+
<button
|
|
116
|
+
onClick={handleResetSpan}
|
|
117
|
+
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
118
|
+
>
|
|
119
|
+
Reset
|
|
120
|
+
</button>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
{/* Visual grid indicator */}
|
|
124
|
+
<div className="flex gap-0.5 mt-1.5">
|
|
125
|
+
{Array.from({ length: gridColumns }).map((_, i) => {
|
|
126
|
+
const colStart = effectiveGridColumn - 1; // 0-based for display
|
|
127
|
+
const colEnd = colStart + effectiveSpan;
|
|
128
|
+
const isActive = i >= colStart && i < colEnd;
|
|
129
|
+
return (
|
|
130
|
+
<div
|
|
131
|
+
key={i}
|
|
132
|
+
className={`h-1.5 flex-1 rounded-full transition-colors ${
|
|
133
|
+
isActive ? "bg-[#076bff]" : "bg-neutral-200"
|
|
134
|
+
}`}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</div>
|
|
139
|
+
</SettingsField>
|
|
140
|
+
|
|
141
|
+
<SettingsField label="Position">
|
|
142
|
+
<p className="text-xs text-neutral-900 py-[7px]">
|
|
143
|
+
Column {effectiveGridColumn}, Row {effectiveGridRow}
|
|
144
|
+
{isResponsive && (
|
|
145
|
+
<span className="text-neutral-400"> (base: {column.grid_column}, {column.grid_row})</span>
|
|
146
|
+
)}
|
|
147
|
+
</p>
|
|
148
|
+
</SettingsField>
|
|
149
|
+
|
|
150
|
+
<SettingsField label="Blocks">
|
|
151
|
+
<p className="text-xs text-neutral-900 py-[7px]">
|
|
152
|
+
{(column.blocks || []).length} block
|
|
153
|
+
{(column.blocks || []).length !== 1 ? "s" : ""}
|
|
154
|
+
</p>
|
|
155
|
+
</SettingsField>
|
|
156
|
+
</SettingsSection>
|
|
157
|
+
|
|
158
|
+
</>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LayoutTab — Page Section styling (spacing, background, offset, border).
|
|
5
|
+
* Viewport-aware with responsive override support.
|
|
6
|
+
*
|
|
7
|
+
* Session 64: Extracted from SettingsPanel.tsx.
|
|
8
|
+
* Session 65: Split out RowLayoutPresetPicker, TRBLInputs, BlockLayoutTab
|
|
9
|
+
* into separate modules. This file now contains only LayoutTab.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
13
|
+
import { resolveEffectiveSpacing } from "../../../lib/builder/layout-styles";
|
|
14
|
+
import type { PageSection } from "../../../lib/sanity/types";
|
|
15
|
+
import {
|
|
16
|
+
SettingsField,
|
|
17
|
+
SettingsSection,
|
|
18
|
+
SELECT_CLASS,
|
|
19
|
+
AssetPathInput,
|
|
20
|
+
} from "../editors/shared";
|
|
21
|
+
import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
|
|
22
|
+
import {
|
|
23
|
+
getRowSettingValue,
|
|
24
|
+
hasRowSettingOverride,
|
|
25
|
+
setRowResponsiveOverride,
|
|
26
|
+
} from "./responsive-helpers";
|
|
27
|
+
import { TRBLInputs } from "./TRBLInputs";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* BUG-007 fix: LayoutTab now handles PageSection styling (spacing, background, border).
|
|
31
|
+
* BUG-013 fix: Sections support responsive overrides (tablet/phone).
|
|
32
|
+
*/
|
|
33
|
+
export function LayoutTab({ section, sectionKey }: { section: PageSection; sectionKey: string }) {
|
|
34
|
+
const store = useBuilderStore();
|
|
35
|
+
const paletteSwatches = usePaletteSwatches();
|
|
36
|
+
const settings = section.settings || {};
|
|
37
|
+
const activeViewport = store.activeViewport;
|
|
38
|
+
|
|
39
|
+
const updateSetting = (updates: Partial<NonNullable<PageSection["settings"]>>) => {
|
|
40
|
+
store.updateSectionSettings(sectionKey, updates);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Update a setting, viewport-aware. Supports sections with responsive overrides. */
|
|
44
|
+
const updateSettingResponsive = (property: string, value: unknown) => {
|
|
45
|
+
// BUG-013 fix: Sections now support responsive overrides
|
|
46
|
+
if (activeViewport === "desktop") {
|
|
47
|
+
store.updateSectionSettings(sectionKey, { [property]: value });
|
|
48
|
+
} else {
|
|
49
|
+
// Build responsive override for section
|
|
50
|
+
const existing = section.responsive || {};
|
|
51
|
+
const vp = activeViewport as "tablet" | "phone";
|
|
52
|
+
const vpOverrides = { ...(existing[vp] || {}), [property]: value };
|
|
53
|
+
if (value === undefined) delete (vpOverrides as Record<string, unknown>)[property];
|
|
54
|
+
const responsive = { ...existing, [vp]: vpOverrides };
|
|
55
|
+
if (Object.keys(vpOverrides).length === 0) delete responsive[vp];
|
|
56
|
+
store.updateSectionResponsive(sectionKey, Object.keys(responsive).length ? responsive : undefined);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Resolve effective spacing — shows real values even when legacy enum is active
|
|
61
|
+
const effective = resolveEffectiveSpacing(settings);
|
|
62
|
+
|
|
63
|
+
// Viewport-aware spacing values
|
|
64
|
+
const effectiveSpacingTop = getRowSettingValue<string>(section, activeViewport, "spacing_top", effective.top);
|
|
65
|
+
const effectiveSpacingRight = getRowSettingValue<string>(section, activeViewport, "spacing_right", effective.right);
|
|
66
|
+
const effectiveSpacingBottom = getRowSettingValue<string>(section, activeViewport, "spacing_bottom", effective.bottom);
|
|
67
|
+
const effectiveSpacingLeft = getRowSettingValue<string>(section, activeViewport, "spacing_left", effective.left);
|
|
68
|
+
|
|
69
|
+
// Parse background color + opacity to display
|
|
70
|
+
const bgOpacity = getRowSettingValue<number>(section, activeViewport, "background_opacity", settings.background_opacity ?? 100);
|
|
71
|
+
|
|
72
|
+
const viewportLabel = activeViewport !== "desktop"
|
|
73
|
+
? activeViewport === "tablet" ? "Tablet" : "Phone"
|
|
74
|
+
: null;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
{viewportLabel && (
|
|
79
|
+
<div className="px-4 pt-3">
|
|
80
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#076bff]/8 border border-[#076bff]/15">
|
|
81
|
+
<span className="text-[11px] font-medium text-[#076bff]">
|
|
82
|
+
Editing {viewportLabel} overrides
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{/* Spacing (Padding) */}
|
|
89
|
+
<SettingsSection title="Spacing" defaultOpen>
|
|
90
|
+
<TRBLInputs
|
|
91
|
+
top={effectiveSpacingTop}
|
|
92
|
+
right={effectiveSpacingRight}
|
|
93
|
+
bottom={effectiveSpacingBottom}
|
|
94
|
+
left={effectiveSpacingLeft}
|
|
95
|
+
onChange={(field, value) => {
|
|
96
|
+
if (activeViewport === "desktop") {
|
|
97
|
+
// When user edits TRBL, set explicit TRBL values
|
|
98
|
+
const base = resolveEffectiveSpacing(settings);
|
|
99
|
+
updateSetting({
|
|
100
|
+
spacing_top: field === "top" ? value : (settings.spacing_top ?? base.top),
|
|
101
|
+
spacing_right: field === "right" ? value : (settings.spacing_right ?? base.right),
|
|
102
|
+
spacing_bottom: field === "bottom" ? value : (settings.spacing_bottom ?? base.bottom),
|
|
103
|
+
spacing_left: field === "left" ? value : (settings.spacing_left ?? base.left),
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
updateSettingResponsive(`spacing_${field}`, value);
|
|
107
|
+
}
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
{activeViewport !== "desktop" && (
|
|
111
|
+
["spacing_top", "spacing_right", "spacing_bottom", "spacing_left"].some(
|
|
112
|
+
(p) => hasRowSettingOverride(section, activeViewport, p)
|
|
113
|
+
) ? (
|
|
114
|
+
<div className="flex items-center gap-2 mt-1">
|
|
115
|
+
<span className="text-[9px] text-[#076bff]">overridden</span>
|
|
116
|
+
<button
|
|
117
|
+
onClick={() => {
|
|
118
|
+
// BUG-021 fix: use proper store action for responsive reset
|
|
119
|
+
let updated = section;
|
|
120
|
+
["spacing_top", "spacing_right", "spacing_bottom", "spacing_left"].forEach((p) => {
|
|
121
|
+
const updates = setRowResponsiveOverride(updated, activeViewport, p, undefined);
|
|
122
|
+
if (updates.responsive !== undefined) {
|
|
123
|
+
updated = { ...updated, responsive: updates.responsive };
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
store.updateSectionResponsive(sectionKey, (updated as PageSection).responsive);
|
|
127
|
+
}}
|
|
128
|
+
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
129
|
+
>
|
|
130
|
+
Reset
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
) : (
|
|
134
|
+
<p className="text-[9px] text-neutral-300 italic mt-1">inherited</p>
|
|
135
|
+
)
|
|
136
|
+
)}
|
|
137
|
+
</SettingsSection>
|
|
138
|
+
|
|
139
|
+
{/* Background */}
|
|
140
|
+
<SettingsSection title="Background" defaultOpen>
|
|
141
|
+
<SettingsField label="Color">
|
|
142
|
+
<ColorSwatchPicker
|
|
143
|
+
value={getRowSettingValue<string>(section, activeViewport, "background_color", "")}
|
|
144
|
+
onChange={(hex) => updateSettingResponsive("background_color", hex)}
|
|
145
|
+
swatches={paletteSwatches}
|
|
146
|
+
/>
|
|
147
|
+
</SettingsField>
|
|
148
|
+
|
|
149
|
+
<SettingsField label="Opacity">
|
|
150
|
+
<div className="flex items-center gap-2">
|
|
151
|
+
<input
|
|
152
|
+
type="range"
|
|
153
|
+
min={0}
|
|
154
|
+
max={100}
|
|
155
|
+
value={bgOpacity}
|
|
156
|
+
onChange={(e) => updateSettingResponsive("background_opacity", parseInt(e.target.value))}
|
|
157
|
+
className="flex-1 accent-[#076bff]"
|
|
158
|
+
/>
|
|
159
|
+
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
160
|
+
{bgOpacity}%
|
|
161
|
+
</span>
|
|
162
|
+
</div>
|
|
163
|
+
</SettingsField>
|
|
164
|
+
|
|
165
|
+
<SettingsField label="Image">
|
|
166
|
+
<AssetPathInput
|
|
167
|
+
value={getRowSettingValue<string>(section, activeViewport, "background_image", "")}
|
|
168
|
+
onFocus={() => store._pushSnapshot()}
|
|
169
|
+
onChange={(v) => updateSettingResponsive("background_image", v)}
|
|
170
|
+
placeholder="path/to/image.jpg"
|
|
171
|
+
filterType="image"
|
|
172
|
+
/>
|
|
173
|
+
</SettingsField>
|
|
174
|
+
|
|
175
|
+
{getRowSettingValue<string>(section, activeViewport, "background_image", "") && (
|
|
176
|
+
<>
|
|
177
|
+
<SettingsField label="Size">
|
|
178
|
+
<select
|
|
179
|
+
value={getRowSettingValue<string>(section, activeViewport, "background_size", "cover")}
|
|
180
|
+
onChange={(e) => updateSettingResponsive("background_size", e.target.value)}
|
|
181
|
+
className={SELECT_CLASS}
|
|
182
|
+
>
|
|
183
|
+
<option value="cover">Cover</option>
|
|
184
|
+
<option value="contain">Contain</option>
|
|
185
|
+
<option value="auto">Auto</option>
|
|
186
|
+
</select>
|
|
187
|
+
</SettingsField>
|
|
188
|
+
|
|
189
|
+
<SettingsField label="Position">
|
|
190
|
+
<select
|
|
191
|
+
value={getRowSettingValue<string>(section, activeViewport, "background_position", "center center")}
|
|
192
|
+
onFocus={() => store._pushSnapshot()}
|
|
193
|
+
onChange={(e) => updateSettingResponsive("background_position", e.target.value)}
|
|
194
|
+
className={SELECT_CLASS}
|
|
195
|
+
>
|
|
196
|
+
<option value="center center">Center</option>
|
|
197
|
+
<option value="top center">Top</option>
|
|
198
|
+
<option value="bottom center">Bottom</option>
|
|
199
|
+
<option value="left center">Left</option>
|
|
200
|
+
<option value="right center">Right</option>
|
|
201
|
+
<option value="top left">Top Left</option>
|
|
202
|
+
<option value="top right">Top Right</option>
|
|
203
|
+
<option value="bottom left">Bottom Left</option>
|
|
204
|
+
<option value="bottom right">Bottom Right</option>
|
|
205
|
+
</select>
|
|
206
|
+
</SettingsField>
|
|
207
|
+
|
|
208
|
+
<SettingsField label="Repeat">
|
|
209
|
+
<select
|
|
210
|
+
value={getRowSettingValue<string>(section, activeViewport, "background_repeat", "no-repeat")}
|
|
211
|
+
onChange={(e) => updateSettingResponsive("background_repeat", e.target.value)}
|
|
212
|
+
className={SELECT_CLASS}
|
|
213
|
+
>
|
|
214
|
+
<option value="no-repeat">No Repeat</option>
|
|
215
|
+
<option value="repeat">Repeat</option>
|
|
216
|
+
<option value="repeat-x">Repeat X</option>
|
|
217
|
+
<option value="repeat-y">Repeat Y</option>
|
|
218
|
+
</select>
|
|
219
|
+
</SettingsField>
|
|
220
|
+
</>
|
|
221
|
+
)}
|
|
222
|
+
</SettingsSection>
|
|
223
|
+
|
|
224
|
+
{/* Offset (Margin) */}
|
|
225
|
+
<SettingsSection title="Offset">
|
|
226
|
+
<TRBLInputs
|
|
227
|
+
top={getRowSettingValue<string>(section, activeViewport, "offset_top", "0")}
|
|
228
|
+
right={getRowSettingValue<string>(section, activeViewport, "offset_right", "0")}
|
|
229
|
+
bottom={getRowSettingValue<string>(section, activeViewport, "offset_bottom", "0")}
|
|
230
|
+
left={getRowSettingValue<string>(section, activeViewport, "offset_left", "0")}
|
|
231
|
+
onChange={(field, value) => {
|
|
232
|
+
updateSettingResponsive(`offset_${field}`, value);
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
</SettingsSection>
|
|
236
|
+
|
|
237
|
+
{/* Border */}
|
|
238
|
+
<SettingsSection title="Border">
|
|
239
|
+
<SettingsField label="Color">
|
|
240
|
+
<ColorSwatchPicker
|
|
241
|
+
value={getRowSettingValue<string>(section, activeViewport, "border_color", "")}
|
|
242
|
+
onChange={(hex) => updateSettingResponsive("border_color", hex)}
|
|
243
|
+
swatches={paletteSwatches}
|
|
244
|
+
/>
|
|
245
|
+
</SettingsField>
|
|
246
|
+
|
|
247
|
+
<SettingsField label="Width">
|
|
248
|
+
<div className="flex items-center gap-2">
|
|
249
|
+
<input
|
|
250
|
+
type="range"
|
|
251
|
+
min={0}
|
|
252
|
+
max={20}
|
|
253
|
+
value={parseInt(getRowSettingValue<string>(section, activeViewport, "border_width", "0"))}
|
|
254
|
+
onChange={(e) => updateSettingResponsive("border_width", e.target.value)}
|
|
255
|
+
className="flex-1 accent-[#076bff]"
|
|
256
|
+
/>
|
|
257
|
+
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
258
|
+
{getRowSettingValue<string>(section, activeViewport, "border_width", "0")}px
|
|
259
|
+
</span>
|
|
260
|
+
</div>
|
|
261
|
+
</SettingsField>
|
|
262
|
+
|
|
263
|
+
<SettingsField label="Style">
|
|
264
|
+
<select
|
|
265
|
+
value={getRowSettingValue<string>(section, activeViewport, "border_style", "none")}
|
|
266
|
+
onChange={(e) => updateSettingResponsive("border_style", e.target.value)}
|
|
267
|
+
className={SELECT_CLASS}
|
|
268
|
+
>
|
|
269
|
+
<option value="none">None</option>
|
|
270
|
+
<option value="solid">Solid</option>
|
|
271
|
+
<option value="dashed">Dashed</option>
|
|
272
|
+
<option value="dotted">Dotted</option>
|
|
273
|
+
</select>
|
|
274
|
+
</SettingsField>
|
|
275
|
+
|
|
276
|
+
<SettingsField label="Sides">
|
|
277
|
+
<select
|
|
278
|
+
value={getRowSettingValue<string>(section, activeViewport, "border_sides", "all")}
|
|
279
|
+
onChange={(e) => updateSettingResponsive("border_sides", e.target.value)}
|
|
280
|
+
className={SELECT_CLASS}
|
|
281
|
+
>
|
|
282
|
+
<option value="all">All</option>
|
|
283
|
+
<option value="top">Top</option>
|
|
284
|
+
<option value="right">Right</option>
|
|
285
|
+
<option value="bottom">Bottom</option>
|
|
286
|
+
<option value="left">Left</option>
|
|
287
|
+
<option value="top-bottom">Top & Bottom</option>
|
|
288
|
+
<option value="left-right">Left & Right</option>
|
|
289
|
+
</select>
|
|
290
|
+
</SettingsField>
|
|
291
|
+
|
|
292
|
+
<SettingsField label="Radius">
|
|
293
|
+
<div className="flex items-center gap-2">
|
|
294
|
+
<input
|
|
295
|
+
type="range"
|
|
296
|
+
min={0}
|
|
297
|
+
max={50}
|
|
298
|
+
value={parseInt(getRowSettingValue<string>(section, activeViewport, "border_radius", "0"))}
|
|
299
|
+
onChange={(e) => updateSettingResponsive("border_radius", e.target.value)}
|
|
300
|
+
className="flex-1 accent-[#076bff]"
|
|
301
|
+
/>
|
|
302
|
+
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
303
|
+
{getRowSettingValue<string>(section, activeViewport, "border_radius", "0")}px
|
|
304
|
+
</span>
|
|
305
|
+
</div>
|
|
306
|
+
</SettingsField>
|
|
307
|
+
</SettingsSection>
|
|
308
|
+
</>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PageSettings — General, Appearance, and Navigation settings (Settings tab).
|
|
5
|
+
* PageSeoSettings — SEO fields (dedicated SEO tab at page level).
|
|
6
|
+
*
|
|
7
|
+
* Session 64: Extracted from SettingsPanel.tsx.
|
|
8
|
+
* Session 135: Reorganized — Layout tab replaced with SEO tab at page level,
|
|
9
|
+
* Nav Color moved from Appearance to Navigation, Nav Animation renamed to Navigation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
14
|
+
import {
|
|
15
|
+
SettingsField,
|
|
16
|
+
SettingsSection,
|
|
17
|
+
INPUT_CLASS,
|
|
18
|
+
} from "../editors/shared";
|
|
19
|
+
import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
|
|
20
|
+
|
|
21
|
+
/** Convert a title to a URL-safe slug. Handles unicode (é, ñ, ü, etc.). */
|
|
22
|
+
function slugify(text: string): string {
|
|
23
|
+
return text
|
|
24
|
+
.normalize("NFD") // decompose accented chars (é → e + combining accent)
|
|
25
|
+
.replace(/[\u0300-\u036f]/g, "") // strip combining diacritical marks
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.trim()
|
|
28
|
+
.replace(/[^\w\s-]/g, "") // remove non-word chars
|
|
29
|
+
.replace(/[\s_]+/g, "-") // spaces/underscores → hyphens
|
|
30
|
+
.replace(/-+/g, "-") // collapse multiple hyphens
|
|
31
|
+
.replace(/^-|-$/g, ""); // trim leading/trailing hyphens
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function PageSettings() {
|
|
35
|
+
const store = useBuilderStore();
|
|
36
|
+
const paletteSwatches = usePaletteSwatches();
|
|
37
|
+
const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<SettingsSection title="General" defaultOpen>
|
|
42
|
+
<SettingsField label="Title">
|
|
43
|
+
<input
|
|
44
|
+
type="text"
|
|
45
|
+
value={store.pageTitle}
|
|
46
|
+
onChange={(e) => {
|
|
47
|
+
const newTitle = e.target.value;
|
|
48
|
+
store.setPageTitle(newTitle);
|
|
49
|
+
// Auto-update slug from title unless user manually edited the slug
|
|
50
|
+
if (!slugManuallyEdited) {
|
|
51
|
+
store.setPageSlug(slugify(newTitle));
|
|
52
|
+
}
|
|
53
|
+
}}
|
|
54
|
+
className={INPUT_CLASS}
|
|
55
|
+
/>
|
|
56
|
+
</SettingsField>
|
|
57
|
+
<SettingsField label="Slug">
|
|
58
|
+
<input
|
|
59
|
+
type="text"
|
|
60
|
+
value={store.pageSlug}
|
|
61
|
+
onChange={(e) => {
|
|
62
|
+
setSlugManuallyEdited(true);
|
|
63
|
+
store.setPageSlug(e.target.value);
|
|
64
|
+
}}
|
|
65
|
+
className={INPUT_CLASS}
|
|
66
|
+
/>
|
|
67
|
+
</SettingsField>
|
|
68
|
+
<SettingsField label="Type">
|
|
69
|
+
<p className="text-xs text-neutral-900 py-[7px]">
|
|
70
|
+
{store.pageType}
|
|
71
|
+
</p>
|
|
72
|
+
</SettingsField>
|
|
73
|
+
</SettingsSection>
|
|
74
|
+
|
|
75
|
+
<SettingsSection title="Appearance" defaultOpen>
|
|
76
|
+
<SettingsField label="Background">
|
|
77
|
+
<ColorSwatchPicker
|
|
78
|
+
value={store.pageSettings.background_color || ""}
|
|
79
|
+
onChange={(hex) => store.updatePageSettings({ background_color: hex || "transparent" })}
|
|
80
|
+
swatches={paletteSwatches}
|
|
81
|
+
/>
|
|
82
|
+
</SettingsField>
|
|
83
|
+
<SettingsField label="Text Color">
|
|
84
|
+
<ColorSwatchPicker
|
|
85
|
+
value={store.pageSettings.text_color || ""}
|
|
86
|
+
onChange={(hex) => store.updatePageSettings({ text_color: hex })}
|
|
87
|
+
swatches={paletteSwatches}
|
|
88
|
+
/>
|
|
89
|
+
</SettingsField>
|
|
90
|
+
</SettingsSection>
|
|
91
|
+
|
|
92
|
+
<SettingsSection title="Navigation">
|
|
93
|
+
<SettingsField label="Nav Color">
|
|
94
|
+
<ColorSwatchPicker
|
|
95
|
+
value={store.pageSettings.nav_color || ""}
|
|
96
|
+
onChange={(hex) => store.updatePageSettings({ nav_color: hex })}
|
|
97
|
+
swatches={paletteSwatches}
|
|
98
|
+
/>
|
|
99
|
+
</SettingsField>
|
|
100
|
+
<SettingsField label="Animation Override" hint="Override global nav entrance for this page">
|
|
101
|
+
<select
|
|
102
|
+
value={store.pageSettings.nav_entrance_animation || ""}
|
|
103
|
+
onChange={(e) => store.updatePageSettings({ nav_entrance_animation: e.target.value as "" | "fade-in" | "slide-down" | "blur-in" })}
|
|
104
|
+
className={INPUT_CLASS}
|
|
105
|
+
>
|
|
106
|
+
<option value="">Use Global</option>
|
|
107
|
+
<option value="fade-in">Fade In</option>
|
|
108
|
+
<option value="slide-down">Slide Down</option>
|
|
109
|
+
<option value="blur-in">Blur In</option>
|
|
110
|
+
</select>
|
|
111
|
+
</SettingsField>
|
|
112
|
+
{store.pageSettings.nav_entrance_animation && (
|
|
113
|
+
<>
|
|
114
|
+
<SettingsField label={`Duration: ${store.pageSettings.nav_entrance_duration || 600}ms`}>
|
|
115
|
+
<input
|
|
116
|
+
type="range"
|
|
117
|
+
min={200}
|
|
118
|
+
max={5000}
|
|
119
|
+
step={50}
|
|
120
|
+
value={store.pageSettings.nav_entrance_duration || 600}
|
|
121
|
+
onChange={(e) => store.updatePageSettings({ nav_entrance_duration: Number(e.target.value) })}
|
|
122
|
+
className="w-full accent-[#076bff]"
|
|
123
|
+
/>
|
|
124
|
+
</SettingsField>
|
|
125
|
+
<SettingsField label={`Delay: ${store.pageSettings.nav_entrance_delay || 0}ms`}>
|
|
126
|
+
<input
|
|
127
|
+
type="range"
|
|
128
|
+
min={0}
|
|
129
|
+
max={5000}
|
|
130
|
+
step={50}
|
|
131
|
+
value={store.pageSettings.nav_entrance_delay || 0}
|
|
132
|
+
onChange={(e) => store.updatePageSettings({ nav_entrance_delay: Number(e.target.value) })}
|
|
133
|
+
className="w-full accent-[#076bff]"
|
|
134
|
+
/>
|
|
135
|
+
</SettingsField>
|
|
136
|
+
</>
|
|
137
|
+
)}
|
|
138
|
+
<SettingsField label="Disable Animation" hint="No nav animation on this page">
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
onClick={() => store.updatePageSettings({ nav_entrance_disabled: !store.pageSettings.nav_entrance_disabled })}
|
|
142
|
+
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${
|
|
143
|
+
store.pageSettings.nav_entrance_disabled ? "bg-[#076bff]" : "bg-neutral-300"
|
|
144
|
+
}`}
|
|
145
|
+
>
|
|
146
|
+
<span
|
|
147
|
+
className={`inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform ${
|
|
148
|
+
store.pageSettings.nav_entrance_disabled ? "translate-x-[18px]" : "translate-x-[3px]"
|
|
149
|
+
}`}
|
|
150
|
+
/>
|
|
151
|
+
</button>
|
|
152
|
+
</SettingsField>
|
|
153
|
+
</SettingsSection>
|
|
154
|
+
|
|
155
|
+
</>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** SEO settings — rendered in the dedicated SEO tab at page level. */
|
|
160
|
+
export function PageSeoSettings() {
|
|
161
|
+
const store = useBuilderStore();
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<>
|
|
165
|
+
<SettingsSection title="SEO" defaultOpen>
|
|
166
|
+
<SettingsField label="SEO Title">
|
|
167
|
+
<input
|
|
168
|
+
type="text"
|
|
169
|
+
value={store.metadata.seo_title || ""}
|
|
170
|
+
onChange={(e) => store.setMetadata({ seo_title: e.target.value })}
|
|
171
|
+
className={INPUT_CLASS}
|
|
172
|
+
placeholder="Page title for search engines"
|
|
173
|
+
/>
|
|
174
|
+
</SettingsField>
|
|
175
|
+
<SettingsField label="Description">
|
|
176
|
+
<textarea
|
|
177
|
+
value={store.metadata.seo_description || ""}
|
|
178
|
+
onChange={(e) =>
|
|
179
|
+
store.setMetadata({ seo_description: e.target.value })
|
|
180
|
+
}
|
|
181
|
+
rows={2}
|
|
182
|
+
className={`${INPUT_CLASS} resize-y`}
|
|
183
|
+
placeholder="Brief description for search results"
|
|
184
|
+
/>
|
|
185
|
+
</SettingsField>
|
|
186
|
+
<SettingsField label="OG Image" hint="Social sharing image path">
|
|
187
|
+
<input
|
|
188
|
+
type="text"
|
|
189
|
+
value={store.metadata.og_image_path || ""}
|
|
190
|
+
onChange={(e) =>
|
|
191
|
+
store.setMetadata({ og_image_path: e.target.value })
|
|
192
|
+
}
|
|
193
|
+
className={INPUT_CLASS}
|
|
194
|
+
placeholder="og/page-image.jpg"
|
|
195
|
+
/>
|
|
196
|
+
</SettingsField>
|
|
197
|
+
</SettingsSection>
|
|
198
|
+
</>
|
|
199
|
+
);
|
|
200
|
+
}
|