@morphika/andami 0.5.1 → 0.5.3
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 +27 -2
- 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 +332 -320
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +44 -27
- package/app/admin/pages/page.tsx +24 -19
- package/app/admin/projects/page.tsx +30 -21
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/app/api/admin/assets/register/route.ts +51 -14
- package/app/api/admin/assets/registry/route.ts +4 -1
- package/app/api/admin/assets/relink/confirm/route.ts +4 -1
- package/app/api/admin/assets/relink/route.ts +4 -1
- package/app/api/admin/assets/scan/route.ts +4 -1
- package/app/api/admin/backups/restore-data/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +4 -1
- package/app/api/admin/r2/delete/route.ts +4 -1
- package/app/api/admin/r2/rename/route.ts +4 -1
- package/app/api/admin/r2/upload-url/route.ts +4 -1
- package/app/api/admin/revalidate/route.ts +4 -1
- package/app/api/admin/storage/switch/route.ts +4 -1
- package/app/api/custom-sections/[id]/route.ts +5 -6
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/PublishToggle.tsx +2 -2
- 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 +8 -6
- 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 +518 -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 +9 -8
- package/components/admin/styles/FontsEditor.tsx +9 -7
- 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 -286
- package/components/blocks/CoverSectionRenderer.tsx +7 -1
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/blocks/SectionV2Renderer.tsx +8 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -1
- package/components/builder/BubbleIcons.tsx +104 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +66 -49
- package/components/builder/CanvasToolbar.tsx +31 -41
- package/components/builder/CoverSectionCanvas.tsx +363 -363
- 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 +5 -3
- package/components/builder/SectionTypePicker.tsx +7 -5
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +82 -68
- package/components/builder/SettingsPanel.tsx +21 -17
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +33 -35
- package/components/builder/VirtualAssetGrid.tsx +10 -4
- package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
- package/components/builder/blockStyles.tsx +192 -185
- 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 +75 -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 +98 -93
- package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
- package/components/builder/editors/AudioBlockEditor.tsx +242 -242
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
- 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 +8 -6
- package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +21 -16
- package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +22 -17
- 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 +10 -8
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
- 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 +293 -291
- package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
- package/components/builder/live-preview/shared.tsx +5 -2
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
- 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 +337 -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 +25 -20
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/components/builder/settings-panel/index.ts +1 -0
- package/lib/animation/enter-types.ts +1 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +1 -0
- package/lib/builder/block-registrations.ts +468 -417
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/serializer/normalizers.ts +14 -0
- package/lib/builder/serializer/serializers.ts +27 -0
- package/lib/builder/store-sections.ts +23 -2
- package/lib/builder/types-slices.ts +428 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/queries.ts +48 -0
- package/lib/sanity/types.ts +112 -1
- package/lib/version.ts +1 -1
- package/package.json +7 -5
- package/sanity/schemas/blocks/audioBlock.ts +69 -69
- package/sanity/schemas/blocks/index.ts +12 -11
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -117
- package/sanity/schemas/objects/coverSection.ts +32 -0
- package/sanity/schemas/objects/parallaxSlide.ts +32 -0
- package/sanity/schemas/pageSectionV2.ts +32 -0
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- package/styles/base.css +114 -114
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
4
|
import { useThumbStatus } from "../../../lib/contexts/ThumbStatusContext";
|
|
5
5
|
import { adminAssetUrl, adminThumbUrl } from "../../../lib/assets";
|
|
6
|
+
import { BubbleTooltip } from "../BubbleIcons";
|
|
6
7
|
|
|
7
8
|
// ============================================
|
|
8
9
|
// Thumbnail status badge (builder-only)
|
|
@@ -14,10 +15,11 @@ export function ThumbBadge({ assetPath }: { assetPath: string }) {
|
|
|
14
15
|
const status = hasThumb(assetPath);
|
|
15
16
|
// undefined = not raster or unknown — don't show badge
|
|
16
17
|
if (status === undefined) return null;
|
|
18
|
+
const label = status ? "Thumbnail available" : "No thumbnail — full resolution";
|
|
17
19
|
return (
|
|
18
20
|
<span
|
|
19
|
-
|
|
20
|
-
className="absolute bottom-1.5 right-1.5 z-10 flex items-center justify-center"
|
|
21
|
+
aria-label={label}
|
|
22
|
+
className="group/bb absolute bottom-1.5 right-1.5 z-10 flex items-center justify-center"
|
|
21
23
|
style={{
|
|
22
24
|
width: 16,
|
|
23
25
|
height: 16,
|
|
@@ -38,6 +40,7 @@ export function ThumbBadge({ assetPath }: { assetPath: string }) {
|
|
|
38
40
|
<rect x="4.25" y="5" width="1.5" height="3" rx="0.5" fill="white" />
|
|
39
41
|
</svg>
|
|
40
42
|
)}
|
|
43
|
+
<BubbleTooltip>{label}</BubbleTooltip>
|
|
41
44
|
</span>
|
|
42
45
|
);
|
|
43
46
|
}
|
|
@@ -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
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
|
|
25
25
|
import { serializeColorField, parseColorField, isGradient } from "../../../lib/color-utils";
|
|
26
26
|
import { TRBLInputs } from "./TRBLInputs";
|
|
27
|
+
import { BubbleTooltip } from "../BubbleIcons";
|
|
27
28
|
import {
|
|
28
29
|
getBlockLayoutValue,
|
|
29
30
|
hasBlockLayoutOverride,
|
|
@@ -129,15 +130,16 @@ function AlignmentButtons<T extends string>({
|
|
|
129
130
|
return (
|
|
130
131
|
<button
|
|
131
132
|
key={opt.value}
|
|
132
|
-
|
|
133
|
+
aria-label={opt.label}
|
|
133
134
|
onClick={() => onChange(opt.value)}
|
|
134
|
-
className={`flex items-center justify-center w-[34px] h-[30px] rounded-md border transition-all ${
|
|
135
|
+
className={`group/bb relative flex items-center justify-center w-[34px] h-[30px] rounded-md border transition-all ${
|
|
135
136
|
isActive
|
|
136
|
-
? "bg-[#
|
|
137
|
+
? "bg-[#3580f9]/10 border-[#3580f9]/30 text-[#3580f9]"
|
|
137
138
|
: "bg-[#f5f5f5] border-transparent text-neutral-400 hover:bg-[#efefef] hover:text-neutral-600"
|
|
138
139
|
}`}
|
|
139
140
|
>
|
|
140
141
|
{renderIcon(opt.value)}
|
|
142
|
+
<BubbleTooltip>{opt.label}</BubbleTooltip>
|
|
141
143
|
</button>
|
|
142
144
|
);
|
|
143
145
|
})}
|
|
@@ -172,7 +174,7 @@ function OverrideBadge({
|
|
|
172
174
|
if (hasAny) {
|
|
173
175
|
return (
|
|
174
176
|
<div className="flex items-center gap-2 mt-1">
|
|
175
|
-
<span className="text-[9px] text-[#
|
|
177
|
+
<span className="text-[9px] text-[#3580f9]">overridden</span>
|
|
176
178
|
<button
|
|
177
179
|
onClick={onReset}
|
|
178
180
|
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
@@ -233,8 +235,8 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
|
|
|
233
235
|
<>
|
|
234
236
|
{viewportLabel && (
|
|
235
237
|
<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-[#
|
|
238
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
|
|
239
|
+
<span className="text-[11px] font-medium text-[#3580f9]">
|
|
238
240
|
Editing {viewportLabel} overrides
|
|
239
241
|
</span>
|
|
240
242
|
</div>
|
|
@@ -345,7 +347,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
|
|
|
345
347
|
max={100}
|
|
346
348
|
value={bgOpacity}
|
|
347
349
|
onChange={(e) => updateLayout("background_opacity", parseInt(e.target.value))}
|
|
348
|
-
className={`flex-1 accent-[#
|
|
350
|
+
className={`flex-1 accent-[#3580f9] ${bgIsGradient ? "opacity-40 pointer-events-none" : ""}`}
|
|
349
351
|
disabled={bgIsGradient}
|
|
350
352
|
/>
|
|
351
353
|
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
@@ -444,7 +446,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
|
|
|
444
446
|
max={20}
|
|
445
447
|
value={parseInt(getBlockLayoutValue<string>(block, activeViewport, "border_width", "0"))}
|
|
446
448
|
onChange={(e) => updateLayout("border_width", e.target.value)}
|
|
447
|
-
className="flex-1 accent-[#
|
|
449
|
+
className="flex-1 accent-[#3580f9]"
|
|
448
450
|
/>
|
|
449
451
|
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
450
452
|
{getBlockLayoutValue<string>(block, activeViewport, "border_width", "0")}px
|
|
@@ -489,7 +491,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
|
|
|
489
491
|
max={50}
|
|
490
492
|
value={parseInt(getBlockLayoutValue<string>(block, activeViewport, "border_radius", "0"))}
|
|
491
493
|
onChange={(e) => updateLayout("border_radius", e.target.value)}
|
|
492
|
-
className="flex-1 accent-[#
|
|
494
|
+
className="flex-1 accent-[#3580f9]"
|
|
493
495
|
/>
|
|
494
496
|
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
495
497
|
{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
|
+
}
|