@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,312 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SectionV2LayoutTab — Layout tab content for V2 grid sections.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from SectionV2Settings.tsx in Session 95.
|
|
7
|
+
* Controls: spacing (TRBL padding), background (color/opacity/image),
|
|
8
|
+
* offset (TRBL margins), and border properties.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
12
|
+
import type { PageSectionV2, SectionV2Settings as SectionV2SettingsType } from "../../../lib/sanity/types";
|
|
13
|
+
import {
|
|
14
|
+
SettingsField,
|
|
15
|
+
SettingsSection,
|
|
16
|
+
INPUT_CLASS,
|
|
17
|
+
} from "../editors/shared";
|
|
18
|
+
import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
|
|
19
|
+
|
|
20
|
+
export function SectionV2LayoutTab({ section }: { section: PageSectionV2 }) {
|
|
21
|
+
const store = useBuilderStore();
|
|
22
|
+
const paletteSwatches = usePaletteSwatches();
|
|
23
|
+
const settings = section.settings;
|
|
24
|
+
const activeViewport = store.activeViewport;
|
|
25
|
+
|
|
26
|
+
const viewportLabel = activeViewport !== "desktop"
|
|
27
|
+
? activeViewport === "tablet" ? "Tablet" : "Phone"
|
|
28
|
+
: null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Update setting with viewport awareness.
|
|
32
|
+
* Desktop writes directly to settings; tablet/phone writes to responsive overrides.
|
|
33
|
+
*/
|
|
34
|
+
const updateSettingResponsive = (property: string, value: unknown) => {
|
|
35
|
+
if (activeViewport === "desktop") {
|
|
36
|
+
store.updateSectionV2Settings(section._key, { [property]: value } as Partial<SectionV2SettingsType>);
|
|
37
|
+
} else {
|
|
38
|
+
const existing = section.responsive || {};
|
|
39
|
+
const vp = activeViewport as "tablet" | "phone";
|
|
40
|
+
const vpSettings = { ...(existing[vp]?.settings || {}), [property]: value };
|
|
41
|
+
if (value === undefined) delete (vpSettings as Record<string, unknown>)[property];
|
|
42
|
+
const vpOverride = { ...(existing[vp] || {}), settings: Object.keys(vpSettings).length ? vpSettings : undefined };
|
|
43
|
+
const responsive = { ...existing, [vp]: vpOverride };
|
|
44
|
+
// Clean up empty viewport override
|
|
45
|
+
if (!vpOverride.columns?.length && !vpOverride.settings) delete responsive[vp];
|
|
46
|
+
store.updateSectionV2Responsive(section._key, Object.keys(responsive).length ? responsive : undefined);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Read a setting value respecting viewport overrides */
|
|
51
|
+
const getSettingValue = <T,>(property: string, fallback: T): T => {
|
|
52
|
+
if (activeViewport !== "desktop") {
|
|
53
|
+
const vp = activeViewport as "tablet" | "phone";
|
|
54
|
+
const vpSettings = section.responsive?.[vp]?.settings as Record<string, unknown> | undefined;
|
|
55
|
+
const override = vpSettings?.[property];
|
|
56
|
+
if (override !== undefined) return override as T;
|
|
57
|
+
}
|
|
58
|
+
const val = (settings as unknown as Record<string, unknown>)[property];
|
|
59
|
+
return (val !== undefined ? val : fallback) as T;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const hasOverride = (property: string): boolean => {
|
|
63
|
+
if (activeViewport === "desktop") return false;
|
|
64
|
+
const vp = activeViewport as "tablet" | "phone";
|
|
65
|
+
const vpSettings = section.responsive?.[vp]?.settings as Record<string, unknown> | undefined;
|
|
66
|
+
return vpSettings?.[property] !== undefined;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
{viewportLabel && (
|
|
72
|
+
<div className="px-4 pt-3">
|
|
73
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#076bff]/8 border border-[#076bff]/15">
|
|
74
|
+
<span className="text-[11px] font-medium text-[#076bff]">
|
|
75
|
+
Editing {viewportLabel} overrides
|
|
76
|
+
</span>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{/* Spacing (Padding) */}
|
|
82
|
+
<SettingsSection title="Spacing" defaultOpen>
|
|
83
|
+
<div className="space-y-2">
|
|
84
|
+
{(["top", "right", "bottom", "left"] as const).map((side) => {
|
|
85
|
+
const prop = `spacing_${side}`;
|
|
86
|
+
return (
|
|
87
|
+
<SettingsField key={side} label={
|
|
88
|
+
<span className="capitalize">
|
|
89
|
+
{side}
|
|
90
|
+
{hasOverride(prop) && (
|
|
91
|
+
<span className="ml-1 text-[9px] text-[#076bff]">overridden</span>
|
|
92
|
+
)}
|
|
93
|
+
</span>
|
|
94
|
+
}>
|
|
95
|
+
<div className="flex items-center gap-2">
|
|
96
|
+
<input
|
|
97
|
+
type="text"
|
|
98
|
+
value={getSettingValue<string>(prop, "0")}
|
|
99
|
+
onChange={(e) => updateSettingResponsive(prop, e.target.value)}
|
|
100
|
+
placeholder="0"
|
|
101
|
+
className={INPUT_CLASS}
|
|
102
|
+
style={{ width: 80 }}
|
|
103
|
+
/>
|
|
104
|
+
<span className="text-[10px] text-neutral-400">px</span>
|
|
105
|
+
{hasOverride(prop) && (
|
|
106
|
+
<button
|
|
107
|
+
onClick={() => updateSettingResponsive(prop, undefined)}
|
|
108
|
+
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors ml-auto"
|
|
109
|
+
>
|
|
110
|
+
Reset
|
|
111
|
+
</button>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
</SettingsField>
|
|
115
|
+
);
|
|
116
|
+
})}
|
|
117
|
+
</div>
|
|
118
|
+
</SettingsSection>
|
|
119
|
+
|
|
120
|
+
{/* Background (full controls) */}
|
|
121
|
+
<SettingsSection title="Background" defaultOpen>
|
|
122
|
+
<SettingsField label="Color">
|
|
123
|
+
<ColorSwatchPicker
|
|
124
|
+
value={getSettingValue<string>("background_color", "")}
|
|
125
|
+
onChange={(hex) => updateSettingResponsive("background_color", hex)}
|
|
126
|
+
swatches={paletteSwatches}
|
|
127
|
+
/>
|
|
128
|
+
</SettingsField>
|
|
129
|
+
|
|
130
|
+
<SettingsField label="Opacity">
|
|
131
|
+
<div className="flex items-center gap-2">
|
|
132
|
+
<input
|
|
133
|
+
type="range"
|
|
134
|
+
min={0}
|
|
135
|
+
max={100}
|
|
136
|
+
value={getSettingValue<number>("background_opacity", 100)}
|
|
137
|
+
onChange={(e) => updateSettingResponsive("background_opacity", parseInt(e.target.value))}
|
|
138
|
+
className="flex-1 accent-[#076bff]"
|
|
139
|
+
/>
|
|
140
|
+
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
141
|
+
{getSettingValue<number>("background_opacity", 100)}%
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
</SettingsField>
|
|
145
|
+
|
|
146
|
+
<SettingsField label="Image">
|
|
147
|
+
<input
|
|
148
|
+
type="text"
|
|
149
|
+
value={getSettingValue<string>("background_image", "")}
|
|
150
|
+
onChange={(e) => updateSettingResponsive("background_image", e.target.value)}
|
|
151
|
+
placeholder="path/to/image.jpg"
|
|
152
|
+
className={INPUT_CLASS}
|
|
153
|
+
/>
|
|
154
|
+
</SettingsField>
|
|
155
|
+
|
|
156
|
+
{getSettingValue<string>("background_image", "") && (
|
|
157
|
+
<>
|
|
158
|
+
<SettingsField label="Size">
|
|
159
|
+
<select
|
|
160
|
+
value={getSettingValue<string>("background_size", "cover")}
|
|
161
|
+
onChange={(e) => updateSettingResponsive("background_size", e.target.value)}
|
|
162
|
+
className={INPUT_CLASS}
|
|
163
|
+
>
|
|
164
|
+
<option value="cover">Cover</option>
|
|
165
|
+
<option value="contain">Contain</option>
|
|
166
|
+
<option value="auto">Auto</option>
|
|
167
|
+
</select>
|
|
168
|
+
</SettingsField>
|
|
169
|
+
|
|
170
|
+
<SettingsField label="Position">
|
|
171
|
+
<select
|
|
172
|
+
value={getSettingValue<string>("background_position", "center center")}
|
|
173
|
+
onChange={(e) => updateSettingResponsive("background_position", e.target.value)}
|
|
174
|
+
className={INPUT_CLASS}
|
|
175
|
+
>
|
|
176
|
+
<option value="center center">Center</option>
|
|
177
|
+
<option value="top center">Top</option>
|
|
178
|
+
<option value="bottom center">Bottom</option>
|
|
179
|
+
<option value="left center">Left</option>
|
|
180
|
+
<option value="right center">Right</option>
|
|
181
|
+
</select>
|
|
182
|
+
</SettingsField>
|
|
183
|
+
|
|
184
|
+
<SettingsField label="Repeat">
|
|
185
|
+
<select
|
|
186
|
+
value={getSettingValue<string>("background_repeat", "no-repeat")}
|
|
187
|
+
onChange={(e) => updateSettingResponsive("background_repeat", e.target.value)}
|
|
188
|
+
className={INPUT_CLASS}
|
|
189
|
+
>
|
|
190
|
+
<option value="no-repeat">No Repeat</option>
|
|
191
|
+
<option value="repeat">Repeat</option>
|
|
192
|
+
<option value="repeat-x">Repeat X</option>
|
|
193
|
+
<option value="repeat-y">Repeat Y</option>
|
|
194
|
+
</select>
|
|
195
|
+
</SettingsField>
|
|
196
|
+
</>
|
|
197
|
+
)}
|
|
198
|
+
</SettingsSection>
|
|
199
|
+
|
|
200
|
+
{/* Offset (Margin) */}
|
|
201
|
+
<SettingsSection title="Offset">
|
|
202
|
+
<div className="space-y-2">
|
|
203
|
+
{(["top", "right", "bottom", "left"] as const).map((side) => {
|
|
204
|
+
const prop = `offset_${side}`;
|
|
205
|
+
return (
|
|
206
|
+
<SettingsField key={side} label={
|
|
207
|
+
<span className="capitalize">
|
|
208
|
+
{side}
|
|
209
|
+
{hasOverride(prop) && (
|
|
210
|
+
<span className="ml-1 text-[9px] text-[#076bff]">overridden</span>
|
|
211
|
+
)}
|
|
212
|
+
</span>
|
|
213
|
+
}>
|
|
214
|
+
<div className="flex items-center gap-2">
|
|
215
|
+
<input
|
|
216
|
+
type="text"
|
|
217
|
+
value={getSettingValue<string>(prop, "0")}
|
|
218
|
+
onChange={(e) => updateSettingResponsive(prop, e.target.value)}
|
|
219
|
+
placeholder="0"
|
|
220
|
+
className={INPUT_CLASS}
|
|
221
|
+
style={{ width: 80 }}
|
|
222
|
+
/>
|
|
223
|
+
<span className="text-[10px] text-neutral-400">px</span>
|
|
224
|
+
{hasOverride(prop) && (
|
|
225
|
+
<button
|
|
226
|
+
onClick={() => updateSettingResponsive(prop, undefined)}
|
|
227
|
+
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors ml-auto"
|
|
228
|
+
>
|
|
229
|
+
Reset
|
|
230
|
+
</button>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
</SettingsField>
|
|
234
|
+
);
|
|
235
|
+
})}
|
|
236
|
+
</div>
|
|
237
|
+
</SettingsSection>
|
|
238
|
+
|
|
239
|
+
{/* Border */}
|
|
240
|
+
<SettingsSection title="Border">
|
|
241
|
+
<SettingsField label="Color">
|
|
242
|
+
<ColorSwatchPicker
|
|
243
|
+
value={getSettingValue<string>("border_color", "")}
|
|
244
|
+
onChange={(hex) => updateSettingResponsive("border_color", hex)}
|
|
245
|
+
swatches={paletteSwatches}
|
|
246
|
+
/>
|
|
247
|
+
</SettingsField>
|
|
248
|
+
|
|
249
|
+
<SettingsField label="Width">
|
|
250
|
+
<div className="flex items-center gap-2">
|
|
251
|
+
<input
|
|
252
|
+
type="range"
|
|
253
|
+
min={0}
|
|
254
|
+
max={20}
|
|
255
|
+
value={parseInt(getSettingValue<string>("border_width", "0"))}
|
|
256
|
+
onChange={(e) => updateSettingResponsive("border_width", e.target.value)}
|
|
257
|
+
className="flex-1 accent-[#076bff]"
|
|
258
|
+
/>
|
|
259
|
+
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
260
|
+
{getSettingValue<string>("border_width", "0")}px
|
|
261
|
+
</span>
|
|
262
|
+
</div>
|
|
263
|
+
</SettingsField>
|
|
264
|
+
|
|
265
|
+
<SettingsField label="Style">
|
|
266
|
+
<select
|
|
267
|
+
value={getSettingValue<string>("border_style", "none")}
|
|
268
|
+
onChange={(e) => updateSettingResponsive("border_style", e.target.value)}
|
|
269
|
+
className={INPUT_CLASS}
|
|
270
|
+
>
|
|
271
|
+
<option value="none">None</option>
|
|
272
|
+
<option value="solid">Solid</option>
|
|
273
|
+
<option value="dashed">Dashed</option>
|
|
274
|
+
<option value="dotted">Dotted</option>
|
|
275
|
+
</select>
|
|
276
|
+
</SettingsField>
|
|
277
|
+
|
|
278
|
+
<SettingsField label="Sides">
|
|
279
|
+
<select
|
|
280
|
+
value={getSettingValue<string>("border_sides", "all")}
|
|
281
|
+
onChange={(e) => updateSettingResponsive("border_sides", e.target.value)}
|
|
282
|
+
className={INPUT_CLASS}
|
|
283
|
+
>
|
|
284
|
+
<option value="all">All</option>
|
|
285
|
+
<option value="top">Top</option>
|
|
286
|
+
<option value="right">Right</option>
|
|
287
|
+
<option value="bottom">Bottom</option>
|
|
288
|
+
<option value="left">Left</option>
|
|
289
|
+
<option value="top-bottom">Top & Bottom</option>
|
|
290
|
+
<option value="left-right">Left & Right</option>
|
|
291
|
+
</select>
|
|
292
|
+
</SettingsField>
|
|
293
|
+
|
|
294
|
+
<SettingsField label="Radius">
|
|
295
|
+
<div className="flex items-center gap-2">
|
|
296
|
+
<input
|
|
297
|
+
type="range"
|
|
298
|
+
min={0}
|
|
299
|
+
max={50}
|
|
300
|
+
value={parseInt(getSettingValue<string>("border_radius", "0"))}
|
|
301
|
+
onChange={(e) => updateSettingResponsive("border_radius", e.target.value)}
|
|
302
|
+
className="flex-1 accent-[#076bff]"
|
|
303
|
+
/>
|
|
304
|
+
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
305
|
+
{getSettingValue<string>("border_radius", "0")}px
|
|
306
|
+
</span>
|
|
307
|
+
</div>
|
|
308
|
+
</SettingsField>
|
|
309
|
+
</SettingsSection>
|
|
310
|
+
</>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SectionV2Settings — Settings panel for V2 grid sections.
|
|
5
|
+
*
|
|
6
|
+
* Three tabs managed by parent SettingsPanel:
|
|
7
|
+
* - Settings: presets grid, col_gap, row_gap, appearance (background color/opacity/image)
|
|
8
|
+
* - Layout: spacing TRBL, offset TRBL, border, background image settings (reuses LayoutTab patterns)
|
|
9
|
+
* - Animation: scroll animation picker, hover animation picker, stagger settings
|
|
10
|
+
*
|
|
11
|
+
* Session 83: Phase 4 of V2 Grid System.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
15
|
+
import type { PageSectionV2, SectionV2Settings as SectionV2SettingsType, SectionV2Preset, SectionV2SettingsOverridable } from "../../../lib/sanity/types";
|
|
16
|
+
import {
|
|
17
|
+
SettingsField,
|
|
18
|
+
SettingsSection,
|
|
19
|
+
} from "../editors/shared";
|
|
20
|
+
import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
|
|
21
|
+
import {
|
|
22
|
+
getSectionV2SettingValue,
|
|
23
|
+
hasSectionV2SettingOverride,
|
|
24
|
+
buildSectionV2SettingOverride,
|
|
25
|
+
hasAnyColumnV2Overrides,
|
|
26
|
+
hasAnySectionV2SettingOverrides,
|
|
27
|
+
buildStackOverride,
|
|
28
|
+
buildColumnV2Overrides,
|
|
29
|
+
} from "./responsive-helpers";
|
|
30
|
+
|
|
31
|
+
// ============================================
|
|
32
|
+
// Preset definitions for the picker grid
|
|
33
|
+
// ============================================
|
|
34
|
+
|
|
35
|
+
interface PresetOption {
|
|
36
|
+
id: SectionV2Preset;
|
|
37
|
+
label: string;
|
|
38
|
+
cols: number[]; // visual representation of column spans
|
|
39
|
+
readonly?: boolean; // "custom" is read-only
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const PRESETS: PresetOption[] = [
|
|
43
|
+
{ id: "full", label: "Full", cols: [12] },
|
|
44
|
+
{ id: "halves", label: "½ + ½", cols: [6, 6] },
|
|
45
|
+
{ id: "thirds", label: "⅓ × 3", cols: [4, 4, 4] },
|
|
46
|
+
{ id: "quarters", label: "¼ × 4", cols: [3, 3, 3, 3] },
|
|
47
|
+
{ id: "1/3+2/3", label: "⅓ + ⅔", cols: [4, 8] },
|
|
48
|
+
{ id: "2/3+1/3", label: "⅔ + ⅓", cols: [8, 4] },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const CUSTOM_PRESET: PresetOption = { id: "custom", label: "Custom", cols: [], readonly: true };
|
|
52
|
+
|
|
53
|
+
// ============================================
|
|
54
|
+
// Preset Grid Component
|
|
55
|
+
// ============================================
|
|
56
|
+
|
|
57
|
+
function PresetGrid({ section }: { section: PageSectionV2 }) {
|
|
58
|
+
const applyPresetV2 = useBuilderStore((s) => s.applyPresetV2);
|
|
59
|
+
const currentPreset = section.settings.preset;
|
|
60
|
+
|
|
61
|
+
const allPresets = currentPreset === "custom"
|
|
62
|
+
? [...PRESETS, CUSTOM_PRESET]
|
|
63
|
+
: PRESETS;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="grid grid-cols-3 gap-1.5">
|
|
67
|
+
{allPresets.map((preset) => {
|
|
68
|
+
const isActive = currentPreset === preset.id;
|
|
69
|
+
const isCustom = preset.id === "custom";
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<button
|
|
73
|
+
key={preset.id}
|
|
74
|
+
onClick={() => !isCustom && applyPresetV2(section._key, preset.id)}
|
|
75
|
+
className={`flex flex-col items-center gap-1 p-2 rounded-lg border transition-all ${
|
|
76
|
+
isActive
|
|
77
|
+
? "border-[#076bff] bg-[#076bff]/5"
|
|
78
|
+
: isCustom
|
|
79
|
+
? "border-neutral-200 bg-neutral-50 opacity-60 cursor-default"
|
|
80
|
+
: "border-neutral-200 bg-white hover:border-neutral-300 hover:bg-neutral-50"
|
|
81
|
+
}`}
|
|
82
|
+
title={isCustom ? "Layout doesn't match any preset" : preset.label}
|
|
83
|
+
>
|
|
84
|
+
{/* Visual representation */}
|
|
85
|
+
<div className="flex gap-0.5 w-full h-4">
|
|
86
|
+
{isCustom ? (
|
|
87
|
+
<div className="flex-1 rounded-sm bg-neutral-200 flex items-center justify-center">
|
|
88
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" className="text-neutral-400">
|
|
89
|
+
<path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
|
90
|
+
</svg>
|
|
91
|
+
</div>
|
|
92
|
+
) : (
|
|
93
|
+
preset.cols.map((span, i) => (
|
|
94
|
+
<div
|
|
95
|
+
key={i}
|
|
96
|
+
className={`rounded-sm transition-colors ${
|
|
97
|
+
isActive ? "bg-[#076bff]" : "bg-neutral-300"
|
|
98
|
+
}`}
|
|
99
|
+
style={{ flex: span }}
|
|
100
|
+
/>
|
|
101
|
+
))
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
<span className={`text-[9px] font-medium ${
|
|
105
|
+
isActive ? "text-[#076bff]" : "text-neutral-500"
|
|
106
|
+
}`}>
|
|
107
|
+
{preset.label}
|
|
108
|
+
</span>
|
|
109
|
+
</button>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================
|
|
117
|
+
// SectionV2Settings — Settings Tab Content
|
|
118
|
+
// ============================================
|
|
119
|
+
|
|
120
|
+
export default function SectionV2Settings({ section }: { section: PageSectionV2 }) {
|
|
121
|
+
const store = useBuilderStore();
|
|
122
|
+
const paletteSwatches = usePaletteSwatches();
|
|
123
|
+
const settings = section.settings;
|
|
124
|
+
const activeViewport = store.activeViewport;
|
|
125
|
+
const isResponsive = activeViewport !== "desktop";
|
|
126
|
+
|
|
127
|
+
const hasColOverrides = hasAnyColumnV2Overrides(section, activeViewport);
|
|
128
|
+
const hasSettingsOverrides = hasAnySectionV2SettingOverrides(section, activeViewport);
|
|
129
|
+
const hasAnyOverrides = hasColOverrides || hasSettingsOverrides;
|
|
130
|
+
|
|
131
|
+
/** Viewport-aware update: desktop writes to settings, tablet/phone to responsive */
|
|
132
|
+
const updateSettingResponsive = (property: keyof SectionV2SettingsOverridable, value: unknown) => {
|
|
133
|
+
if (activeViewport === "desktop") {
|
|
134
|
+
store.updateSectionV2Settings(section._key, { [property]: value } as Partial<SectionV2SettingsType>);
|
|
135
|
+
} else {
|
|
136
|
+
const responsive = buildSectionV2SettingOverride(section, activeViewport, property, value);
|
|
137
|
+
store.updateSectionV2Responsive(section._key, responsive ?? undefined);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const getGapValue = (property: "col_gap" | "row_gap", fallback: number): number => {
|
|
142
|
+
return getSectionV2SettingValue(section, activeViewport, property, fallback);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const handleStack = () => {
|
|
146
|
+
const colOverrides = buildStackOverride(section);
|
|
147
|
+
const responsive = buildColumnV2Overrides(section, activeViewport, colOverrides);
|
|
148
|
+
store.updateSectionV2Responsive(section._key, responsive ?? undefined);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const handleReset = () => {
|
|
152
|
+
// Clear all responsive overrides for the current viewport
|
|
153
|
+
const existing = section.responsive || {};
|
|
154
|
+
const vp = activeViewport as "tablet" | "phone";
|
|
155
|
+
const responsive = { ...existing };
|
|
156
|
+
delete responsive[vp];
|
|
157
|
+
store.updateSectionV2Responsive(section._key, Object.keys(responsive).length ? responsive : undefined);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
{/* Responsive info banner */}
|
|
163
|
+
{isResponsive && (
|
|
164
|
+
<div className="px-4 pt-3">
|
|
165
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#076bff]/8 border border-[#076bff]/15">
|
|
166
|
+
<span className="text-[11px] font-medium text-[#076bff]">
|
|
167
|
+
Editing {activeViewport === "tablet" ? "Tablet" : "Phone"} overrides
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{/* Responsive actions — Stack & Reset (only in tablet/phone) */}
|
|
174
|
+
{isResponsive && (
|
|
175
|
+
<div className="px-4 pt-3">
|
|
176
|
+
<div className="flex gap-2">
|
|
177
|
+
<button
|
|
178
|
+
onClick={handleStack}
|
|
179
|
+
className="flex-1 rounded-lg bg-[#076bff]/8 border border-[#076bff]/20 py-2 text-xs font-medium text-[#076bff] hover:bg-[#076bff]/15 transition-colors"
|
|
180
|
+
title="Stack all columns vertically (full width, one per row)"
|
|
181
|
+
>
|
|
182
|
+
Stack Columns
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
onClick={handleReset}
|
|
186
|
+
disabled={!hasAnyOverrides}
|
|
187
|
+
className={`flex-1 rounded-lg border py-2 text-xs font-medium transition-colors ${
|
|
188
|
+
hasAnyOverrides
|
|
189
|
+
? "bg-neutral-100 border-neutral-200 text-neutral-600 hover:bg-neutral-200"
|
|
190
|
+
: "bg-neutral-50 border-neutral-100 text-neutral-300 cursor-not-allowed"
|
|
191
|
+
}`}
|
|
192
|
+
title="Reset all responsive overrides for this viewport"
|
|
193
|
+
>
|
|
194
|
+
Reset Overrides
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
{hasAnyOverrides && (
|
|
198
|
+
<p className="text-[10px] text-[#076bff]/60 mt-1.5">
|
|
199
|
+
{hasColOverrides ? "Column layout" : ""}
|
|
200
|
+
{hasColOverrides && hasSettingsOverrides ? " + " : ""}
|
|
201
|
+
{hasSettingsOverrides ? "settings" : ""}
|
|
202
|
+
{" "}overridden
|
|
203
|
+
</p>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{/* Presets — desktop only (changing structure from responsive would be confusing) */}
|
|
209
|
+
{!isResponsive && (
|
|
210
|
+
<SettingsSection title="Layout Preset" defaultOpen>
|
|
211
|
+
<PresetGrid section={section} />
|
|
212
|
+
<p className="text-[10px] text-neutral-400 mt-1.5">
|
|
213
|
+
{section.columns.length} column{section.columns.length !== 1 ? "s" : ""}
|
|
214
|
+
{" · "}
|
|
215
|
+
{section.columns.map((c) => c.span).join(" + ")}
|
|
216
|
+
{" / "}{settings.grid_columns}
|
|
217
|
+
</p>
|
|
218
|
+
</SettingsSection>
|
|
219
|
+
)}
|
|
220
|
+
|
|
221
|
+
{/* Gaps */}
|
|
222
|
+
<SettingsSection title="Grid Gaps" defaultOpen>
|
|
223
|
+
<SettingsField label={
|
|
224
|
+
<span>
|
|
225
|
+
Col Gap
|
|
226
|
+
{isResponsive && hasSectionV2SettingOverride(section, activeViewport, "col_gap") && (
|
|
227
|
+
<span className="ml-1 text-[9px] text-[#076bff]">overridden</span>
|
|
228
|
+
)}
|
|
229
|
+
</span>
|
|
230
|
+
}>
|
|
231
|
+
<div className="flex items-center gap-2">
|
|
232
|
+
<input
|
|
233
|
+
type="range"
|
|
234
|
+
min={0}
|
|
235
|
+
max={80}
|
|
236
|
+
step={4}
|
|
237
|
+
value={getGapValue("col_gap", 20)}
|
|
238
|
+
onChange={(e) => updateSettingResponsive("col_gap", parseInt(e.target.value))}
|
|
239
|
+
className="flex-1 accent-[#076bff]"
|
|
240
|
+
/>
|
|
241
|
+
<span className="text-xs text-neutral-900 w-12 text-right">
|
|
242
|
+
{getGapValue("col_gap", 20)}px
|
|
243
|
+
</span>
|
|
244
|
+
{isResponsive && hasSectionV2SettingOverride(section, activeViewport, "col_gap") && (
|
|
245
|
+
<button
|
|
246
|
+
onClick={() => updateSettingResponsive("col_gap", undefined)}
|
|
247
|
+
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
248
|
+
>
|
|
249
|
+
Reset
|
|
250
|
+
</button>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</SettingsField>
|
|
254
|
+
|
|
255
|
+
<SettingsField label={
|
|
256
|
+
<span>
|
|
257
|
+
Row Gap
|
|
258
|
+
{isResponsive && hasSectionV2SettingOverride(section, activeViewport, "row_gap") && (
|
|
259
|
+
<span className="ml-1 text-[9px] text-[#076bff]">overridden</span>
|
|
260
|
+
)}
|
|
261
|
+
</span>
|
|
262
|
+
}>
|
|
263
|
+
<div className="flex items-center gap-2">
|
|
264
|
+
<input
|
|
265
|
+
type="range"
|
|
266
|
+
min={0}
|
|
267
|
+
max={80}
|
|
268
|
+
step={4}
|
|
269
|
+
value={getGapValue("row_gap", 20)}
|
|
270
|
+
onChange={(e) => updateSettingResponsive("row_gap", parseInt(e.target.value))}
|
|
271
|
+
className="flex-1 accent-[#076bff]"
|
|
272
|
+
/>
|
|
273
|
+
<span className="text-xs text-neutral-900 w-12 text-right">
|
|
274
|
+
{getGapValue("row_gap", 20)}px
|
|
275
|
+
</span>
|
|
276
|
+
{isResponsive && hasSectionV2SettingOverride(section, activeViewport, "row_gap") && (
|
|
277
|
+
<button
|
|
278
|
+
onClick={() => updateSettingResponsive("row_gap", undefined)}
|
|
279
|
+
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
280
|
+
>
|
|
281
|
+
Reset
|
|
282
|
+
</button>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
</SettingsField>
|
|
286
|
+
</SettingsSection>
|
|
287
|
+
|
|
288
|
+
{/* Appearance */}
|
|
289
|
+
<SettingsSection title="Appearance">
|
|
290
|
+
<SettingsField label="Background">
|
|
291
|
+
<ColorSwatchPicker
|
|
292
|
+
value={getSectionV2SettingValue(section, activeViewport, "background_color", "")}
|
|
293
|
+
onChange={(hex) => updateSettingResponsive("background_color", hex)}
|
|
294
|
+
swatches={paletteSwatches}
|
|
295
|
+
/>
|
|
296
|
+
</SettingsField>
|
|
297
|
+
|
|
298
|
+
{getSectionV2SettingValue(section, activeViewport, "background_color", "") && (
|
|
299
|
+
<SettingsField label="Opacity">
|
|
300
|
+
<div className="flex items-center gap-2">
|
|
301
|
+
<input
|
|
302
|
+
type="range"
|
|
303
|
+
min={0}
|
|
304
|
+
max={100}
|
|
305
|
+
value={getSectionV2SettingValue(section, activeViewport, "background_opacity", 100)}
|
|
306
|
+
onChange={(e) => updateSettingResponsive("background_opacity", parseInt(e.target.value))}
|
|
307
|
+
className="flex-1 accent-[#076bff]"
|
|
308
|
+
/>
|
|
309
|
+
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
310
|
+
{getSectionV2SettingValue(section, activeViewport, "background_opacity", 100)}%
|
|
311
|
+
</span>
|
|
312
|
+
</div>
|
|
313
|
+
</SettingsField>
|
|
314
|
+
)}
|
|
315
|
+
</SettingsSection>
|
|
316
|
+
|
|
317
|
+
</>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Re-export extracted components for backward compatibility
|
|
322
|
+
export { SectionV2LayoutTab } from "./SectionV2LayoutTab";
|
|
323
|
+
export { SectionV2AnimationTab } from "./SectionV2AnimationTab";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TRBLInputs — Shared 4-field input row for Top/Right/Bottom/Left values.
|
|
5
|
+
* Used by both LayoutTab (row spacing/offset) and BlockLayoutTab (block spacing/offset).
|
|
6
|
+
*
|
|
7
|
+
* Session 65: Extracted from LayoutTab.tsx.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
11
|
+
|
|
12
|
+
export function TRBLInputs({
|
|
13
|
+
top,
|
|
14
|
+
right,
|
|
15
|
+
bottom,
|
|
16
|
+
left,
|
|
17
|
+
onChange,
|
|
18
|
+
}: {
|
|
19
|
+
top: string;
|
|
20
|
+
right: string;
|
|
21
|
+
bottom: string;
|
|
22
|
+
left: string;
|
|
23
|
+
onChange: (field: "top" | "right" | "bottom" | "left", value: string) => void;
|
|
24
|
+
}) {
|
|
25
|
+
const store = useBuilderStore();
|
|
26
|
+
const fields = [
|
|
27
|
+
{ key: "top" as const, label: "TOP", value: top },
|
|
28
|
+
{ key: "right" as const, label: "RIGHT", value: right },
|
|
29
|
+
{ key: "bottom" as const, label: "BOTTOM", value: bottom },
|
|
30
|
+
{ key: "left" as const, label: "LEFT", value: left },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="grid grid-cols-4 gap-1.5">
|
|
35
|
+
{fields.map((f) => (
|
|
36
|
+
<div key={f.key} className="flex flex-col items-center gap-1">
|
|
37
|
+
<span className="text-[9px] text-neutral-300 uppercase tracking-wider">
|
|
38
|
+
{f.label}
|
|
39
|
+
</span>
|
|
40
|
+
<input
|
|
41
|
+
type="number"
|
|
42
|
+
value={f.value || "0"}
|
|
43
|
+
onFocus={() => store._pushSnapshot()}
|
|
44
|
+
onChange={(e) => onChange(f.key, e.target.value)}
|
|
45
|
+
className="w-full rounded-lg border border-transparent bg-[#f5f5f5] px-1.5 py-[6px] text-xs text-neutral-900 text-center outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#076bff] focus:shadow-[0_0_0_3px_rgba(7,107,255,0.06)]"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|