@morphika/andami 0.5.0 → 0.5.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/README.md +151 -36
- package/app/admin/assets/page.tsx +6 -6
- package/app/admin/database/page.tsx +302 -302
- package/app/admin/error.tsx +53 -53
- package/app/admin/layout.tsx +320 -327
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +6 -6
- package/app/admin/pages/page.tsx +11 -11
- package/app/admin/projects/page.tsx +14 -14
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/nav-builder/NavBuilder.tsx +1 -1
- package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
- package/components/admin/nav-builder/NavGridCell.tsx +48 -48
- package/components/admin/nav-builder/NavGridItem.tsx +4 -4
- package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
- package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
- package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
- package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
- package/components/admin/nav-builder/NavSettingsFields.tsx +514 -514
- package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
- package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
- package/components/admin/setup-wizard/DoneStep.tsx +1 -1
- package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
- package/components/admin/setup-wizard/StorageStep.tsx +2 -2
- package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
- package/components/admin/styles/ColorsEditor.tsx +2 -2
- package/components/admin/styles/FontsEditor.tsx +6 -6
- package/components/admin/styles/GridLayoutEditor.tsx +9 -9
- package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
- package/components/admin/styles/TypographyEditor.tsx +6 -6
- package/components/admin/styles/shared.tsx +68 -68
- package/components/blocks/AudioBlockRenderer.tsx +286 -0
- package/components/blocks/BeforeAfterBlockRenderer.tsx +274 -0
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/builder/BlockCardIcons.tsx +316 -227
- package/components/builder/BlockTypePicker.tsx +3 -1
- package/components/builder/BubbleIcons.tsx +90 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +2 -2
- package/components/builder/CoverSectionCanvas.tsx +363 -275
- package/components/builder/DeviceFrame.tsx +1 -1
- package/components/builder/DndWrapper.tsx +3 -3
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/SectionCardIcons.tsx +421 -320
- package/components/builder/SectionEditorBar.tsx +1 -1
- package/components/builder/SectionTypePicker.tsx +4 -4
- package/components/builder/SectionV2Canvas.tsx +20 -4
- package/components/builder/SectionV2Column.tsx +74 -68
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +27 -26
- package/components/builder/VirtualAssetGrid.tsx +2 -2
- package/components/builder/asset-browser/R2BrowserContent.tsx +34 -17
- package/components/builder/asset-browser/helpers.ts +4 -0
- package/components/builder/asset-browser/types.ts +2 -1
- package/components/builder/blockStyles.tsx +192 -173
- package/components/builder/color-picker/AlphaSlider.tsx +141 -141
- package/components/builder/color-picker/ColorInputs.tsx +105 -105
- package/components/builder/color-picker/EyedropperButton.tsx +74 -74
- package/components/builder/color-picker/HueSlider.tsx +124 -124
- package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
- package/components/builder/color-picker/SwatchBar.tsx +93 -93
- package/components/builder/editors/AudioBlockEditor.tsx +242 -0
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -0
- package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
- package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
- package/components/builder/editors/HoverEffectPicker.tsx +2 -2
- package/components/builder/editors/ImageBlockEditor.tsx +2 -2
- package/components/builder/editors/ImageGridBlockEditor.tsx +4 -4
- package/components/builder/editors/MarqueeBlockEditor.tsx +621 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +9 -9
- package/components/builder/editors/SpacerBlockEditor.tsx +5 -5
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +3 -3
- package/components/builder/editors/TextStylePicker.tsx +1 -1
- package/components/builder/editors/VideoBlockEditor.tsx +2 -2
- package/components/builder/editors/index.ts +11 -10
- package/components/builder/editors/shared.tsx +7 -7
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -0
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +176 -0
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +1 -1
- package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
- package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
- package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +291 -291
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +7 -7
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
- package/components/builder/settings-panel/CoverSectionSettings.tsx +335 -335
- package/components/builder/settings-panel/PageSettings.tsx +3 -3
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
- package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/lib/animation/enter-types.ts +3 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +3 -0
- package/lib/builder/block-registrations.ts +468 -335
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/store-sections.ts +2 -2
- package/lib/builder/types-slices.ts +414 -414
- package/lib/builder/types.ts +6 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/types.ts +156 -1
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/blocks/audioBlock.ts +69 -0
- package/sanity/schemas/blocks/beforeAfterBlock.ts +121 -0
- package/sanity/schemas/blocks/index.ts +12 -9
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -111
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- package/styles/base.css +114 -114
|
@@ -1,138 +1,138 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* AnimationTab — Routes to EnterAnimationPicker / HoverEffectPicker based on selection.
|
|
5
|
-
*
|
|
6
|
-
* Extracted from SettingsPanel.tsx in Session C (refactor split).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { useBuilderStore } from "../../../lib/builder/store";
|
|
10
|
-
import type { ContentBlock, ProjectGridBlock } from "../../../lib/sanity/types";
|
|
11
|
-
import type { HoverEffectConfig } from "../../../lib/animation/hover-effect-types";
|
|
12
|
-
import EnterAnimationPicker from "../editors/EnterAnimationPicker";
|
|
13
|
-
import HoverEffectPicker from "../editors/HoverEffectPicker";
|
|
14
|
-
import {
|
|
15
|
-
getBlockAnimationValue,
|
|
16
|
-
hasBlockAnimationOverride,
|
|
17
|
-
setBlockAnimationOverride,
|
|
18
|
-
} from "./responsive-helpers";
|
|
19
|
-
import { CardEntranceSection } from "./CardEntranceSection";
|
|
20
|
-
|
|
21
|
-
/** Safely extract hover_effect (new unified type) from any content block.
|
|
22
|
-
* ProjectGridBlock has a legacy `hover_effect: "3d" | "scale" | "none"` string field
|
|
23
|
-
* that collides — skip it (returns undefined). Other blocks have HoverEffectConfig. */
|
|
24
|
-
export function getBlockHoverEffect(block: ContentBlock): HoverEffectConfig | undefined {
|
|
25
|
-
// ProjectGridBlock hover_effect is the old per-card string — not HoverEffectConfig
|
|
26
|
-
if (block._type === "projectGridBlock") return undefined;
|
|
27
|
-
const val = (block as unknown as Record<string, unknown>).hover_effect;
|
|
28
|
-
if (val === undefined || val === null) return undefined;
|
|
29
|
-
if (typeof val === "object") return val as HoverEffectConfig;
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface AnimationTabProps {
|
|
34
|
-
selectedBlock: { block: ContentBlock; rowKey: string; colKey: string; isSection: boolean } | null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function AnimationTab({ selectedBlock }: AnimationTabProps) {
|
|
38
|
-
const store = useBuilderStore();
|
|
39
|
-
|
|
40
|
-
// Block level: type-specific enter picker + card entrance for projectGrid
|
|
41
|
-
if (selectedBlock) {
|
|
42
|
-
const isProjectGrid = selectedBlock.block._type === "projectGridBlock";
|
|
43
|
-
const pgBlock = isProjectGrid ? (selectedBlock.block as ProjectGridBlock) : null;
|
|
44
|
-
const bvp = store.activeViewport;
|
|
45
|
-
const isBlockResponsive = bvp !== "desktop";
|
|
46
|
-
|
|
47
|
-
const effectiveEnterAnim = getBlockAnimationValue(
|
|
48
|
-
selectedBlock.block, bvp, "enter_animation", undefined
|
|
49
|
-
);
|
|
50
|
-
const effectiveHoverEffect = getBlockAnimationValue(
|
|
51
|
-
selectedBlock.block, bvp, "hover_effect", undefined
|
|
52
|
-
) as HoverEffectConfig | undefined;
|
|
53
|
-
|
|
54
|
-
const hasEnterOverride = hasBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation");
|
|
55
|
-
const hasHoverOverride = hasBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect");
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<>
|
|
59
|
-
{isBlockResponsive && (
|
|
60
|
-
<div className="px-4 pt-3">
|
|
61
|
-
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#
|
|
62
|
-
<span className="text-[11px] font-medium text-[#
|
|
63
|
-
Editing {bvp === "tablet" ? "Tablet" : "Phone"} overrides
|
|
64
|
-
</span>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
)}
|
|
68
|
-
<div className="relative">
|
|
69
|
-
{hasEnterOverride && (
|
|
70
|
-
<div className="flex items-center justify-between px-4 pt-2">
|
|
71
|
-
<span className="text-[9px] text-[#
|
|
72
|
-
<button
|
|
73
|
-
onClick={() => {
|
|
74
|
-
const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation", undefined);
|
|
75
|
-
store.updateBlock(selectedBlock.block._key, updates);
|
|
76
|
-
}}
|
|
77
|
-
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
78
|
-
>
|
|
79
|
-
Reset
|
|
80
|
-
</button>
|
|
81
|
-
</div>
|
|
82
|
-
)}
|
|
83
|
-
<EnterAnimationPicker
|
|
84
|
-
mode={{ level: "block", blockType: selectedBlock.block._type }}
|
|
85
|
-
config={effectiveEnterAnim}
|
|
86
|
-
onChange={(cfg) => {
|
|
87
|
-
const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation", cfg);
|
|
88
|
-
store.updateBlock(selectedBlock.block._key, updates);
|
|
89
|
-
}}
|
|
90
|
-
/>
|
|
91
|
-
</div>
|
|
92
|
-
{/* Hover Effect — block-level only, shown if block type has hover presets */}
|
|
93
|
-
<div className="border-t border-neutral-200 my-1" />
|
|
94
|
-
<div className="relative">
|
|
95
|
-
{hasHoverOverride && (
|
|
96
|
-
<div className="flex items-center justify-between px-4 pt-2">
|
|
97
|
-
<span className="text-[9px] text-[#
|
|
98
|
-
<button
|
|
99
|
-
onClick={() => {
|
|
100
|
-
const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect", undefined);
|
|
101
|
-
store.updateBlock(selectedBlock.block._key, updates);
|
|
102
|
-
}}
|
|
103
|
-
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
104
|
-
>
|
|
105
|
-
Reset
|
|
106
|
-
</button>
|
|
107
|
-
</div>
|
|
108
|
-
)}
|
|
109
|
-
<HoverEffectPicker
|
|
110
|
-
blockType={selectedBlock.block._type}
|
|
111
|
-
config={effectiveHoverEffect ?? getBlockHoverEffect(selectedBlock.block)}
|
|
112
|
-
onChange={(cfg) => {
|
|
113
|
-
const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect", cfg);
|
|
114
|
-
store.updateBlock(selectedBlock.block._key, updates);
|
|
115
|
-
}}
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
{isProjectGrid && pgBlock && (
|
|
119
|
-
<>
|
|
120
|
-
<div className="border-t border-neutral-200 my-1" />
|
|
121
|
-
<CardEntranceSection block={pgBlock} />
|
|
122
|
-
</>
|
|
123
|
-
)}
|
|
124
|
-
</>
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Page-level: generic enter animation (no hover at page level)
|
|
129
|
-
return (
|
|
130
|
-
<EnterAnimationPicker
|
|
131
|
-
mode={{ level: "page" }}
|
|
132
|
-
config={store.pageSettings.enter_animation}
|
|
133
|
-
onChange={(cfg) => {
|
|
134
|
-
store.updatePageSettings({ enter_animation: cfg });
|
|
135
|
-
}}
|
|
136
|
-
/>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AnimationTab — Routes to EnterAnimationPicker / HoverEffectPicker based on selection.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from SettingsPanel.tsx in Session C (refactor split).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
10
|
+
import type { ContentBlock, ProjectGridBlock } from "../../../lib/sanity/types";
|
|
11
|
+
import type { HoverEffectConfig } from "../../../lib/animation/hover-effect-types";
|
|
12
|
+
import EnterAnimationPicker from "../editors/EnterAnimationPicker";
|
|
13
|
+
import HoverEffectPicker from "../editors/HoverEffectPicker";
|
|
14
|
+
import {
|
|
15
|
+
getBlockAnimationValue,
|
|
16
|
+
hasBlockAnimationOverride,
|
|
17
|
+
setBlockAnimationOverride,
|
|
18
|
+
} from "./responsive-helpers";
|
|
19
|
+
import { CardEntranceSection } from "./CardEntranceSection";
|
|
20
|
+
|
|
21
|
+
/** Safely extract hover_effect (new unified type) from any content block.
|
|
22
|
+
* ProjectGridBlock has a legacy `hover_effect: "3d" | "scale" | "none"` string field
|
|
23
|
+
* that collides — skip it (returns undefined). Other blocks have HoverEffectConfig. */
|
|
24
|
+
export function getBlockHoverEffect(block: ContentBlock): HoverEffectConfig | undefined {
|
|
25
|
+
// ProjectGridBlock hover_effect is the old per-card string — not HoverEffectConfig
|
|
26
|
+
if (block._type === "projectGridBlock") return undefined;
|
|
27
|
+
const val = (block as unknown as Record<string, unknown>).hover_effect;
|
|
28
|
+
if (val === undefined || val === null) return undefined;
|
|
29
|
+
if (typeof val === "object") return val as HoverEffectConfig;
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface AnimationTabProps {
|
|
34
|
+
selectedBlock: { block: ContentBlock; rowKey: string; colKey: string; isSection: boolean } | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function AnimationTab({ selectedBlock }: AnimationTabProps) {
|
|
38
|
+
const store = useBuilderStore();
|
|
39
|
+
|
|
40
|
+
// Block level: type-specific enter picker + card entrance for projectGrid
|
|
41
|
+
if (selectedBlock) {
|
|
42
|
+
const isProjectGrid = selectedBlock.block._type === "projectGridBlock";
|
|
43
|
+
const pgBlock = isProjectGrid ? (selectedBlock.block as ProjectGridBlock) : null;
|
|
44
|
+
const bvp = store.activeViewport;
|
|
45
|
+
const isBlockResponsive = bvp !== "desktop";
|
|
46
|
+
|
|
47
|
+
const effectiveEnterAnim = getBlockAnimationValue(
|
|
48
|
+
selectedBlock.block, bvp, "enter_animation", undefined
|
|
49
|
+
);
|
|
50
|
+
const effectiveHoverEffect = getBlockAnimationValue(
|
|
51
|
+
selectedBlock.block, bvp, "hover_effect", undefined
|
|
52
|
+
) as HoverEffectConfig | undefined;
|
|
53
|
+
|
|
54
|
+
const hasEnterOverride = hasBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation");
|
|
55
|
+
const hasHoverOverride = hasBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect");
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
{isBlockResponsive && (
|
|
60
|
+
<div className="px-4 pt-3">
|
|
61
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
|
|
62
|
+
<span className="text-[11px] font-medium text-[#3580f9]">
|
|
63
|
+
Editing {bvp === "tablet" ? "Tablet" : "Phone"} overrides
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
<div className="relative">
|
|
69
|
+
{hasEnterOverride && (
|
|
70
|
+
<div className="flex items-center justify-between px-4 pt-2">
|
|
71
|
+
<span className="text-[9px] text-[#3580f9] font-medium">overridden</span>
|
|
72
|
+
<button
|
|
73
|
+
onClick={() => {
|
|
74
|
+
const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation", undefined);
|
|
75
|
+
store.updateBlock(selectedBlock.block._key, updates);
|
|
76
|
+
}}
|
|
77
|
+
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
78
|
+
>
|
|
79
|
+
Reset
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
<EnterAnimationPicker
|
|
84
|
+
mode={{ level: "block", blockType: selectedBlock.block._type }}
|
|
85
|
+
config={effectiveEnterAnim}
|
|
86
|
+
onChange={(cfg) => {
|
|
87
|
+
const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation", cfg);
|
|
88
|
+
store.updateBlock(selectedBlock.block._key, updates);
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
{/* Hover Effect — block-level only, shown if block type has hover presets */}
|
|
93
|
+
<div className="border-t border-neutral-200 my-1" />
|
|
94
|
+
<div className="relative">
|
|
95
|
+
{hasHoverOverride && (
|
|
96
|
+
<div className="flex items-center justify-between px-4 pt-2">
|
|
97
|
+
<span className="text-[9px] text-[#3580f9] font-medium">overridden</span>
|
|
98
|
+
<button
|
|
99
|
+
onClick={() => {
|
|
100
|
+
const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect", undefined);
|
|
101
|
+
store.updateBlock(selectedBlock.block._key, updates);
|
|
102
|
+
}}
|
|
103
|
+
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
104
|
+
>
|
|
105
|
+
Reset
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
<HoverEffectPicker
|
|
110
|
+
blockType={selectedBlock.block._type}
|
|
111
|
+
config={effectiveHoverEffect ?? getBlockHoverEffect(selectedBlock.block)}
|
|
112
|
+
onChange={(cfg) => {
|
|
113
|
+
const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect", cfg);
|
|
114
|
+
store.updateBlock(selectedBlock.block._key, updates);
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
{isProjectGrid && pgBlock && (
|
|
119
|
+
<>
|
|
120
|
+
<div className="border-t border-neutral-200 my-1" />
|
|
121
|
+
<CardEntranceSection block={pgBlock} />
|
|
122
|
+
</>
|
|
123
|
+
)}
|
|
124
|
+
</>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Page-level: generic enter animation (no hover at page level)
|
|
129
|
+
return (
|
|
130
|
+
<EnterAnimationPicker
|
|
131
|
+
mode={{ level: "page" }}
|
|
132
|
+
config={store.pageSettings.enter_animation}
|
|
133
|
+
onChange={(cfg) => {
|
|
134
|
+
store.updatePageSettings({ enter_animation: cfg });
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -133,7 +133,7 @@ function AlignmentButtons<T extends string>({
|
|
|
133
133
|
onClick={() => onChange(opt.value)}
|
|
134
134
|
className={`flex items-center justify-center w-[34px] h-[30px] rounded-md border transition-all ${
|
|
135
135
|
isActive
|
|
136
|
-
? "bg-[#
|
|
136
|
+
? "bg-[#3580f9]/10 border-[#3580f9]/30 text-[#3580f9]"
|
|
137
137
|
: "bg-[#f5f5f5] border-transparent text-neutral-400 hover:bg-[#efefef] hover:text-neutral-600"
|
|
138
138
|
}`}
|
|
139
139
|
>
|
|
@@ -172,7 +172,7 @@ function OverrideBadge({
|
|
|
172
172
|
if (hasAny) {
|
|
173
173
|
return (
|
|
174
174
|
<div className="flex items-center gap-2 mt-1">
|
|
175
|
-
<span className="text-[9px] text-[#
|
|
175
|
+
<span className="text-[9px] text-[#3580f9]">overridden</span>
|
|
176
176
|
<button
|
|
177
177
|
onClick={onReset}
|
|
178
178
|
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
@@ -233,8 +233,8 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
|
|
|
233
233
|
<>
|
|
234
234
|
{viewportLabel && (
|
|
235
235
|
<div className="px-4 pt-3">
|
|
236
|
-
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#
|
|
237
|
-
<span className="text-[11px] font-medium text-[#
|
|
236
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
|
|
237
|
+
<span className="text-[11px] font-medium text-[#3580f9]">
|
|
238
238
|
Editing {viewportLabel} overrides
|
|
239
239
|
</span>
|
|
240
240
|
</div>
|
|
@@ -345,7 +345,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
|
|
|
345
345
|
max={100}
|
|
346
346
|
value={bgOpacity}
|
|
347
347
|
onChange={(e) => updateLayout("background_opacity", parseInt(e.target.value))}
|
|
348
|
-
className={`flex-1 accent-[#
|
|
348
|
+
className={`flex-1 accent-[#3580f9] ${bgIsGradient ? "opacity-40 pointer-events-none" : ""}`}
|
|
349
349
|
disabled={bgIsGradient}
|
|
350
350
|
/>
|
|
351
351
|
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
@@ -444,7 +444,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
|
|
|
444
444
|
max={20}
|
|
445
445
|
value={parseInt(getBlockLayoutValue<string>(block, activeViewport, "border_width", "0"))}
|
|
446
446
|
onChange={(e) => updateLayout("border_width", e.target.value)}
|
|
447
|
-
className="flex-1 accent-[#
|
|
447
|
+
className="flex-1 accent-[#3580f9]"
|
|
448
448
|
/>
|
|
449
449
|
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
450
450
|
{getBlockLayoutValue<string>(block, activeViewport, "border_width", "0")}px
|
|
@@ -489,7 +489,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
|
|
|
489
489
|
max={50}
|
|
490
490
|
value={parseInt(getBlockLayoutValue<string>(block, activeViewport, "border_radius", "0"))}
|
|
491
491
|
onChange={(e) => updateLayout("border_radius", e.target.value)}
|
|
492
|
-
className="flex-1 accent-[#
|
|
492
|
+
className="flex-1 accent-[#3580f9]"
|
|
493
493
|
/>
|
|
494
494
|
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
495
495
|
{getBlockLayoutValue<string>(block, activeViewport, "border_radius", "0")}px
|
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* CardEntranceSection — Toggle + preset/stagger/duration controls for
|
|
5
|
-
* ProjectGrid card entrance animations.
|
|
6
|
-
*
|
|
7
|
-
* Extracted from SettingsPanel.tsx in Session C (refactor split).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { useBuilderStore } from "../../../lib/builder/store";
|
|
11
|
-
import type { ProjectGridBlock, CardEntranceConfig } from "../../../lib/sanity/types";
|
|
12
|
-
|
|
13
|
-
export const ENTRANCE_PRESETS = [
|
|
14
|
-
{ value: "fade", label: "Fade" },
|
|
15
|
-
{ value: "slide-up", label: "Slide Up" },
|
|
16
|
-
{ value: "scale", label: "Scale" },
|
|
17
|
-
] as const;
|
|
18
|
-
|
|
19
|
-
export const CARD_ENTRANCE_SELECT_CLASS =
|
|
20
|
-
"w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#
|
|
21
|
-
|
|
22
|
-
export const CARD_ENTRANCE_SLIDER_CLASS =
|
|
23
|
-
"w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#
|
|
24
|
-
|
|
25
|
-
export function CardEntranceSection({ block }: { block: ProjectGridBlock }) {
|
|
26
|
-
const updateBlock = useBuilderStore((s) => s.updateBlock);
|
|
27
|
-
const entrance = block.card_entrance;
|
|
28
|
-
const enabled = entrance?.enabled ?? false;
|
|
29
|
-
|
|
30
|
-
const update = (updates: Partial<CardEntranceConfig>) => {
|
|
31
|
-
updateBlock(block._key, {
|
|
32
|
-
card_entrance: { ...entrance, ...updates },
|
|
33
|
-
} as Partial<ProjectGridBlock>);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div className="px-4 py-3">
|
|
38
|
-
<div className="flex items-center justify-between mb-2.5">
|
|
39
|
-
<span className="text-xs font-medium text-neutral-700">Card Entrance</span>
|
|
40
|
-
<button
|
|
41
|
-
type="button"
|
|
42
|
-
onClick={() => update({ enabled: !enabled })}
|
|
43
|
-
className={`relative w-8 h-[18px] rounded-full transition-colors ${
|
|
44
|
-
enabled ? "bg-[#
|
|
45
|
-
}`}
|
|
46
|
-
>
|
|
47
|
-
<span
|
|
48
|
-
className={`absolute top-[2px] w-[14px] h-[14px] rounded-full bg-white shadow transition-transform ${
|
|
49
|
-
enabled ? "translate-x-[16px]" : "translate-x-[2px]"
|
|
50
|
-
}`}
|
|
51
|
-
/>
|
|
52
|
-
</button>
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
{enabled && (
|
|
56
|
-
<div className="space-y-3">
|
|
57
|
-
{/* Preset — dropdown instead of segmented buttons */}
|
|
58
|
-
<div>
|
|
59
|
-
<label className="text-[11px] text-neutral-500 mb-1 block">Preset</label>
|
|
60
|
-
<select
|
|
61
|
-
value={entrance?.preset || "slide-up"}
|
|
62
|
-
onChange={(e) => update({ preset: e.target.value as "fade" | "slide-up" | "scale" })}
|
|
63
|
-
className={CARD_ENTRANCE_SELECT_CLASS}
|
|
64
|
-
>
|
|
65
|
-
{ENTRANCE_PRESETS.map((opt) => (
|
|
66
|
-
<option key={opt.value} value={opt.value}>
|
|
67
|
-
{opt.label}
|
|
68
|
-
</option>
|
|
69
|
-
))}
|
|
70
|
-
</select>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
{/* Stagger delay */}
|
|
74
|
-
<div>
|
|
75
|
-
<div className="flex items-center justify-between mb-1">
|
|
76
|
-
<label className="text-[11px] text-neutral-500">Stagger</label>
|
|
77
|
-
<span className="text-[11px] text-neutral-500 tabular-nums">
|
|
78
|
-
{entrance?.stagger_delay ?? 80}ms
|
|
79
|
-
</span>
|
|
80
|
-
</div>
|
|
81
|
-
<input
|
|
82
|
-
type="range"
|
|
83
|
-
min={0}
|
|
84
|
-
max={5000}
|
|
85
|
-
step={10}
|
|
86
|
-
value={entrance?.stagger_delay ?? 80}
|
|
87
|
-
onChange={(e) => update({ stagger_delay: Number(e.target.value) })}
|
|
88
|
-
className={CARD_ENTRANCE_SLIDER_CLASS}
|
|
89
|
-
/>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
{/* Duration */}
|
|
93
|
-
<div>
|
|
94
|
-
<div className="flex items-center justify-between mb-1">
|
|
95
|
-
<label className="text-[11px] text-neutral-500">Duration</label>
|
|
96
|
-
<span className="text-[11px] text-neutral-500 tabular-nums">
|
|
97
|
-
{entrance?.duration ?? 500}ms
|
|
98
|
-
</span>
|
|
99
|
-
</div>
|
|
100
|
-
<input
|
|
101
|
-
type="range"
|
|
102
|
-
min={200}
|
|
103
|
-
max={5000}
|
|
104
|
-
step={50}
|
|
105
|
-
value={entrance?.duration ?? 500}
|
|
106
|
-
onChange={(e) => update({ duration: Number(e.target.value) })}
|
|
107
|
-
className={CARD_ENTRANCE_SLIDER_CLASS}
|
|
108
|
-
/>
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
)}
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CardEntranceSection — Toggle + preset/stagger/duration controls for
|
|
5
|
+
* ProjectGrid card entrance animations.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from SettingsPanel.tsx in Session C (refactor split).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
11
|
+
import type { ProjectGridBlock, CardEntranceConfig } from "../../../lib/sanity/types";
|
|
12
|
+
|
|
13
|
+
export const ENTRANCE_PRESETS = [
|
|
14
|
+
{ value: "fade", label: "Fade" },
|
|
15
|
+
{ value: "slide-up", label: "Slide Up" },
|
|
16
|
+
{ value: "scale", label: "Scale" },
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export const CARD_ENTRANCE_SELECT_CLASS =
|
|
20
|
+
"w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#3580f9] focus:shadow-[0_0_0_3px_rgba(53, 128, 249,0.06)]";
|
|
21
|
+
|
|
22
|
+
export const CARD_ENTRANCE_SLIDER_CLASS =
|
|
23
|
+
"w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#3580f9] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3.5 [&::-webkit-slider-thumb]:h-3.5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#3580f9] [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:shadow-sm";
|
|
24
|
+
|
|
25
|
+
export function CardEntranceSection({ block }: { block: ProjectGridBlock }) {
|
|
26
|
+
const updateBlock = useBuilderStore((s) => s.updateBlock);
|
|
27
|
+
const entrance = block.card_entrance;
|
|
28
|
+
const enabled = entrance?.enabled ?? false;
|
|
29
|
+
|
|
30
|
+
const update = (updates: Partial<CardEntranceConfig>) => {
|
|
31
|
+
updateBlock(block._key, {
|
|
32
|
+
card_entrance: { ...entrance, ...updates },
|
|
33
|
+
} as Partial<ProjectGridBlock>);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="px-4 py-3">
|
|
38
|
+
<div className="flex items-center justify-between mb-2.5">
|
|
39
|
+
<span className="text-xs font-medium text-neutral-700">Card Entrance</span>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
onClick={() => update({ enabled: !enabled })}
|
|
43
|
+
className={`relative w-8 h-[18px] rounded-full transition-colors ${
|
|
44
|
+
enabled ? "bg-[#3580f9]" : "bg-neutral-300"
|
|
45
|
+
}`}
|
|
46
|
+
>
|
|
47
|
+
<span
|
|
48
|
+
className={`absolute top-[2px] w-[14px] h-[14px] rounded-full bg-white shadow transition-transform ${
|
|
49
|
+
enabled ? "translate-x-[16px]" : "translate-x-[2px]"
|
|
50
|
+
}`}
|
|
51
|
+
/>
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{enabled && (
|
|
56
|
+
<div className="space-y-3">
|
|
57
|
+
{/* Preset — dropdown instead of segmented buttons */}
|
|
58
|
+
<div>
|
|
59
|
+
<label className="text-[11px] text-neutral-500 mb-1 block">Preset</label>
|
|
60
|
+
<select
|
|
61
|
+
value={entrance?.preset || "slide-up"}
|
|
62
|
+
onChange={(e) => update({ preset: e.target.value as "fade" | "slide-up" | "scale" })}
|
|
63
|
+
className={CARD_ENTRANCE_SELECT_CLASS}
|
|
64
|
+
>
|
|
65
|
+
{ENTRANCE_PRESETS.map((opt) => (
|
|
66
|
+
<option key={opt.value} value={opt.value}>
|
|
67
|
+
{opt.label}
|
|
68
|
+
</option>
|
|
69
|
+
))}
|
|
70
|
+
</select>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* Stagger delay */}
|
|
74
|
+
<div>
|
|
75
|
+
<div className="flex items-center justify-between mb-1">
|
|
76
|
+
<label className="text-[11px] text-neutral-500">Stagger</label>
|
|
77
|
+
<span className="text-[11px] text-neutral-500 tabular-nums">
|
|
78
|
+
{entrance?.stagger_delay ?? 80}ms
|
|
79
|
+
</span>
|
|
80
|
+
</div>
|
|
81
|
+
<input
|
|
82
|
+
type="range"
|
|
83
|
+
min={0}
|
|
84
|
+
max={5000}
|
|
85
|
+
step={10}
|
|
86
|
+
value={entrance?.stagger_delay ?? 80}
|
|
87
|
+
onChange={(e) => update({ stagger_delay: Number(e.target.value) })}
|
|
88
|
+
className={CARD_ENTRANCE_SLIDER_CLASS}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Duration */}
|
|
93
|
+
<div>
|
|
94
|
+
<div className="flex items-center justify-between mb-1">
|
|
95
|
+
<label className="text-[11px] text-neutral-500">Duration</label>
|
|
96
|
+
<span className="text-[11px] text-neutral-500 tabular-nums">
|
|
97
|
+
{entrance?.duration ?? 500}ms
|
|
98
|
+
</span>
|
|
99
|
+
</div>
|
|
100
|
+
<input
|
|
101
|
+
type="range"
|
|
102
|
+
min={200}
|
|
103
|
+
max={5000}
|
|
104
|
+
step={50}
|
|
105
|
+
value={entrance?.duration ?? 500}
|
|
106
|
+
onChange={(e) => update({ duration: Number(e.target.value) })}
|
|
107
|
+
className={CARD_ENTRANCE_SLIDER_CLASS}
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -85,8 +85,8 @@ export default function ColumnV2Settings({
|
|
|
85
85
|
<>
|
|
86
86
|
{isResponsive && (
|
|
87
87
|
<div className="px-4 pt-3">
|
|
88
|
-
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#
|
|
89
|
-
<span className="text-[11px] font-medium text-[#
|
|
88
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
|
|
89
|
+
<span className="text-[11px] font-medium text-[#3580f9]">
|
|
90
90
|
Editing {activeViewport === "tablet" ? "Tablet" : "Phone"} overrides
|
|
91
91
|
</span>
|
|
92
92
|
</div>
|
|
@@ -98,7 +98,7 @@ export default function ColumnV2Settings({
|
|
|
98
98
|
<span>
|
|
99
99
|
Span
|
|
100
100
|
{hasSpanOverride && (
|
|
101
|
-
<span className="ml-1 text-[9px] text-[#
|
|
101
|
+
<span className="ml-1 text-[9px] text-[#3580f9]">overridden</span>
|
|
102
102
|
)}
|
|
103
103
|
</span>
|
|
104
104
|
}>
|
|
@@ -109,7 +109,7 @@ export default function ColumnV2Settings({
|
|
|
109
109
|
max={gridColumns}
|
|
110
110
|
value={effectiveSpan}
|
|
111
111
|
onChange={(e) => handleSpanChange(parseInt(e.target.value))}
|
|
112
|
-
className="flex-1 accent-[#
|
|
112
|
+
className="flex-1 accent-[#3580f9]"
|
|
113
113
|
/>
|
|
114
114
|
<span className="text-xs text-neutral-900 w-12 text-right font-medium">
|
|
115
115
|
{effectiveSpan}/{gridColumns}
|
|
@@ -133,7 +133,7 @@ export default function ColumnV2Settings({
|
|
|
133
133
|
<div
|
|
134
134
|
key={i}
|
|
135
135
|
className={`h-1.5 flex-1 rounded-full transition-colors ${
|
|
136
|
-
isActive ? "bg-[#
|
|
136
|
+
isActive ? "bg-[#3580f9]" : "bg-neutral-200"
|
|
137
137
|
}`}
|
|
138
138
|
/>
|
|
139
139
|
);
|