@morphika/webframe 0.1.0
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 +46 -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 +210 -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,297 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EnterAnimationPicker — Compact dropdown-based enter animation selector.
|
|
5
|
+
*
|
|
6
|
+
* Three modes:
|
|
7
|
+
* - **Page (generic):** Dropdown with GENERIC_ENTER_PRESETS, duration/delay sliders, easing dropdown.
|
|
8
|
+
* - **Section/Column (generic + inherit):** Inherit/Custom segmented toggle. Inherit shows parent label.
|
|
9
|
+
* - **Block (type-specific):** Dropdown with BLOCK_ENTER_PRESETS[blockType], same sliders.
|
|
10
|
+
* If blockType has no presets (spacer, projectGrid, parallax), renders nothing.
|
|
11
|
+
*
|
|
12
|
+
* Session 118 — Animation UX Refactor.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useState, useEffect } from "react";
|
|
16
|
+
import type { EnterAnimationConfig, EnterPreset, AnimationEasing } from "../../../lib/animation/enter-types";
|
|
17
|
+
import { GENERIC_ENTER_PRESETS, BLOCK_ENTER_PRESETS } from "../../../lib/animation/enter-types";
|
|
18
|
+
import { ENTER_DEFAULTS } from "../../../lib/animation/enter-types";
|
|
19
|
+
import {
|
|
20
|
+
ENTER_PRESET_MAP,
|
|
21
|
+
ENTER_EASINGS,
|
|
22
|
+
DURATION_RANGE,
|
|
23
|
+
DELAY_RANGE,
|
|
24
|
+
} from "../../../lib/animation/enter-presets";
|
|
25
|
+
import type { ContentBlock } from "../../../lib/sanity/types";
|
|
26
|
+
|
|
27
|
+
// ── CSS constants ─────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const SELECT_CLASS =
|
|
30
|
+
"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-[#076bff] focus:shadow-[0_0_0_3px_rgba(7,107,255,0.06)]";
|
|
31
|
+
|
|
32
|
+
const SLIDER_CLASS =
|
|
33
|
+
"w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#076bff] [&::-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-[#076bff] [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:shadow-sm";
|
|
34
|
+
|
|
35
|
+
// ── Types ─────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
type PickerMode =
|
|
38
|
+
| { level: "page" }
|
|
39
|
+
| { level: "section" | "column"; parentConfig?: EnterAnimationConfig }
|
|
40
|
+
| { level: "block"; blockType: ContentBlock["_type"] };
|
|
41
|
+
|
|
42
|
+
interface EnterAnimationPickerProps {
|
|
43
|
+
mode: PickerMode;
|
|
44
|
+
/** Current config — undefined means "inherit" for section/column/block modes */
|
|
45
|
+
config?: EnterAnimationConfig;
|
|
46
|
+
/** Callback: undefined = inherit, object = custom override */
|
|
47
|
+
onChange: (config: EnterAnimationConfig | undefined) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
function presetLabel(preset: EnterPreset): string {
|
|
53
|
+
return ENTER_PRESET_MAP[preset]?.label ?? preset;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolvedDescription(config?: EnterAnimationConfig): string {
|
|
57
|
+
if (!config || config.preset === "none" || !config.preset) return "None";
|
|
58
|
+
const label = presetLabel(config.preset);
|
|
59
|
+
const dur = config.duration ?? ENTER_DEFAULTS.duration;
|
|
60
|
+
return `${label} · ${dur}ms`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Component ─────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
export default function EnterAnimationPicker({
|
|
66
|
+
mode,
|
|
67
|
+
config,
|
|
68
|
+
onChange,
|
|
69
|
+
}: EnterAnimationPickerProps) {
|
|
70
|
+
// For block mode: bail out if no presets available
|
|
71
|
+
if (mode.level === "block") {
|
|
72
|
+
const presets = BLOCK_ENTER_PRESETS[mode.blockType] ?? [];
|
|
73
|
+
if (presets.length === 0) return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Inherit/Custom toggle state for section/column modes
|
|
77
|
+
const supportsInherit = mode.level === "section" || mode.level === "column";
|
|
78
|
+
const isInheriting = supportsInherit && config === undefined;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="px-4 py-3">
|
|
82
|
+
<div className="flex items-center justify-between mb-2.5">
|
|
83
|
+
<span className="text-xs font-medium text-neutral-700">Enter Animation</span>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* Inherit/Custom toggle for section & column */}
|
|
87
|
+
{supportsInherit && (mode.level === "section" || mode.level === "column") && (
|
|
88
|
+
<InheritToggle
|
|
89
|
+
isInheriting={isInheriting}
|
|
90
|
+
parentConfig={mode.parentConfig}
|
|
91
|
+
onToggle={(inherit) => {
|
|
92
|
+
if (inherit) {
|
|
93
|
+
onChange(undefined);
|
|
94
|
+
} else {
|
|
95
|
+
// Switch to custom: start with parent values or defaults
|
|
96
|
+
const parent = mode.parentConfig;
|
|
97
|
+
onChange({
|
|
98
|
+
preset: parent?.preset ?? "none",
|
|
99
|
+
duration: parent?.duration ?? ENTER_DEFAULTS.duration,
|
|
100
|
+
delay: parent?.delay ?? ENTER_DEFAULTS.delay,
|
|
101
|
+
easing: parent?.easing ?? ENTER_DEFAULTS.easing,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{/* Full editor — shown when NOT inheriting */}
|
|
109
|
+
{!isInheriting && (
|
|
110
|
+
<EnterAnimationEditor
|
|
111
|
+
mode={mode}
|
|
112
|
+
config={config}
|
|
113
|
+
onChange={onChange}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Inherit Toggle ────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
function InheritToggle({
|
|
123
|
+
isInheriting,
|
|
124
|
+
parentConfig,
|
|
125
|
+
onToggle,
|
|
126
|
+
}: {
|
|
127
|
+
isInheriting: boolean;
|
|
128
|
+
parentConfig?: EnterAnimationConfig;
|
|
129
|
+
onToggle: (inherit: boolean) => void;
|
|
130
|
+
}) {
|
|
131
|
+
return (
|
|
132
|
+
<div className="mb-3">
|
|
133
|
+
{/* Segmented control */}
|
|
134
|
+
<div className="flex gap-0.5 p-0.5 bg-[#f0f0f0] rounded-lg mb-2">
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
onClick={() => onToggle(true)}
|
|
138
|
+
className={`flex-1 py-1.5 rounded-md text-[11px] font-medium transition-all ${
|
|
139
|
+
isInheriting
|
|
140
|
+
? "bg-white text-neutral-900 shadow-sm"
|
|
141
|
+
: "text-neutral-400 hover:text-neutral-500"
|
|
142
|
+
}`}
|
|
143
|
+
>
|
|
144
|
+
Inherit
|
|
145
|
+
</button>
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={() => onToggle(false)}
|
|
149
|
+
className={`flex-1 py-1.5 rounded-md text-[11px] font-medium transition-all ${
|
|
150
|
+
!isInheriting
|
|
151
|
+
? "bg-white text-neutral-900 shadow-sm"
|
|
152
|
+
: "text-neutral-400 hover:text-neutral-500"
|
|
153
|
+
}`}
|
|
154
|
+
>
|
|
155
|
+
Custom
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Show inherited value when in inherit mode */}
|
|
160
|
+
{isInheriting && (
|
|
161
|
+
<div className="flex items-center gap-1.5 px-2.5 py-2 rounded-lg bg-[#f5f5f5]">
|
|
162
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-neutral-400 shrink-0">
|
|
163
|
+
<polyline points="7 13 12 18 17 13" />
|
|
164
|
+
<line x1="12" y1="6" x2="12" y2="18" />
|
|
165
|
+
</svg>
|
|
166
|
+
<span className="text-[11px] text-neutral-500">
|
|
167
|
+
{resolvedDescription(parentConfig)}
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Editor (preset dropdown + sliders) ────────────────────────────
|
|
176
|
+
|
|
177
|
+
function EnterAnimationEditor({
|
|
178
|
+
mode,
|
|
179
|
+
config,
|
|
180
|
+
onChange,
|
|
181
|
+
}: {
|
|
182
|
+
mode: PickerMode;
|
|
183
|
+
config?: EnterAnimationConfig;
|
|
184
|
+
onChange: (config: EnterAnimationConfig | undefined) => void;
|
|
185
|
+
}) {
|
|
186
|
+
const preset = config?.preset ?? "none";
|
|
187
|
+
const duration = config?.duration ?? ENTER_DEFAULTS.duration;
|
|
188
|
+
const delay = config?.delay ?? ENTER_DEFAULTS.delay;
|
|
189
|
+
const easing = config?.easing ?? ENTER_DEFAULTS.easing;
|
|
190
|
+
|
|
191
|
+
// Determine available presets
|
|
192
|
+
const availablePresets: readonly EnterPreset[] =
|
|
193
|
+
mode.level === "block"
|
|
194
|
+
? BLOCK_ENTER_PRESETS[mode.blockType] ?? []
|
|
195
|
+
: GENERIC_ENTER_PRESETS;
|
|
196
|
+
|
|
197
|
+
// Build update helper
|
|
198
|
+
const update = (updates: Partial<EnterAnimationConfig>) => {
|
|
199
|
+
const next: EnterAnimationConfig = {
|
|
200
|
+
preset,
|
|
201
|
+
duration,
|
|
202
|
+
delay,
|
|
203
|
+
easing,
|
|
204
|
+
...updates,
|
|
205
|
+
};
|
|
206
|
+
// For page mode, if preset is "none", store it explicitly (page always has a config)
|
|
207
|
+
onChange(next);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const showControls = preset !== "none";
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className="space-y-3">
|
|
214
|
+
{/* Preset dropdown */}
|
|
215
|
+
<div>
|
|
216
|
+
<label className="text-[11px] text-neutral-500 mb-1 block">Preset</label>
|
|
217
|
+
<select
|
|
218
|
+
value={preset}
|
|
219
|
+
onChange={(e) => {
|
|
220
|
+
const newPreset = e.target.value as EnterPreset;
|
|
221
|
+
if (newPreset === "none") {
|
|
222
|
+
// For page level, keep config with "none"; for others, same
|
|
223
|
+
update({ preset: "none" });
|
|
224
|
+
} else {
|
|
225
|
+
update({ preset: newPreset });
|
|
226
|
+
}
|
|
227
|
+
}}
|
|
228
|
+
className={SELECT_CLASS}
|
|
229
|
+
>
|
|
230
|
+
<option value="none">None</option>
|
|
231
|
+
{availablePresets
|
|
232
|
+
.filter((p) => p !== "none")
|
|
233
|
+
.map((p) => (
|
|
234
|
+
<option key={p} value={p}>
|
|
235
|
+
{presetLabel(p)}
|
|
236
|
+
</option>
|
|
237
|
+
))}
|
|
238
|
+
</select>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Duration + Delay sliders — only when a preset is selected */}
|
|
242
|
+
{showControls && (
|
|
243
|
+
<>
|
|
244
|
+
{/* Duration */}
|
|
245
|
+
<div>
|
|
246
|
+
<div className="flex items-center justify-between mb-1">
|
|
247
|
+
<label className="text-[11px] text-neutral-500">Duration</label>
|
|
248
|
+
<span className="text-[11px] text-neutral-500 tabular-nums">{duration}ms</span>
|
|
249
|
+
</div>
|
|
250
|
+
<input
|
|
251
|
+
type="range"
|
|
252
|
+
min={DURATION_RANGE.min}
|
|
253
|
+
max={DURATION_RANGE.max}
|
|
254
|
+
step={DURATION_RANGE.step}
|
|
255
|
+
value={duration}
|
|
256
|
+
onChange={(e) => update({ duration: Number(e.target.value) })}
|
|
257
|
+
className={SLIDER_CLASS}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{/* Delay */}
|
|
262
|
+
<div>
|
|
263
|
+
<div className="flex items-center justify-between mb-1">
|
|
264
|
+
<label className="text-[11px] text-neutral-500">Delay</label>
|
|
265
|
+
<span className="text-[11px] text-neutral-500 tabular-nums">{delay}ms</span>
|
|
266
|
+
</div>
|
|
267
|
+
<input
|
|
268
|
+
type="range"
|
|
269
|
+
min={DELAY_RANGE.min}
|
|
270
|
+
max={DELAY_RANGE.max}
|
|
271
|
+
step={DELAY_RANGE.step}
|
|
272
|
+
value={delay}
|
|
273
|
+
onChange={(e) => update({ delay: Number(e.target.value) })}
|
|
274
|
+
className={SLIDER_CLASS}
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
{/* Easing */}
|
|
279
|
+
<div>
|
|
280
|
+
<label className="text-[11px] text-neutral-500 mb-1 block">Easing</label>
|
|
281
|
+
<select
|
|
282
|
+
value={easing}
|
|
283
|
+
onChange={(e) => update({ easing: e.target.value as AnimationEasing })}
|
|
284
|
+
className={SELECT_CLASS}
|
|
285
|
+
>
|
|
286
|
+
{ENTER_EASINGS.map((opt) => (
|
|
287
|
+
<option key={opt.id} value={opt.id}>
|
|
288
|
+
{opt.label}
|
|
289
|
+
</option>
|
|
290
|
+
))}
|
|
291
|
+
</select>
|
|
292
|
+
</div>
|
|
293
|
+
</>
|
|
294
|
+
)}
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HoverEffectPicker — Unified hover effect selector for blocks.
|
|
5
|
+
*
|
|
6
|
+
* Combines CSS-based hovers (scale-up, lift, tilt-3d, etc.) and
|
|
7
|
+
* shader-based hovers (ripple, rgb-shift, pixelate) into one dropdown.
|
|
8
|
+
*
|
|
9
|
+
* Block-level only — no cascade, no inherit/custom toggle.
|
|
10
|
+
* Renders nothing if the block type has no available hover presets.
|
|
11
|
+
*
|
|
12
|
+
* When a shader preset is selected, additional controls appear:
|
|
13
|
+
* speed slider + smoothness segmented selector.
|
|
14
|
+
*
|
|
15
|
+
* Session 119 — Animation UX Refactor.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
HoverEffectConfig,
|
|
20
|
+
HoverPreset,
|
|
21
|
+
HoverEasing,
|
|
22
|
+
ShaderSmoothness,
|
|
23
|
+
} from "../../../lib/animation/hover-effect-types";
|
|
24
|
+
import { BLOCK_HOVER_PRESETS, HOVER_EFFECT_DEFAULTS } from "../../../lib/animation/hover-effect-types";
|
|
25
|
+
import {
|
|
26
|
+
HOVER_PRESET_MAP,
|
|
27
|
+
HOVER_EFFECT_EASINGS,
|
|
28
|
+
HOVER_DURATION_RANGE,
|
|
29
|
+
SHADER_SPEED_RANGE,
|
|
30
|
+
SHADER_SMOOTHNESS_OPTIONS,
|
|
31
|
+
isShaderPreset,
|
|
32
|
+
} from "../../../lib/animation/hover-effect-presets";
|
|
33
|
+
import type { ContentBlock } from "../../../lib/sanity/types";
|
|
34
|
+
|
|
35
|
+
// ── CSS constants ─────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
const SELECT_CLASS =
|
|
38
|
+
"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-[#076bff] focus:shadow-[0_0_0_3px_rgba(7,107,255,0.06)]";
|
|
39
|
+
|
|
40
|
+
const SLIDER_CLASS =
|
|
41
|
+
"w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#076bff] [&::-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-[#076bff] [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:shadow-sm";
|
|
42
|
+
|
|
43
|
+
// ── Types ─────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
interface HoverEffectPickerProps {
|
|
46
|
+
blockType: ContentBlock["_type"];
|
|
47
|
+
config?: HoverEffectConfig;
|
|
48
|
+
onChange: (config: HoverEffectConfig | undefined) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
function presetLabel(preset: HoverPreset): string {
|
|
54
|
+
return HOVER_PRESET_MAP[preset]?.label ?? preset;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Component ─────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export default function HoverEffectPicker({
|
|
60
|
+
blockType,
|
|
61
|
+
config,
|
|
62
|
+
onChange,
|
|
63
|
+
}: HoverEffectPickerProps) {
|
|
64
|
+
const availablePresets = BLOCK_HOVER_PRESETS[blockType] ?? [];
|
|
65
|
+
|
|
66
|
+
// Don't render anything if block type has no hover presets
|
|
67
|
+
if (availablePresets.length === 0) return null;
|
|
68
|
+
|
|
69
|
+
const preset = config?.preset ?? "none";
|
|
70
|
+
const duration = config?.duration ?? HOVER_EFFECT_DEFAULTS.duration;
|
|
71
|
+
const easing = config?.easing ?? HOVER_EFFECT_DEFAULTS.easing;
|
|
72
|
+
const shaderSpeed = config?.shader_speed ?? HOVER_EFFECT_DEFAULTS.shader_speed;
|
|
73
|
+
const shaderSmoothness = config?.shader_smoothness ?? HOVER_EFFECT_DEFAULTS.shader_smoothness;
|
|
74
|
+
|
|
75
|
+
const isShader = preset !== "none" && isShaderPreset(preset);
|
|
76
|
+
const showControls = preset !== "none";
|
|
77
|
+
|
|
78
|
+
const update = (updates: Partial<HoverEffectConfig>) => {
|
|
79
|
+
const next: HoverEffectConfig = {
|
|
80
|
+
preset,
|
|
81
|
+
duration,
|
|
82
|
+
easing,
|
|
83
|
+
...(isShader || updates.preset && isShaderPreset(updates.preset as HoverPreset)
|
|
84
|
+
? { shader_speed: shaderSpeed, shader_smoothness: shaderSmoothness }
|
|
85
|
+
: {}),
|
|
86
|
+
...updates,
|
|
87
|
+
};
|
|
88
|
+
onChange(next);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="px-4 py-3">
|
|
93
|
+
<div className="flex items-center justify-between mb-2.5">
|
|
94
|
+
<span className="text-xs font-medium text-neutral-700">Hover Effect</span>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div className="space-y-3">
|
|
98
|
+
{/* Preset dropdown */}
|
|
99
|
+
<div>
|
|
100
|
+
<label className="text-[11px] text-neutral-500 mb-1 block">Preset</label>
|
|
101
|
+
<select
|
|
102
|
+
value={preset}
|
|
103
|
+
onChange={(e) => {
|
|
104
|
+
const newPreset = e.target.value as HoverPreset;
|
|
105
|
+
if (newPreset === "none") {
|
|
106
|
+
onChange(undefined);
|
|
107
|
+
} else {
|
|
108
|
+
update({ preset: newPreset });
|
|
109
|
+
}
|
|
110
|
+
}}
|
|
111
|
+
className={SELECT_CLASS}
|
|
112
|
+
>
|
|
113
|
+
<option value="none">None</option>
|
|
114
|
+
{availablePresets.map((p) => {
|
|
115
|
+
const info = HOVER_PRESET_MAP[p];
|
|
116
|
+
const shaderTag = info?.isShader ? " (WebGL)" : "";
|
|
117
|
+
return (
|
|
118
|
+
<option key={p} value={p}>
|
|
119
|
+
{presetLabel(p)}{shaderTag}
|
|
120
|
+
</option>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
</select>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Controls — only when a preset is selected */}
|
|
127
|
+
{showControls && (
|
|
128
|
+
<>
|
|
129
|
+
{/* Duration */}
|
|
130
|
+
<div>
|
|
131
|
+
<div className="flex items-center justify-between mb-1">
|
|
132
|
+
<label className="text-[11px] text-neutral-500">Duration</label>
|
|
133
|
+
<span className="text-[11px] text-neutral-500 tabular-nums">{duration}ms</span>
|
|
134
|
+
</div>
|
|
135
|
+
<input
|
|
136
|
+
type="range"
|
|
137
|
+
min={HOVER_DURATION_RANGE.min}
|
|
138
|
+
max={HOVER_DURATION_RANGE.max}
|
|
139
|
+
step={HOVER_DURATION_RANGE.step}
|
|
140
|
+
value={duration}
|
|
141
|
+
onChange={(e) => update({ duration: Number(e.target.value) })}
|
|
142
|
+
className={SLIDER_CLASS}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Easing */}
|
|
147
|
+
<div>
|
|
148
|
+
<label className="text-[11px] text-neutral-500 mb-1 block">Easing</label>
|
|
149
|
+
<select
|
|
150
|
+
value={easing}
|
|
151
|
+
onChange={(e) => update({ easing: e.target.value as HoverEasing })}
|
|
152
|
+
className={SELECT_CLASS}
|
|
153
|
+
>
|
|
154
|
+
{HOVER_EFFECT_EASINGS.map((opt) => (
|
|
155
|
+
<option key={opt.id} value={opt.id}>
|
|
156
|
+
{opt.label}
|
|
157
|
+
</option>
|
|
158
|
+
))}
|
|
159
|
+
</select>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{/* Shader-specific controls */}
|
|
163
|
+
{isShader && (
|
|
164
|
+
<>
|
|
165
|
+
{/* Speed */}
|
|
166
|
+
<div>
|
|
167
|
+
<div className="flex items-center justify-between mb-1">
|
|
168
|
+
<label className="text-[11px] text-neutral-500">Speed</label>
|
|
169
|
+
<span className="text-[11px] text-neutral-500 tabular-nums">{shaderSpeed.toFixed(1)}x</span>
|
|
170
|
+
</div>
|
|
171
|
+
<input
|
|
172
|
+
type="range"
|
|
173
|
+
min={SHADER_SPEED_RANGE.min}
|
|
174
|
+
max={SHADER_SPEED_RANGE.max}
|
|
175
|
+
step={SHADER_SPEED_RANGE.step}
|
|
176
|
+
value={shaderSpeed}
|
|
177
|
+
onChange={(e) => update({ shader_speed: Number(e.target.value) })}
|
|
178
|
+
className={SLIDER_CLASS}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Smoothness */}
|
|
183
|
+
<div>
|
|
184
|
+
<label className="text-[11px] text-neutral-500 mb-1 block">Smoothness</label>
|
|
185
|
+
<div className="flex gap-0.5 p-0.5 bg-[#f0f0f0] rounded-lg">
|
|
186
|
+
{SHADER_SMOOTHNESS_OPTIONS.map((opt) => (
|
|
187
|
+
<button
|
|
188
|
+
key={opt.id}
|
|
189
|
+
type="button"
|
|
190
|
+
onClick={() => update({ shader_smoothness: opt.id })}
|
|
191
|
+
className={`flex-1 py-1.5 rounded-md text-[11px] font-medium transition-all ${
|
|
192
|
+
shaderSmoothness === opt.id
|
|
193
|
+
? "bg-white text-neutral-900 shadow-sm"
|
|
194
|
+
: "text-neutral-400 hover:text-neutral-500"
|
|
195
|
+
}`}
|
|
196
|
+
>
|
|
197
|
+
{opt.label}
|
|
198
|
+
</button>
|
|
199
|
+
))}
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</>
|
|
203
|
+
)}
|
|
204
|
+
</>
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|