@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
|
@@ -100,7 +100,7 @@ export function BrandingStep({ onNext, onBack }: WizardStepProps) {
|
|
|
100
100
|
}, [siteTitle, onNext]);
|
|
101
101
|
|
|
102
102
|
const inputClass =
|
|
103
|
-
"w-full rounded-lg border border-black/[0.08] bg-white px-3 py-2.5 text-sm text-[#333] placeholder:text-[#bbb] focus:outline-none focus:ring-2 focus:ring-[#
|
|
103
|
+
"w-full rounded-lg border border-black/[0.08] bg-white px-3 py-2.5 text-sm text-[#333] placeholder:text-[#bbb] focus:outline-none focus:ring-2 focus:ring-[#3580f9]/20 focus:border-[#3580f9]/40";
|
|
104
104
|
|
|
105
105
|
if (loading) {
|
|
106
106
|
return (
|
|
@@ -196,7 +196,7 @@ export function BrandingStep({ onNext, onBack }: WizardStepProps) {
|
|
|
196
196
|
<button
|
|
197
197
|
onClick={handleSave}
|
|
198
198
|
disabled={saving || !siteTitle.trim()}
|
|
199
|
-
className="px-5 py-2.5 bg-[#
|
|
199
|
+
className="px-5 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors disabled:opacity-50"
|
|
200
200
|
>
|
|
201
201
|
{saving ? "Saving..." : "Save & Continue"}
|
|
202
202
|
</button>
|
|
@@ -206,7 +206,7 @@ export function BrandingStep({ onNext, onBack }: WizardStepProps) {
|
|
|
206
206
|
{saved && (
|
|
207
207
|
<button
|
|
208
208
|
onClick={onNext}
|
|
209
|
-
className="px-5 py-2.5 bg-[#
|
|
209
|
+
className="px-5 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors"
|
|
210
210
|
>
|
|
211
211
|
Next
|
|
212
212
|
</button>
|
|
@@ -309,7 +309,7 @@ export function DatabaseStep({ onNext, onBack }: WizardStepProps) {
|
|
|
309
309
|
<button
|
|
310
310
|
onClick={seedDocuments}
|
|
311
311
|
disabled={seedState === "seeding"}
|
|
312
|
-
className="px-4 py-2 text-sm bg-[#
|
|
312
|
+
className="px-4 py-2 text-sm bg-[#3580f9] text-white rounded-lg hover:bg-[#2d6dd4] transition-colors disabled:opacity-50"
|
|
313
313
|
>
|
|
314
314
|
{seedState === "seeding" ? "Creating..." : "Create Initial Documents"}
|
|
315
315
|
</button>
|
|
@@ -319,7 +319,7 @@ export function DatabaseStep({ onNext, onBack }: WizardStepProps) {
|
|
|
319
319
|
{canAdvance && (
|
|
320
320
|
<button
|
|
321
321
|
onClick={onNext}
|
|
322
|
-
className="px-5 py-2.5 bg-[#
|
|
322
|
+
className="px-5 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors"
|
|
323
323
|
>
|
|
324
324
|
Next
|
|
325
325
|
</button>
|
|
@@ -177,7 +177,7 @@ export function DoneStep({ onBack }: WizardStepProps) {
|
|
|
177
177
|
<button
|
|
178
178
|
onClick={handleComplete}
|
|
179
179
|
disabled={completing}
|
|
180
|
-
className="px-6 py-2.5 bg-[#
|
|
180
|
+
className="px-6 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors disabled:opacity-50"
|
|
181
181
|
>
|
|
182
182
|
{completing ? "Starting..." : "Start Building"}
|
|
183
183
|
</button>
|
|
@@ -105,9 +105,9 @@ export function SetupWizard({ initialStatus }: SetupWizardProps) {
|
|
|
105
105
|
<div
|
|
106
106
|
className={`w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-semibold transition-colors ${
|
|
107
107
|
isActive
|
|
108
|
-
? "bg-[#
|
|
108
|
+
? "bg-[#3580f9] text-white"
|
|
109
109
|
: isDone
|
|
110
|
-
? "bg-[#
|
|
110
|
+
? "bg-[#3580f9]/20 text-[#3580f9]"
|
|
111
111
|
: "bg-black/[0.06] text-[#999]"
|
|
112
112
|
}`}
|
|
113
113
|
>
|
|
@@ -124,7 +124,7 @@ export function SetupWizard({ initialStatus }: SetupWizardProps) {
|
|
|
124
124
|
isActive
|
|
125
125
|
? "text-[#333] font-medium"
|
|
126
126
|
: isDone
|
|
127
|
-
? "text-[#
|
|
127
|
+
? "text-[#3580f9]"
|
|
128
128
|
: "text-[#999]"
|
|
129
129
|
}`}
|
|
130
130
|
>
|
|
@@ -135,7 +135,7 @@ export function SetupWizard({ initialStatus }: SetupWizardProps) {
|
|
|
135
135
|
{i < STEPS.length - 1 && (
|
|
136
136
|
<div
|
|
137
137
|
className={`w-8 h-px transition-colors ${
|
|
138
|
-
i < currentStep ? "bg-[#
|
|
138
|
+
i < currentStep ? "bg-[#3580f9]/30" : "bg-black/[0.08]"
|
|
139
139
|
}`}
|
|
140
140
|
/>
|
|
141
141
|
)}
|
|
@@ -128,7 +128,7 @@ export function StorageStep({ onNext, onBack }: WizardStepProps) {
|
|
|
128
128
|
);
|
|
129
129
|
|
|
130
130
|
const inputClass =
|
|
131
|
-
"w-full rounded-lg border border-black/[0.08] bg-white px-3 py-2 text-sm text-[#333] placeholder:text-[#bbb] focus:outline-none focus:ring-2 focus:ring-[#
|
|
131
|
+
"w-full rounded-lg border border-black/[0.08] bg-white px-3 py-2 text-sm text-[#333] placeholder:text-[#bbb] focus:outline-none focus:ring-2 focus:ring-[#3580f9]/20 focus:border-[#3580f9]/40";
|
|
132
132
|
const labelClass = "block text-xs font-medium text-[#666] mb-1";
|
|
133
133
|
|
|
134
134
|
if (state === "loading") {
|
|
@@ -296,7 +296,7 @@ export function StorageStep({ onNext, onBack }: WizardStepProps) {
|
|
|
296
296
|
{state === "connected" && (
|
|
297
297
|
<button
|
|
298
298
|
onClick={onNext}
|
|
299
|
-
className="px-5 py-2.5 bg-[#
|
|
299
|
+
className="px-5 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors"
|
|
300
300
|
>
|
|
301
301
|
Next
|
|
302
302
|
</button>
|
|
@@ -75,7 +75,7 @@ export function WelcomeStep({ onNext }: WizardStepProps) {
|
|
|
75
75
|
},
|
|
76
76
|
].map((item) => (
|
|
77
77
|
<li key={item.label} className="flex items-start gap-3">
|
|
78
|
-
<span className="text-[#
|
|
78
|
+
<span className="text-[#3580f9] mt-0.5 shrink-0">{item.icon}</span>
|
|
79
79
|
<div>
|
|
80
80
|
<span className="text-sm font-medium text-[#333]">{item.label}</span>
|
|
81
81
|
<p className="text-xs text-[#999] mt-0.5">{item.desc}</p>
|
|
@@ -87,7 +87,7 @@ export function WelcomeStep({ onNext }: WizardStepProps) {
|
|
|
87
87
|
|
|
88
88
|
<button
|
|
89
89
|
onClick={onNext}
|
|
90
|
-
className="px-6 py-2.5 bg-[#
|
|
90
|
+
className="px-6 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors"
|
|
91
91
|
>
|
|
92
92
|
Get Started
|
|
93
93
|
</button>
|
|
@@ -5,6 +5,7 @@ import ColorPicker, { isValidHex } from "../../../components/builder/ColorPicker
|
|
|
5
5
|
import { invalidatePaletteCache } from "../../../components/builder/ColorSwatchPicker";
|
|
6
6
|
import type { SiteStyles, ColorSwatch } from "../../../lib/sanity/types";
|
|
7
7
|
import { Section, SaveButton } from "./shared";
|
|
8
|
+
import { BubbleTooltip } from "../../builder/BubbleIcons";
|
|
8
9
|
|
|
9
10
|
function getContrastColor(hex: string): string {
|
|
10
11
|
if (!isValidHex(hex)) return "#000";
|
|
@@ -119,14 +120,14 @@ export function ColorsEditor({
|
|
|
119
120
|
<div className="absolute inset-0 bg-black/15 flex items-center justify-center gap-1.5">
|
|
120
121
|
<button
|
|
121
122
|
onClick={(e) => { e.stopPropagation(); setEditingIndex(i); setPickerOpen(true); }}
|
|
122
|
-
className="w-6 h-6 rounded-full bg-white/90 flex items-center justify-center text-[11px] cursor-pointer hover:bg-white transition-colors border-none"
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
className="group/bb relative w-6 h-6 rounded-full bg-white/90 flex items-center justify-center text-[11px] cursor-pointer hover:bg-white transition-colors border-none"
|
|
124
|
+
aria-label="Edit color"
|
|
125
|
+
>✎<BubbleTooltip>Edit color</BubbleTooltip></button>
|
|
125
126
|
<button
|
|
126
127
|
onClick={(e) => { e.stopPropagation(); removeSwatch(i); }}
|
|
127
|
-
className="w-6 h-6 rounded-full bg-white/90 flex items-center justify-center text-[13px] text-red-500 cursor-pointer hover:bg-white transition-colors border-none"
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
className="group/bb relative w-6 h-6 rounded-full bg-white/90 flex items-center justify-center text-[13px] text-red-500 cursor-pointer hover:bg-white transition-colors border-none"
|
|
129
|
+
aria-label="Remove"
|
|
130
|
+
>×<BubbleTooltip>Remove</BubbleTooltip></button>
|
|
130
131
|
</div>
|
|
131
132
|
)}
|
|
132
133
|
</div>
|
|
@@ -156,7 +157,7 @@ export function ColorsEditor({
|
|
|
156
157
|
{!pickerOpen && (
|
|
157
158
|
<button
|
|
158
159
|
onClick={() => { setEditingIndex(null); setPickerOpen(true); }}
|
|
159
|
-
className="rounded-xl border-2 border-dashed border-neutral-200 h-[100px] flex flex-col items-center justify-center gap-1 cursor-pointer hover:border-[#
|
|
160
|
+
className="rounded-xl border-2 border-dashed border-neutral-200 h-[100px] flex flex-col items-center justify-center gap-1 cursor-pointer hover:border-[#3580f9] hover:text-[#3580f9] text-neutral-400 transition-colors bg-transparent"
|
|
160
161
|
>
|
|
161
162
|
<span className="text-xl leading-none">+</span>
|
|
162
163
|
<span className="text-[10px] uppercase tracking-wider">Add</span>
|
|
@@ -190,7 +191,7 @@ export function ColorsEditor({
|
|
|
190
191
|
{pickerOpen && (
|
|
191
192
|
<div className="flex justify-center mb-4">
|
|
192
193
|
<ColorPicker
|
|
193
|
-
color={editingIndex !== null ? swatches[editingIndex].hex : "#
|
|
194
|
+
color={editingIndex !== null ? swatches[editingIndex].hex : "#3580f9"}
|
|
194
195
|
onChange={addSwatch}
|
|
195
196
|
onClose={() => { setPickerOpen(false); setEditingIndex(null); }}
|
|
196
197
|
confirmLabel={editingIndex !== null ? "Update color" : "Add to palette"}
|
|
@@ -4,6 +4,7 @@ import { useState, useEffect, useRef } from "react";
|
|
|
4
4
|
import { csrfHeaders } from "../../../lib/csrf-client";
|
|
5
5
|
import type { FontFamily, FontVariant } from "../../../lib/sanity/types";
|
|
6
6
|
import { Section, SaveButton } from "./shared";
|
|
7
|
+
import { BubbleTooltip } from "../../builder/BubbleIcons";
|
|
7
8
|
|
|
8
9
|
export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; onSave: (fonts: FontFamily[]) => void; saving: boolean }) {
|
|
9
10
|
const [localFonts, setLocalFonts] = useState<FontFamily[]>(fonts);
|
|
@@ -115,7 +116,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
|
|
|
115
116
|
key={font._key}
|
|
116
117
|
className={`rounded-xl border overflow-hidden transition-all cursor-pointer ${
|
|
117
118
|
isExpanded
|
|
118
|
-
? "border-[#
|
|
119
|
+
? "border-[#3580f9] shadow-sm ring-1 ring-[#3580f9]/20"
|
|
119
120
|
: "border-neutral-200 hover:shadow-md hover:-translate-y-0.5"
|
|
120
121
|
}`}
|
|
121
122
|
onClick={() => setExpandedFont(isExpanded ? null : font._key)}
|
|
@@ -147,7 +148,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
|
|
|
147
148
|
{/* Add font card */}
|
|
148
149
|
<button
|
|
149
150
|
onClick={addFont}
|
|
150
|
-
className="rounded-xl border-2 border-dashed border-neutral-200 min-h-[120px] flex flex-col items-center justify-center gap-1.5 cursor-pointer hover:border-[#
|
|
151
|
+
className="rounded-xl border-2 border-dashed border-neutral-200 min-h-[120px] flex flex-col items-center justify-center gap-1.5 cursor-pointer hover:border-[#3580f9] hover:text-[#3580f9] text-neutral-400 transition-colors bg-transparent"
|
|
151
152
|
>
|
|
152
153
|
<span className="text-xl leading-none">+</span>
|
|
153
154
|
<span className="text-[10px] uppercase tracking-wider">Add Font Family</span>
|
|
@@ -166,7 +167,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
|
|
|
166
167
|
value={font.family}
|
|
167
168
|
onChange={(e) => updateFont(font._key, { family: e.target.value })}
|
|
168
169
|
placeholder="Font Family Name (e.g. Inter)"
|
|
169
|
-
className="flex-1 rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm text-neutral-900 focus:border-[#
|
|
170
|
+
className="flex-1 rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
|
|
170
171
|
disabled={font.is_builtin}
|
|
171
172
|
onClick={(e) => e.stopPropagation()}
|
|
172
173
|
/>
|
|
@@ -190,7 +191,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
|
|
|
190
191
|
<select
|
|
191
192
|
value={v.weight}
|
|
192
193
|
onChange={(e) => updateVariant(font._key, v._key, { weight: e.target.value })}
|
|
193
|
-
className="rounded border border-neutral-200 bg-white px-2 py-1 text-xs focus:border-[#
|
|
194
|
+
className="rounded border border-neutral-200 bg-white px-2 py-1 text-xs focus:border-[#3580f9] focus:outline-none"
|
|
194
195
|
>
|
|
195
196
|
{["100", "300", "400", "500", "600", "700", "800", "900"].map((w) => (
|
|
196
197
|
<option key={w} value={w}>{w}</option>
|
|
@@ -199,13 +200,14 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
|
|
|
199
200
|
<select
|
|
200
201
|
value={v.style}
|
|
201
202
|
onChange={(e) => updateVariant(font._key, v._key, { style: e.target.value as "normal" | "italic" })}
|
|
202
|
-
className="rounded border border-neutral-200 bg-white px-2 py-1 text-xs focus:border-[#
|
|
203
|
+
className="rounded border border-neutral-200 bg-white px-2 py-1 text-xs focus:border-[#3580f9] focus:outline-none"
|
|
203
204
|
>
|
|
204
205
|
<option value="normal">Normal</option>
|
|
205
206
|
<option value="italic">Italic</option>
|
|
206
207
|
</select>
|
|
207
|
-
<span className="text-neutral-400 truncate flex-1"
|
|
208
|
+
<span className="group/bb relative text-neutral-400 truncate flex-1" aria-label={v.original_filename}>
|
|
208
209
|
{v.original_filename}
|
|
210
|
+
<BubbleTooltip>{v.original_filename}</BubbleTooltip>
|
|
209
211
|
</span>
|
|
210
212
|
{!font.is_builtin && (
|
|
211
213
|
<button
|
|
@@ -225,7 +227,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
|
|
|
225
227
|
fileInputRef.current?.click();
|
|
226
228
|
}}
|
|
227
229
|
disabled={uploading}
|
|
228
|
-
className="text-xs text-[#
|
|
230
|
+
className="text-xs text-[#3580f9] hover:text-[#2d6dd4] transition-colors mt-1"
|
|
229
231
|
>
|
|
230
232
|
{uploading && uploadTarget === font._key ? "Uploading..." : "+ Add variant file"}
|
|
231
233
|
</button>
|
|
@@ -15,7 +15,7 @@ const DEFAULT_GRID = {
|
|
|
15
15
|
|
|
16
16
|
/** SVG illustration for the Column Gutter card */
|
|
17
17
|
function GutterIcon() {
|
|
18
|
-
const ACCENT = "#
|
|
18
|
+
const ACCENT = "#3580f9";
|
|
19
19
|
return (
|
|
20
20
|
<svg viewBox="0 0 120 72" fill="none" className="w-full h-full">
|
|
21
21
|
{/* 5 columns (ghost blue) */}
|
|
@@ -44,7 +44,7 @@ function GutterIcon() {
|
|
|
44
44
|
/** SVG illustration for the Max Page Width card */
|
|
45
45
|
function MaxWidthIcon() {
|
|
46
46
|
// Frame/dots/content stay neutral; arrows + dashed boundaries are the accent.
|
|
47
|
-
const ACCENT = "#
|
|
47
|
+
const ACCENT = "#3580f9";
|
|
48
48
|
return (
|
|
49
49
|
<svg viewBox="0 0 120 72" fill="none" className="w-full h-full">
|
|
50
50
|
{/* Browser frame */}
|
|
@@ -73,7 +73,7 @@ function MaxWidthIcon() {
|
|
|
73
73
|
/** SVG illustration for the Scroll Animations card */
|
|
74
74
|
function ScrollAnimIcon() {
|
|
75
75
|
// 3 blue layers with increasing opacity convey the fade-in effect; accent arrow below.
|
|
76
|
-
const ACCENT = "#
|
|
76
|
+
const ACCENT = "#3580f9";
|
|
77
77
|
return (
|
|
78
78
|
<svg viewBox="0 0 120 72" fill="none" className="w-full h-full">
|
|
79
79
|
{/* 3 stacked layers (fade-in effect) */}
|
|
@@ -169,7 +169,7 @@ export function GridLayoutEditor({
|
|
|
169
169
|
type="text"
|
|
170
170
|
value={local.grid_gutter_desktop}
|
|
171
171
|
onChange={(e) => setLocal({ ...local, grid_gutter_desktop: e.target.value })}
|
|
172
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#
|
|
172
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
|
|
173
173
|
placeholder="30"
|
|
174
174
|
/>
|
|
175
175
|
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
|
|
@@ -187,7 +187,7 @@ export function GridLayoutEditor({
|
|
|
187
187
|
type="text"
|
|
188
188
|
value={local.grid_width}
|
|
189
189
|
onChange={(e) => setLocal({ ...local, grid_width: e.target.value })}
|
|
190
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#
|
|
190
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
|
|
191
191
|
placeholder={DEFAULT_GRID_WIDTH}
|
|
192
192
|
/>
|
|
193
193
|
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
|
|
@@ -206,7 +206,7 @@ export function GridLayoutEditor({
|
|
|
206
206
|
type="button"
|
|
207
207
|
onClick={() => setAnimLocal(!animLocal)}
|
|
208
208
|
className={`relative w-9 h-5 rounded-full transition-colors shrink-0 ${
|
|
209
|
-
animLocal ? "bg-[#
|
|
209
|
+
animLocal ? "bg-[#3580f9]" : "bg-neutral-300"
|
|
210
210
|
}`}
|
|
211
211
|
>
|
|
212
212
|
<span
|
|
@@ -234,7 +234,7 @@ export function GridLayoutEditor({
|
|
|
234
234
|
type="text"
|
|
235
235
|
value={local.grid_gutter_desktop}
|
|
236
236
|
onChange={(e) => setLocal({ ...local, grid_gutter_desktop: e.target.value })}
|
|
237
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#
|
|
237
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
|
|
238
238
|
placeholder="30"
|
|
239
239
|
/>
|
|
240
240
|
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
|
|
@@ -253,7 +253,7 @@ export function GridLayoutEditor({
|
|
|
253
253
|
type="text"
|
|
254
254
|
value={local.grid_gutter_responsive}
|
|
255
255
|
onChange={(e) => setLocal({ ...local, grid_gutter_responsive: e.target.value })}
|
|
256
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#
|
|
256
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
|
|
257
257
|
placeholder="30"
|
|
258
258
|
/>
|
|
259
259
|
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
|
|
@@ -272,7 +272,7 @@ export function GridLayoutEditor({
|
|
|
272
272
|
type="text"
|
|
273
273
|
value={local.grid_gutter_phone || "16"}
|
|
274
274
|
onChange={(e) => setLocal({ ...local, grid_gutter_phone: e.target.value })}
|
|
275
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#
|
|
275
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
|
|
276
276
|
placeholder="16"
|
|
277
277
|
/>
|
|
278
278
|
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
|
|
@@ -16,8 +16,8 @@ export function LinksButtonsEditor({
|
|
|
16
16
|
saving: boolean;
|
|
17
17
|
}) {
|
|
18
18
|
const [local, setLocal] = useState({
|
|
19
|
-
link_color: linkStyle?.color || "#
|
|
20
|
-
link_hover_color: linkStyle?.hover_color || "#
|
|
19
|
+
link_color: linkStyle?.color || "#3580f9",
|
|
20
|
+
link_hover_color: linkStyle?.hover_color || "#2d6dd4",
|
|
21
21
|
link_underline: linkStyle?.underline ?? true,
|
|
22
22
|
button_primary_bg: buttonStyle?.primary_bg || "#ffffff",
|
|
23
23
|
button_primary_text: buttonStyle?.primary_text || "#000000",
|
|
@@ -28,8 +28,8 @@ export function LinksButtonsEditor({
|
|
|
28
28
|
|
|
29
29
|
useEffect(() => {
|
|
30
30
|
setLocal({
|
|
31
|
-
link_color: linkStyle?.color || "#
|
|
32
|
-
link_hover_color: linkStyle?.hover_color || "#
|
|
31
|
+
link_color: linkStyle?.color || "#3580f9",
|
|
32
|
+
link_hover_color: linkStyle?.hover_color || "#2d6dd4",
|
|
33
33
|
link_underline: linkStyle?.underline ?? true,
|
|
34
34
|
button_primary_bg: buttonStyle?.primary_bg || "#ffffff",
|
|
35
35
|
button_primary_text: buttonStyle?.primary_text || "#000000",
|
|
@@ -51,7 +51,7 @@ export function LinksButtonsEditor({
|
|
|
51
51
|
type="checkbox"
|
|
52
52
|
checked={local.link_underline}
|
|
53
53
|
onChange={(e) => setLocal({ ...local, link_underline: e.target.checked })}
|
|
54
|
-
className="accent-[#
|
|
54
|
+
className="accent-[#3580f9]"
|
|
55
55
|
/>
|
|
56
56
|
<span className="text-xs text-neutral-700">Underline links</span>
|
|
57
57
|
</label>
|
|
@@ -179,7 +179,7 @@ export function TypographyEditor({
|
|
|
179
179
|
<select
|
|
180
180
|
value={t.font_family || ""}
|
|
181
181
|
onChange={(e) => updateLevel(level, { font_family: e.target.value })}
|
|
182
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#
|
|
182
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
|
|
183
183
|
>
|
|
184
184
|
<option value="">Inherit</option>
|
|
185
185
|
{fontFamilies.map((f) => (
|
|
@@ -193,7 +193,7 @@ export function TypographyEditor({
|
|
|
193
193
|
type="text"
|
|
194
194
|
value={t.font_size}
|
|
195
195
|
onChange={(e) => updateLevel(level, { font_size: e.target.value })}
|
|
196
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#
|
|
196
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
|
|
197
197
|
placeholder="3rem"
|
|
198
198
|
/>
|
|
199
199
|
</div>
|
|
@@ -202,7 +202,7 @@ export function TypographyEditor({
|
|
|
202
202
|
<select
|
|
203
203
|
value={t.font_weight}
|
|
204
204
|
onChange={(e) => updateLevel(level, { font_weight: e.target.value })}
|
|
205
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#
|
|
205
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
|
|
206
206
|
>
|
|
207
207
|
{["100", "300", "400", "500", "600", "700", "800", "900"].map((w) => (
|
|
208
208
|
<option key={w} value={w}>{w}</option>
|
|
@@ -215,7 +215,7 @@ export function TypographyEditor({
|
|
|
215
215
|
type="text"
|
|
216
216
|
value={t.line_height}
|
|
217
217
|
onChange={(e) => updateLevel(level, { line_height: e.target.value })}
|
|
218
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#
|
|
218
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
|
|
219
219
|
placeholder="1.1"
|
|
220
220
|
/>
|
|
221
221
|
</div>
|
|
@@ -225,7 +225,7 @@ export function TypographyEditor({
|
|
|
225
225
|
type="text"
|
|
226
226
|
value={t.letter_spacing}
|
|
227
227
|
onChange={(e) => updateLevel(level, { letter_spacing: e.target.value })}
|
|
228
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#
|
|
228
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
|
|
229
229
|
placeholder="-0.02em"
|
|
230
230
|
/>
|
|
231
231
|
</div>
|
|
@@ -234,7 +234,7 @@ export function TypographyEditor({
|
|
|
234
234
|
<select
|
|
235
235
|
value={t.text_transform || "none"}
|
|
236
236
|
onChange={(e) => updateLevel(level, { text_transform: e.target.value as TypographyLevel["text_transform"] })}
|
|
237
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#
|
|
237
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
|
|
238
238
|
>
|
|
239
239
|
<option value="none">None</option>
|
|
240
240
|
<option value="uppercase">UPPERCASE</option>
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
export function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) {
|
|
4
|
-
return (
|
|
5
|
-
<section className="bg-white rounded-2xl border border-neutral-200 p-6">
|
|
6
|
-
<h2 className="text-lg font-semibold text-neutral-900 mb-1">{title}</h2>
|
|
7
|
-
{description && <p className="text-xs text-neutral-500 mb-5">{description}</p>}
|
|
8
|
-
{!description && <div className="mb-5" />}
|
|
9
|
-
{children}
|
|
10
|
-
</section>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function ColorField({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) {
|
|
15
|
-
return (
|
|
16
|
-
<div className="flex items-center gap-3">
|
|
17
|
-
<input
|
|
18
|
-
type="color"
|
|
19
|
-
value={value}
|
|
20
|
-
onChange={(e) => onChange(e.target.value)}
|
|
21
|
-
className="w-8 h-8 rounded-lg border border-neutral-200 cursor-pointer bg-transparent [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:rounded-md [&::-webkit-color-swatch]:border-none"
|
|
22
|
-
/>
|
|
23
|
-
<div className="flex-1">
|
|
24
|
-
<label className="text-xs text-neutral-500 block mb-0.5">{label}</label>
|
|
25
|
-
<input
|
|
26
|
-
type="text"
|
|
27
|
-
value={value}
|
|
28
|
-
onChange={(e) => onChange(e.target.value)}
|
|
29
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1 text-xs text-neutral-900 focus:border-[#
|
|
30
|
-
/>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function SaveButton({ onClick, saving, label = "Save" }: { onClick: () => void; saving: boolean; label?: string }) {
|
|
37
|
-
return (
|
|
38
|
-
<button
|
|
39
|
-
onClick={onClick}
|
|
40
|
-
disabled={saving}
|
|
41
|
-
className="rounded-lg bg-[#
|
|
42
|
-
>
|
|
43
|
-
{saving ? "Saving..." : label}
|
|
44
|
-
</button>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function FieldInput({ label, value, onChange, placeholder, helpText }: {
|
|
49
|
-
label: string;
|
|
50
|
-
value: string;
|
|
51
|
-
onChange: (v: string) => void;
|
|
52
|
-
placeholder?: string;
|
|
53
|
-
helpText?: string;
|
|
54
|
-
}) {
|
|
55
|
-
return (
|
|
56
|
-
<div>
|
|
57
|
-
<label className="text-[10px] text-neutral-400 uppercase tracking-wider block mb-1">{label}</label>
|
|
58
|
-
<input
|
|
59
|
-
type="text"
|
|
60
|
-
value={value}
|
|
61
|
-
onChange={(e) => onChange(e.target.value)}
|
|
62
|
-
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-neutral-900 focus:border-[#
|
|
63
|
-
placeholder={placeholder}
|
|
64
|
-
/>
|
|
65
|
-
{helpText && <p className="text-[10px] text-neutral-400 mt-1">{helpText}</p>}
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) {
|
|
4
|
+
return (
|
|
5
|
+
<section className="bg-white rounded-2xl border border-neutral-200 p-6">
|
|
6
|
+
<h2 className="text-lg font-semibold text-neutral-900 mb-1">{title}</h2>
|
|
7
|
+
{description && <p className="text-xs text-neutral-500 mb-5">{description}</p>}
|
|
8
|
+
{!description && <div className="mb-5" />}
|
|
9
|
+
{children}
|
|
10
|
+
</section>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ColorField({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex items-center gap-3">
|
|
17
|
+
<input
|
|
18
|
+
type="color"
|
|
19
|
+
value={value}
|
|
20
|
+
onChange={(e) => onChange(e.target.value)}
|
|
21
|
+
className="w-8 h-8 rounded-lg border border-neutral-200 cursor-pointer bg-transparent [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:rounded-md [&::-webkit-color-swatch]:border-none"
|
|
22
|
+
/>
|
|
23
|
+
<div className="flex-1">
|
|
24
|
+
<label className="text-xs text-neutral-500 block mb-0.5">{label}</label>
|
|
25
|
+
<input
|
|
26
|
+
type="text"
|
|
27
|
+
value={value}
|
|
28
|
+
onChange={(e) => onChange(e.target.value)}
|
|
29
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1 text-xs text-neutral-900 focus:border-[#3580f9] focus:outline-none"
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function SaveButton({ onClick, saving, label = "Save" }: { onClick: () => void; saving: boolean; label?: string }) {
|
|
37
|
+
return (
|
|
38
|
+
<button
|
|
39
|
+
onClick={onClick}
|
|
40
|
+
disabled={saving}
|
|
41
|
+
className="rounded-lg bg-[#3580f9] px-5 py-1.5 text-sm font-medium text-white hover:bg-[#2d6dd4] transition-colors disabled:opacity-50"
|
|
42
|
+
>
|
|
43
|
+
{saving ? "Saving..." : label}
|
|
44
|
+
</button>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function FieldInput({ label, value, onChange, placeholder, helpText }: {
|
|
49
|
+
label: string;
|
|
50
|
+
value: string;
|
|
51
|
+
onChange: (v: string) => void;
|
|
52
|
+
placeholder?: string;
|
|
53
|
+
helpText?: string;
|
|
54
|
+
}) {
|
|
55
|
+
return (
|
|
56
|
+
<div>
|
|
57
|
+
<label className="text-[10px] text-neutral-400 uppercase tracking-wider block mb-1">{label}</label>
|
|
58
|
+
<input
|
|
59
|
+
type="text"
|
|
60
|
+
value={value}
|
|
61
|
+
onChange={(e) => onChange(e.target.value)}
|
|
62
|
+
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
|
|
63
|
+
placeholder={placeholder}
|
|
64
|
+
/>
|
|
65
|
+
{helpText && <p className="text-[10px] text-neutral-400 mt-1">{helpText}</p>}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|