@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
|
@@ -75,7 +75,7 @@ function BlockCard({
|
|
|
75
75
|
aria-hidden="true"
|
|
76
76
|
className="absolute inset-0 rounded-2xl pointer-events-none"
|
|
77
77
|
style={{
|
|
78
|
-
boxShadow: isHovered ? "inset 0 0 0 2px #
|
|
78
|
+
boxShadow: isHovered ? "inset 0 0 0 2px #3580f9" : "inset 0 0 0 2px transparent",
|
|
79
79
|
transition: "box-shadow 160ms ease",
|
|
80
80
|
}}
|
|
81
81
|
/>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BubbleIcons — shared icon set + tooltip wrapper for the block & column bubbles
|
|
5
|
+
* floating at the top-outside of each entity in the visual builder.
|
|
6
|
+
*
|
|
7
|
+
* Icons come from the Tabler set, re-exported as filled <path> SVGs so they
|
|
8
|
+
* scale cleanly without stroke-width inconsistencies. Viewbox is 24×24; the
|
|
9
|
+
* default display size is 12px (pill-scale).
|
|
10
|
+
*
|
|
11
|
+
* Tooltip styling mirrors the admin sidebar's group-hover tooltip
|
|
12
|
+
* (app/admin/layout.tsx) — dark pill, 150ms fade + slide-in.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ReactNode } from "react";
|
|
16
|
+
|
|
17
|
+
// ============================================
|
|
18
|
+
// Icons (filled, 24×24 viewBox)
|
|
19
|
+
// ============================================
|
|
20
|
+
|
|
21
|
+
interface IconProps {
|
|
22
|
+
size?: number;
|
|
23
|
+
color?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function DragDropIcon({ size = 12, color = "currentColor" }: IconProps) {
|
|
27
|
+
return (
|
|
28
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill={color} aria-hidden="true">
|
|
29
|
+
<path d="M11,18H9c-0.6,0-1-0.4-1-1V9c0-0.6,0.4-1,1-1h8c0.6,0,1,0.4,1,1v2c0,0.6,0.4,1,1,1s1-0.4,1-1V9c0-1.7-1.3-3-3-3H9 C7.3,6,6,7.3,6,9v8c0,1.7,1.3,3,3,3h2c0.6,0,1-0.4,1-1S11.6,18,11,18z" />
|
|
30
|
+
<path d="M22.3,15.1l-9-3c-0.4-0.1-0.8,0-1,0.2c-0.3,0.3-0.4,0.7-0.2,1l3,9c0.1,0.4,0.5,0.7,0.9,0.7c0,0,0,0,0.1,0 c0.4,0,0.7-0.2,0.9-0.6l1.9-3.7l3.7-1.9c0.4-0.2,0.6-0.6,0.5-1C23,15.5,22.7,15.2,22.3,15.1z M17.6,17.1c-0.2,0.1-0.4,0.3-0.4,0.4 l-0.9,1.8l-1.6-4.8l4.8,1.6L17.6,17.1z" />
|
|
31
|
+
<path d="M3,2C2.4,2,2,2.5,2,3s0.4,1,1,1s1-0.4,1-1v0C4,2.4,3.6,2,3,2z" />
|
|
32
|
+
<path d="M7,4c0.6,0,1-0.4,1-1v0c0-0.6-0.4-1-1-1S6,2.5,6,3S6.4,4,7,4z" />
|
|
33
|
+
<path d="M11,4c0.6,0,1-0.4,1-1v0c0-0.6-0.4-1-1-1s-1,0.5-1,1S10.4,4,11,4z" />
|
|
34
|
+
<path d="M15,4c0.6,0,1-0.4,1-1v0c0-0.6-0.4-1-1-1s-1,0.5-1,1S14.4,4,15,4z" />
|
|
35
|
+
<path d="M3,6C2.4,6,2,6.5,2,7s0.4,1,1,1s1-0.4,1-1v0C4,6.4,3.6,6,3,6z" />
|
|
36
|
+
<path d="M3,10c-0.6,0-1,0.5-1,1s0.4,1,1,1s1-0.4,1-1v0C4,10.4,3.6,10,3,10z" />
|
|
37
|
+
<path d="M3,14c-0.6,0-1,0.5-1,1s0.4,1,1,1s1-0.4,1-1v0C4,14.4,3.6,14,3,14z" />
|
|
38
|
+
</svg>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function CopyIcon({ size = 12, color = "currentColor" }: IconProps) {
|
|
43
|
+
return (
|
|
44
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill={color} aria-hidden="true">
|
|
45
|
+
<path d="M18.3,22H9.7c-2,0-3.7-1.6-3.7-3.7V9.7C6,7.6,7.6,6,9.7,6h8.7c2,0,3.7,1.6,3.7,3.7v8.7C22,20.4,20.4,22,18.3,22z M9.7,8 C8.7,8,8,8.7,8,9.7v8.7C8,19.3,8.7,20,9.7,20h8.7c0.9,0,1.7-0.7,1.7-1.7V9.7C20,8.7,19.3,8,18.3,8H9.7z" />
|
|
46
|
+
<path d="M4,17.7c-0.2,0-0.3,0-0.5-0.1C2.6,17.1,2,16.1,2,15V5c0-1.7,1.3-3,3-3h10c1.5,0,2.2,1.1,2.4,1.5C17.6,4,17.5,4.6,17,4.9 c-0.5,0.3-1.1,0.1-1.4-0.4C15.4,4,15.2,4,15,4H5C4.4,4,4,4.4,4,5v10c0,0.4,0.2,0.7,0.5,0.9c0.5,0.3,0.6,0.9,0.4,1.4 C4.7,17.6,4.4,17.7,4,17.7z" />
|
|
47
|
+
</svg>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function ArrowDownIcon({ size = 12, color = "currentColor" }: IconProps) {
|
|
52
|
+
return (
|
|
53
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill={color} aria-hidden="true">
|
|
54
|
+
<path d="M16.7,14.3c-0.4-0.4-1-0.4-1.4,0L13,16.6V5c0-0.6-0.4-1-1-1s-1,0.4-1,1v11.6l-2.3-2.3c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4 l4,4c0.1,0.1,0.2,0.2,0.3,0.2C11.7,20,11.9,20,12,20s0.3,0,0.4-0.1c0.1-0.1,0.2-0.1,0.3-0.2l4-4C17.1,15.3,17.1,14.7,16.7,14.3z" />
|
|
55
|
+
</svg>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ArrowUpIcon({ size = 12, color = "currentColor" }: IconProps) {
|
|
60
|
+
return (
|
|
61
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill={color} aria-hidden="true" style={{ transform: "rotate(180deg)" }}>
|
|
62
|
+
<path d="M16.7,14.3c-0.4-0.4-1-0.4-1.4,0L13,16.6V5c0-0.6-0.4-1-1-1s-1,0.4-1,1v11.6l-2.3-2.3c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4 l4,4c0.1,0.1,0.2,0.2,0.3,0.2C11.7,20,11.9,20,12,20s0.3,0,0.4-0.1c0.1-0.1,0.2-0.1,0.3-0.2l4-4C17.1,15.3,17.1,14.7,16.7,14.3z" />
|
|
63
|
+
</svg>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function CloseIcon({ size = 12, color = "currentColor" }: IconProps) {
|
|
68
|
+
return (
|
|
69
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill={color} aria-hidden="true">
|
|
70
|
+
<path d="M16.2,4H7.6c-2,0-3.7,1.6-3.7,3.7v8.7c0,2,1.6,3.7,3.7,3.7h8.7c2,0,3.7-1.6,3.7-3.7V7.7C19.9,5.7,18.3,4,16.2,4z M15,13.8 c0.4,0.4,0.4,1,0,1.4l-0.1,0.1c-0.4,0.3-1,0.3-1.3-0.1l-1.8-1.8L10,15.2c-0.4,0.4-1,0.4-1.4,0l-0.1-0.1c-0.3-0.4-0.3-1,0.1-1.3 l1.8-1.8l-1.7-1.8c-0.4-0.4-0.4-1,0-1.4l0.1-0.1c0.4-0.3,1-0.3,1.3,0.1l1.7,1.8l1.8-1.8c0.4-0.4,1-0.4,1.4,0l0.1,0.1 c0.3,0.4,0.3,1-0.1,1.3L13.3,12L15,13.8z" />
|
|
71
|
+
</svg>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================
|
|
76
|
+
// Tooltip — mirrors the admin sidebar pattern (CSS-only, group-hover).
|
|
77
|
+
// Positioned below the trigger; parent needs `group relative`.
|
|
78
|
+
// ============================================
|
|
79
|
+
|
|
80
|
+
export function BubbleTooltip({ children }: { children: ReactNode }) {
|
|
81
|
+
return (
|
|
82
|
+
<span
|
|
83
|
+
role="tooltip"
|
|
84
|
+
className="pointer-events-none absolute left-1/2 top-full z-50 mt-1.5 whitespace-nowrap rounded-md border border-white/10 bg-[#2a2d33] px-2.5 py-1.5 text-[11px] font-medium text-white shadow-lg opacity-0 transition-[opacity,margin] duration-150 ease-out group-hover/bb:opacity-100 group-hover/bb:mt-3"
|
|
85
|
+
style={{ transform: "translateX(-50%)" }}
|
|
86
|
+
>
|
|
87
|
+
{children}
|
|
88
|
+
</span>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Same styling as BubbleTooltip but positioned ABOVE the trigger.
|
|
93
|
+
* For bottom-anchored toolbars (e.g. the floating canvas toolbar). */
|
|
94
|
+
export function BubbleTooltipAbove({ children }: { children: ReactNode }) {
|
|
95
|
+
return (
|
|
96
|
+
<span
|
|
97
|
+
role="tooltip"
|
|
98
|
+
className="pointer-events-none absolute left-1/2 bottom-full z-50 mb-1.5 whitespace-nowrap rounded-md border border-white/10 bg-[#2a2d33] px-2.5 py-1.5 text-[11px] font-medium text-white shadow-lg opacity-0 transition-[opacity,margin] duration-150 ease-out group-hover/bb:opacity-100 group-hover/bb:mb-3"
|
|
99
|
+
style={{ transform: "translateX(-50%)" }}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</span>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -69,6 +69,7 @@ export default function BuilderCanvas({ children }: BuilderCanvasProps) {
|
|
|
69
69
|
const setActiveViewport = useBuilderStore((s) => s.setActiveViewport);
|
|
70
70
|
const bgColor = useBuilderStore((s) => s.pageSettings.background_color);
|
|
71
71
|
const pageSlug = useBuilderStore((s) => s.pageSlug);
|
|
72
|
+
const clearSelection = useBuilderStore((s) => s.clearSelection);
|
|
72
73
|
|
|
73
74
|
// Refs for stable wheel/pan handlers — avoids re-creating callbacks on every zoom/pan change
|
|
74
75
|
const zoomRef = useRef(zoom);
|
|
@@ -288,6 +289,7 @@ export default function BuilderCanvas({ children }: BuilderCanvasProps) {
|
|
|
288
289
|
onMouseMove={handleMouseMove}
|
|
289
290
|
onMouseUp={handleMouseUp}
|
|
290
291
|
onMouseLeave={handleMouseUp}
|
|
292
|
+
onClick={clearSelection}
|
|
291
293
|
>
|
|
292
294
|
{/* Dot grid background */}
|
|
293
295
|
<div
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useCallback, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { useBuilderStore } from "../../lib/builder/store";
|
|
5
5
|
import { DEVICE_WIDTHS } from "../../lib/builder/types";
|
|
6
|
+
import { BubbleTooltipAbove } from "./BubbleIcons";
|
|
6
7
|
|
|
7
8
|
// ============================================
|
|
8
9
|
// CanvasMinimap — Shows viewport position in canvas
|
|
@@ -121,80 +122,96 @@ export default function CanvasMinimap({
|
|
|
121
122
|
return (
|
|
122
123
|
<button
|
|
123
124
|
onClick={() => setIsCollapsed(false)}
|
|
124
|
-
className="absolute bottom-6 right-4 z-40 w-8 h-8 flex items-center justify-center rounded-lg bg-[#1a1a1a]/80 text-white/60 hover:text-white hover:bg-[#1a1a1a] transition-colors shadow-lg backdrop-blur-sm"
|
|
125
|
-
title="Show minimap"
|
|
125
|
+
className="group/bb absolute bottom-6 right-4 z-40 w-8 h-8 flex items-center justify-center rounded-lg bg-[#1a1a1a]/80 text-white/60 hover:text-white hover:bg-[#1a1a1a] transition-colors shadow-lg backdrop-blur-sm"
|
|
126
126
|
aria-label="Show minimap"
|
|
127
127
|
>
|
|
128
128
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
129
129
|
<rect x="1" y="1" width="12" height="12" rx="1.5" stroke="currentColor" strokeWidth="1.2" />
|
|
130
130
|
<rect x="3" y="3" width="4" height="3" rx="0.5" stroke="currentColor" strokeWidth="0.8" opacity="0.6" />
|
|
131
131
|
</svg>
|
|
132
|
+
<BubbleTooltipAbove>Show minimap</BubbleTooltipAbove>
|
|
132
133
|
</button>
|
|
133
134
|
);
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
return (
|
|
137
138
|
<div
|
|
138
|
-
className="absolute bottom-6 right-4 z-40 rounded-lg overflow-hidden shadow-
|
|
139
|
+
className="absolute bottom-6 right-4 z-40 rounded-lg overflow-hidden shadow-xl backdrop-blur-sm"
|
|
139
140
|
style={{
|
|
140
141
|
width: MINIMAP_WIDTH,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
142
|
+
backgroundColor: "rgba(26, 26, 26, 0.92)",
|
|
143
|
+
border: "1px solid rgba(255, 255, 255, 0.08)",
|
|
144
144
|
userSelect: "none",
|
|
145
145
|
}}
|
|
146
|
-
onMouseDown={handleMinimapMouseDown}
|
|
147
|
-
onMouseMove={handleMouseMove}
|
|
148
|
-
onMouseUp={handleMouseUp}
|
|
149
|
-
onMouseLeave={handleMouseUp}
|
|
150
146
|
>
|
|
151
|
-
{/*
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
147
|
+
{/* Header — label + close button. Matches the toolbar's dark pill aesthetic. */}
|
|
148
|
+
<div
|
|
149
|
+
className="flex items-center justify-between px-2 py-1"
|
|
150
|
+
style={{
|
|
151
|
+
borderBottom: "1px solid rgba(255, 255, 255, 0.06)",
|
|
152
|
+
background: "rgba(255, 255, 255, 0.02)",
|
|
156
153
|
}}
|
|
157
|
-
className="absolute top-1 right-1 z-50 w-4 h-4 flex items-center justify-center rounded text-white/40 hover:text-white/80 transition-colors"
|
|
158
|
-
title="Hide minimap"
|
|
159
|
-
aria-label="Hide minimap"
|
|
160
154
|
>
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
</
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
155
|
+
<span className="text-[9px] font-semibold tracking-[0.08em] uppercase text-white/45">
|
|
156
|
+
Overview
|
|
157
|
+
</span>
|
|
158
|
+
<button
|
|
159
|
+
onClick={() => setIsCollapsed(true)}
|
|
160
|
+
className="group/bb relative w-4 h-4 flex items-center justify-center rounded text-white/40 hover:text-white/90 hover:bg-white/10 transition-colors"
|
|
161
|
+
aria-label="Hide minimap"
|
|
162
|
+
>
|
|
163
|
+
<svg width="8" height="8" viewBox="0 0 8 8" fill="none">
|
|
164
|
+
<path d="M1 1L7 7M7 1L1 7" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
|
|
165
|
+
</svg>
|
|
166
|
+
<BubbleTooltipAbove>Hide minimap</BubbleTooltipAbove>
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Content area — all pan/drag interactions live here (not the header). */}
|
|
171
|
+
<div
|
|
172
|
+
className="relative"
|
|
173
|
+
style={{
|
|
174
|
+
width: MINIMAP_WIDTH,
|
|
175
|
+
height: MINIMAP_HEIGHT,
|
|
176
|
+
}}
|
|
177
|
+
onMouseDown={handleMinimapMouseDown}
|
|
178
|
+
onMouseMove={handleMouseMove}
|
|
179
|
+
onMouseUp={handleMouseUp}
|
|
180
|
+
onMouseLeave={handleMouseUp}
|
|
181
|
+
>
|
|
182
|
+
{/* Device frame outlines */}
|
|
183
|
+
{frames.map((f, i) => (
|
|
184
|
+
<div
|
|
185
|
+
key={i}
|
|
186
|
+
className="absolute"
|
|
187
|
+
style={{
|
|
188
|
+
left: f.x,
|
|
189
|
+
top: 8 * scale,
|
|
190
|
+
width: f.w,
|
|
191
|
+
height: (contentEstimateHeight - 40) * scale,
|
|
192
|
+
border: `1px solid ${f.active ? "rgba(53, 128, 249, 0.6)" : "rgba(255, 255, 255, 0.15)"}`,
|
|
193
|
+
borderRadius: 2,
|
|
194
|
+
backgroundColor: f.active ? "rgba(53, 128, 249, 0.08)" : "rgba(255, 255, 255, 0.03)",
|
|
195
|
+
}}
|
|
196
|
+
/>
|
|
197
|
+
))}
|
|
198
|
+
|
|
199
|
+
{/* Viewport indicator */}
|
|
168
200
|
<div
|
|
169
|
-
key={i}
|
|
170
201
|
className="absolute"
|
|
171
202
|
style={{
|
|
172
|
-
left:
|
|
173
|
-
top:
|
|
174
|
-
width:
|
|
175
|
-
height: (
|
|
176
|
-
border:
|
|
203
|
+
left: Math.max(0, viewX),
|
|
204
|
+
top: Math.max(0, viewY),
|
|
205
|
+
width: Math.min(viewW, MINIMAP_WIDTH),
|
|
206
|
+
height: Math.min(viewH, MINIMAP_HEIGHT),
|
|
207
|
+
border: "1.5px solid rgba(237, 56, 33, 0.7)",
|
|
177
208
|
borderRadius: 2,
|
|
178
|
-
backgroundColor:
|
|
209
|
+
backgroundColor: "rgba(237, 56, 33, 0.06)",
|
|
210
|
+
cursor: isDragging ? "grabbing" : "grab",
|
|
211
|
+
pointerEvents: "none",
|
|
179
212
|
}}
|
|
180
213
|
/>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
{/* Viewport indicator */}
|
|
184
|
-
<div
|
|
185
|
-
className="absolute"
|
|
186
|
-
style={{
|
|
187
|
-
left: Math.max(0, viewX),
|
|
188
|
-
top: Math.max(0, viewY),
|
|
189
|
-
width: Math.min(viewW, MINIMAP_WIDTH),
|
|
190
|
-
height: Math.min(viewH, MINIMAP_HEIGHT),
|
|
191
|
-
border: "1.5px solid rgba(237, 56, 33, 0.7)",
|
|
192
|
-
borderRadius: 2,
|
|
193
|
-
backgroundColor: "rgba(237, 56, 33, 0.06)",
|
|
194
|
-
cursor: isDragging ? "grabbing" : "grab",
|
|
195
|
-
pointerEvents: "none",
|
|
196
|
-
}}
|
|
197
|
-
/>
|
|
214
|
+
</div>
|
|
198
215
|
</div>
|
|
199
216
|
);
|
|
200
217
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useRef, useState, useEffect, useCallback } from "react";
|
|
4
4
|
import { useBuilderStore } from "../../lib/builder/store";
|
|
5
|
+
import { BubbleTooltipAbove } from "./BubbleIcons";
|
|
5
6
|
|
|
6
7
|
// ============================================
|
|
7
8
|
// CanvasToolbar — Floating mini toolbar
|
|
@@ -72,66 +73,68 @@ export default function CanvasToolbar({ viewportRef, onAnimatedAction }: CanvasT
|
|
|
72
73
|
const zoomPercent = Math.round(zoom * 100);
|
|
73
74
|
|
|
74
75
|
return (
|
|
75
|
-
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-40 flex items-center gap-
|
|
76
|
+
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-40 flex items-center gap-0.5 rounded-full bg-[#1a1a1a] shadow-xl px-1.5 py-1.5"
|
|
76
77
|
style={{ userSelect: "none" }}
|
|
77
78
|
>
|
|
78
|
-
{/* Select tool — Figma-style filled cursor
|
|
79
|
+
{/* Select tool — Figma-style filled cursor.
|
|
80
|
+
Active state uses the app's primary blue so selection signals
|
|
81
|
+
match the rest of the builder (block/column accents). */}
|
|
79
82
|
<button
|
|
80
83
|
onClick={() => setCanvasTool("select")}
|
|
81
|
-
className={`flex items-center justify-center w-8 h-8 rounded-full transition-colors ${
|
|
84
|
+
className={`group/bb relative flex items-center justify-center w-8 h-8 rounded-full transition-colors ${
|
|
82
85
|
tool === "select"
|
|
83
|
-
? "bg-
|
|
86
|
+
? "bg-[#3580f9] text-white"
|
|
84
87
|
: "text-neutral-400 hover:text-white hover:bg-white/10"
|
|
85
88
|
}`}
|
|
86
|
-
title="Select tool (V)"
|
|
87
89
|
aria-label="Select tool"
|
|
88
90
|
>
|
|
89
91
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round">
|
|
90
92
|
<path d="M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z" />
|
|
91
93
|
</svg>
|
|
94
|
+
<BubbleTooltipAbove>Select <span className="ml-1 text-white/55">V</span></BubbleTooltipAbove>
|
|
92
95
|
</button>
|
|
93
96
|
|
|
94
|
-
{/* Hand tool —
|
|
97
|
+
{/* Hand tool — tabler hand-stop (outline) */}
|
|
95
98
|
<button
|
|
96
99
|
onClick={() => setCanvasTool("hand")}
|
|
97
|
-
className={`flex items-center justify-center w-8 h-8 rounded-full transition-colors ${
|
|
100
|
+
className={`group/bb relative flex items-center justify-center w-8 h-8 rounded-full transition-colors ${
|
|
98
101
|
tool === "hand"
|
|
99
|
-
? "bg-
|
|
102
|
+
? "bg-[#3580f9] text-white"
|
|
100
103
|
: "text-neutral-400 hover:text-white hover:bg-white/10"
|
|
101
104
|
}`}
|
|
102
|
-
title="Hand tool (H)"
|
|
103
105
|
aria-label="Hand tool"
|
|
104
106
|
>
|
|
105
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="
|
|
106
|
-
<path d="
|
|
107
|
-
<path d="
|
|
108
|
-
<path d="
|
|
109
|
-
<path d="
|
|
107
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
108
|
+
<path d="M8 13v-7.5a1.5 1.5 0 0 1 3 0v6.5" />
|
|
109
|
+
<path d="M11 5.5v-2a1.5 1.5 0 1 1 3 0v8.5" />
|
|
110
|
+
<path d="M14 5.5a1.5 1.5 0 0 1 3 0v6.5" />
|
|
111
|
+
<path d="M17 7.5a1.5 1.5 0 0 1 3 0v8.5a6 6 0 0 1 -6 6h-2h.208a6 6 0 0 1 -5.012 -2.7a69.74 69.74 0 0 1 -.196 -.3c-.312 -.479 -1.407 -2.388 -3.286 -5.728a1.5 1.5 0 0 1 .536 -2.022a1.867 1.867 0 0 1 2.28 .28l1.47 1.47" />
|
|
110
112
|
</svg>
|
|
113
|
+
<BubbleTooltipAbove>Hand <span className="ml-1 text-white/55">H</span></BubbleTooltipAbove>
|
|
111
114
|
</button>
|
|
112
115
|
|
|
113
116
|
{/* Divider */}
|
|
114
|
-
<div className="w-px h-5 bg-white/
|
|
117
|
+
<div className="w-px h-5 bg-white/15 mx-2" />
|
|
115
118
|
|
|
116
119
|
{/* Zoom out */}
|
|
117
120
|
<button
|
|
118
121
|
onClick={handleZoomOut}
|
|
119
|
-
className="flex items-center justify-center w-7 h-8 text-neutral-400 hover:text-white transition-colors text-sm"
|
|
120
|
-
title="Zoom out (Ctrl + −)"
|
|
122
|
+
className="group/bb relative flex items-center justify-center w-7 h-8 text-neutral-400 hover:text-white transition-colors text-sm"
|
|
121
123
|
aria-label="Zoom out"
|
|
122
124
|
>
|
|
123
125
|
−
|
|
126
|
+
<BubbleTooltipAbove>Zoom out <span className="ml-1 text-white/55">Ctrl −</span></BubbleTooltipAbove>
|
|
124
127
|
</button>
|
|
125
128
|
|
|
126
129
|
{/* Zoom percentage (clickable dropdown) */}
|
|
127
130
|
<div className="relative" ref={presetsRef}>
|
|
128
131
|
<button
|
|
129
132
|
onClick={() => setShowPresets((v) => !v)}
|
|
130
|
-
className="flex items-center justify-center min-w-[48px] h-8 text-white text-[11px] font-medium hover:bg-white/10 rounded-md transition-colors px-1"
|
|
131
|
-
title="Click to select zoom level"
|
|
133
|
+
className="group/bb relative flex items-center justify-center min-w-[48px] h-8 text-white text-[11px] font-medium hover:bg-white/10 rounded-md transition-colors px-1"
|
|
132
134
|
aria-label="Select zoom level"
|
|
133
135
|
>
|
|
134
136
|
{zoomPercent}%
|
|
137
|
+
<BubbleTooltipAbove>Zoom level</BubbleTooltipAbove>
|
|
135
138
|
</button>
|
|
136
139
|
|
|
137
140
|
{showPresets && (
|
|
@@ -156,40 +159,27 @@ export default function CanvasToolbar({ viewportRef, onAnimatedAction }: CanvasT
|
|
|
156
159
|
{/* Zoom in */}
|
|
157
160
|
<button
|
|
158
161
|
onClick={handleZoomIn}
|
|
159
|
-
className="flex items-center justify-center w-7 h-8 text-neutral-400 hover:text-white transition-colors text-sm"
|
|
160
|
-
title="Zoom in (Ctrl + +)"
|
|
162
|
+
className="group/bb relative flex items-center justify-center w-7 h-8 text-neutral-400 hover:text-white transition-colors text-sm"
|
|
161
163
|
aria-label="Zoom in"
|
|
162
164
|
>
|
|
163
165
|
+
|
|
166
|
+
<BubbleTooltipAbove>Zoom in <span className="ml-1 text-white/55">Ctrl +</span></BubbleTooltipAbove>
|
|
164
167
|
</button>
|
|
165
168
|
|
|
166
169
|
{/* Divider */}
|
|
167
|
-
<div className="w-px h-5 bg-white/
|
|
170
|
+
<div className="w-px h-5 bg-white/15 mx-2" />
|
|
168
171
|
|
|
169
|
-
{/* Fit */}
|
|
172
|
+
{/* Fit — tabler zoom (magnifying glass, outline) */}
|
|
170
173
|
<button
|
|
171
174
|
onClick={handleFit}
|
|
172
|
-
className="flex items-center justify-center w-8 h-8 rounded-full text-neutral-400 hover:text-white hover:bg-white/10 transition-colors"
|
|
173
|
-
title="Zoom to fit (Ctrl + 0)"
|
|
175
|
+
className="group/bb relative flex items-center justify-center w-8 h-8 rounded-full text-neutral-400 hover:text-white hover:bg-white/10 transition-colors"
|
|
174
176
|
aria-label="Zoom to fit"
|
|
175
177
|
>
|
|
176
|
-
<svg width="
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
y="2"
|
|
180
|
-
width="10"
|
|
181
|
-
height="10"
|
|
182
|
-
rx="1.5"
|
|
183
|
-
stroke="currentColor"
|
|
184
|
-
strokeWidth="1.5"
|
|
185
|
-
/>
|
|
186
|
-
<path
|
|
187
|
-
d="M5 2V0M9 2V0M5 14V12M9 14V12M0 5H2M0 9H2M12 5H14M12 9H14"
|
|
188
|
-
stroke="currentColor"
|
|
189
|
-
strokeWidth="1"
|
|
190
|
-
opacity="0.5"
|
|
191
|
-
/>
|
|
178
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
179
|
+
<path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />
|
|
180
|
+
<path d="M21 21l-6 -6" />
|
|
192
181
|
</svg>
|
|
182
|
+
<BubbleTooltipAbove>Zoom to fit <span className="ml-1 text-white/55">Ctrl 0</span></BubbleTooltipAbove>
|
|
193
183
|
</button>
|
|
194
184
|
</div>
|
|
195
185
|
);
|