@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,266 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import type { SiteStyles, TypographyLevel } from "../../../lib/sanity/types";
|
|
5
|
+
import { Section, SaveButton } from "./shared";
|
|
6
|
+
|
|
7
|
+
const TYPO_SAMPLE_TEXTS: Record<string, string> = {
|
|
8
|
+
h1: "The quick brown fox",
|
|
9
|
+
h2: "Jumps over the lazy dog",
|
|
10
|
+
h3: "Pack my box with five dozen",
|
|
11
|
+
h4: "How vexingly quick daft zebras jump",
|
|
12
|
+
body: "Apparently we had reached a great height in the atmosphere, for the sky was a dead black, and the stars had ceased to twinkle.",
|
|
13
|
+
small: "Value your time — nothing is impossible",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const TYPO_LEVEL_LABELS: Record<string, string> = {
|
|
17
|
+
h1: "Heading 1",
|
|
18
|
+
h2: "Heading 2",
|
|
19
|
+
h3: "Heading 3",
|
|
20
|
+
h4: "Heading 4",
|
|
21
|
+
body: "Body",
|
|
22
|
+
small: "Small",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function ChevronDownIcon({ open }: { open: boolean }) {
|
|
26
|
+
return (
|
|
27
|
+
<svg
|
|
28
|
+
width="14"
|
|
29
|
+
height="14"
|
|
30
|
+
viewBox="0 0 24 24"
|
|
31
|
+
fill="none"
|
|
32
|
+
stroke="currentColor"
|
|
33
|
+
strokeWidth="2"
|
|
34
|
+
className="transition-transform duration-200"
|
|
35
|
+
style={{ transform: open ? "rotate(180deg)" : "rotate(0deg)" }}
|
|
36
|
+
>
|
|
37
|
+
<polyline points="6 9 12 15 18 9" />
|
|
38
|
+
</svg>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_TYPOGRAPHY: Record<string, TypographyLevel> = {
|
|
43
|
+
h1: { font_size: "3rem", font_weight: "700", line_height: "1.1", letter_spacing: "-0.02em" },
|
|
44
|
+
h2: { font_size: "2rem", font_weight: "700", line_height: "1.2", letter_spacing: "-0.01em" },
|
|
45
|
+
h3: { font_size: "1.5rem", font_weight: "500", line_height: "1.3", letter_spacing: "0" },
|
|
46
|
+
h4: { font_size: "1.125rem", font_weight: "500", line_height: "1.4", letter_spacing: "0" },
|
|
47
|
+
body: { font_size: "0.875rem", font_weight: "400", line_height: "1.6", letter_spacing: "0" },
|
|
48
|
+
small: { font_size: "0.75rem", font_weight: "400", line_height: "1.5", letter_spacing: "0.02em" },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export function TypographyEditor({
|
|
52
|
+
typography,
|
|
53
|
+
fontFamilies,
|
|
54
|
+
onSave,
|
|
55
|
+
saving,
|
|
56
|
+
}: {
|
|
57
|
+
typography?: SiteStyles["typography"];
|
|
58
|
+
fontFamilies: string[];
|
|
59
|
+
onSave: (data: Record<string, unknown>) => void;
|
|
60
|
+
saving: boolean;
|
|
61
|
+
}) {
|
|
62
|
+
const levels = ["h1", "h2", "h3", "h4", "body", "small"] as const;
|
|
63
|
+
|
|
64
|
+
const [local, setLocal] = useState<Record<string, TypographyLevel>>(() => {
|
|
65
|
+
const result: Record<string, TypographyLevel> = {};
|
|
66
|
+
for (const level of levels) {
|
|
67
|
+
result[level] = typography?.[level] || DEFAULT_TYPOGRAPHY[level];
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const [expanded, setExpanded] = useState<string | null>(null);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const result: Record<string, TypographyLevel> = {};
|
|
76
|
+
for (const level of levels) {
|
|
77
|
+
result[level] = typography?.[level] || DEFAULT_TYPOGRAPHY[level];
|
|
78
|
+
}
|
|
79
|
+
setLocal(result);
|
|
80
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
|
+
}, [typography]);
|
|
82
|
+
|
|
83
|
+
const updateLevel = (level: string, updates: Partial<TypographyLevel>) => {
|
|
84
|
+
setLocal({ ...local, [level]: { ...local[level], ...updates } });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handleSave = () => {
|
|
88
|
+
const data: Record<string, unknown> = {};
|
|
89
|
+
for (const level of levels) {
|
|
90
|
+
data[`typography_${level}`] = local[level];
|
|
91
|
+
}
|
|
92
|
+
onSave(data);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const toggle = (level: string) => {
|
|
96
|
+
setExpanded(expanded === level ? null : level);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Resolve the primary font family from h1 for the big "Aa" preview
|
|
100
|
+
const primaryFont = local.h1?.font_family || fontFamilies[0] || "monospace";
|
|
101
|
+
|
|
102
|
+
// Collect unique weights used across all levels
|
|
103
|
+
const usedWeights = Array.from(
|
|
104
|
+
new Set(levels.map((l) => local[l]?.font_weight).filter(Boolean))
|
|
105
|
+
).sort();
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<Section title="Typography" description="Define the typographic hierarchy. Click any level to edit its properties.">
|
|
109
|
+
<div className="rounded-2xl border border-neutral-200 bg-white overflow-hidden">
|
|
110
|
+
{/* Preview area */}
|
|
111
|
+
<div className="px-8 pt-8 pb-6 md:px-10 md:pt-10">
|
|
112
|
+
{/* Typeface hero */}
|
|
113
|
+
<div className="flex items-end gap-8 mb-8 pb-8 border-b border-neutral-100">
|
|
114
|
+
<span
|
|
115
|
+
style={{ fontFamily: `'${primaryFont}', monospace` }}
|
|
116
|
+
className="text-[5rem] font-bold leading-none text-neutral-900 select-none"
|
|
117
|
+
>
|
|
118
|
+
Aa
|
|
119
|
+
</span>
|
|
120
|
+
<div className="pb-1">
|
|
121
|
+
<p className="text-[10px] uppercase tracking-[0.1em] text-neutral-400 mb-1.5">Typeface</p>
|
|
122
|
+
<p className="text-sm font-semibold text-neutral-900">{primaryFont}</p>
|
|
123
|
+
<p className="text-[11px] text-neutral-400 mt-0.5">
|
|
124
|
+
{usedWeights.length > 0
|
|
125
|
+
? `Weights: ${usedWeights.join(" · ")}`
|
|
126
|
+
: "No weights configured"}
|
|
127
|
+
</p>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* Type scale — live preview rows */}
|
|
132
|
+
<div className="flex flex-col">
|
|
133
|
+
{levels.map((level) => {
|
|
134
|
+
const t = local[level];
|
|
135
|
+
const isExpanded = expanded === level;
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div key={level}>
|
|
139
|
+
{/* Clickable preview row */}
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
onClick={() => toggle(level)}
|
|
143
|
+
className={`w-full grid items-baseline gap-4 py-3 px-3 -mx-3 rounded-lg cursor-pointer transition-colors text-left ${
|
|
144
|
+
isExpanded ? "bg-neutral-50" : "hover:bg-neutral-50/60"
|
|
145
|
+
}`}
|
|
146
|
+
style={{ gridTemplateColumns: "120px 1fr" }}
|
|
147
|
+
>
|
|
148
|
+
{/* Label */}
|
|
149
|
+
<span className="flex items-center gap-1.5">
|
|
150
|
+
<ChevronDownIcon open={isExpanded} />
|
|
151
|
+
<span className="text-[10px] uppercase tracking-[0.1em] text-neutral-400 font-medium">
|
|
152
|
+
{TYPO_LEVEL_LABELS[level]}
|
|
153
|
+
</span>
|
|
154
|
+
</span>
|
|
155
|
+
|
|
156
|
+
{/* Live sample */}
|
|
157
|
+
<span
|
|
158
|
+
style={{
|
|
159
|
+
fontFamily: t.font_family ? `'${t.font_family}', monospace` : "inherit",
|
|
160
|
+
fontSize: t.font_size,
|
|
161
|
+
fontWeight: Number(t.font_weight) || 400,
|
|
162
|
+
lineHeight: t.line_height,
|
|
163
|
+
letterSpacing: t.letter_spacing,
|
|
164
|
+
textTransform: (t.text_transform || "none") as React.CSSProperties["textTransform"],
|
|
165
|
+
color: t.color || "#0a0a0a",
|
|
166
|
+
}}
|
|
167
|
+
className={level === "body" ? "" : "truncate block"}
|
|
168
|
+
>
|
|
169
|
+
{TYPO_SAMPLE_TEXTS[level]}
|
|
170
|
+
</span>
|
|
171
|
+
</button>
|
|
172
|
+
|
|
173
|
+
{/* Expanded controls */}
|
|
174
|
+
{isExpanded && (
|
|
175
|
+
<div className="bg-neutral-50 rounded-xl border border-neutral-100 p-4 mb-2 -mx-3">
|
|
176
|
+
<div className="grid grid-cols-6 gap-3">
|
|
177
|
+
<div>
|
|
178
|
+
<label className="text-[9px] uppercase tracking-[0.08em] text-neutral-400 font-medium block mb-1">Font</label>
|
|
179
|
+
<select
|
|
180
|
+
value={t.font_family || ""}
|
|
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-[#076bff] focus:outline-none"
|
|
183
|
+
>
|
|
184
|
+
<option value="">Inherit</option>
|
|
185
|
+
{fontFamilies.map((f) => (
|
|
186
|
+
<option key={f} value={f}>{f}</option>
|
|
187
|
+
))}
|
|
188
|
+
</select>
|
|
189
|
+
</div>
|
|
190
|
+
<div>
|
|
191
|
+
<label className="text-[9px] uppercase tracking-[0.08em] text-neutral-400 font-medium block mb-1">Size</label>
|
|
192
|
+
<input
|
|
193
|
+
type="text"
|
|
194
|
+
value={t.font_size}
|
|
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-[#076bff] focus:outline-none"
|
|
197
|
+
placeholder="3rem"
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
<div>
|
|
201
|
+
<label className="text-[9px] uppercase tracking-[0.08em] text-neutral-400 font-medium block mb-1">Weight</label>
|
|
202
|
+
<select
|
|
203
|
+
value={t.font_weight}
|
|
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-[#076bff] focus:outline-none"
|
|
206
|
+
>
|
|
207
|
+
{["100", "300", "400", "500", "600", "700", "800", "900"].map((w) => (
|
|
208
|
+
<option key={w} value={w}>{w}</option>
|
|
209
|
+
))}
|
|
210
|
+
</select>
|
|
211
|
+
</div>
|
|
212
|
+
<div>
|
|
213
|
+
<label className="text-[9px] uppercase tracking-[0.08em] text-neutral-400 font-medium block mb-1">Line Height</label>
|
|
214
|
+
<input
|
|
215
|
+
type="text"
|
|
216
|
+
value={t.line_height}
|
|
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-[#076bff] focus:outline-none"
|
|
219
|
+
placeholder="1.1"
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
<div>
|
|
223
|
+
<label className="text-[9px] uppercase tracking-[0.08em] text-neutral-400 font-medium block mb-1">Spacing</label>
|
|
224
|
+
<input
|
|
225
|
+
type="text"
|
|
226
|
+
value={t.letter_spacing}
|
|
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-[#076bff] focus:outline-none"
|
|
229
|
+
placeholder="-0.02em"
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
<div>
|
|
233
|
+
<label className="text-[9px] uppercase tracking-[0.08em] text-neutral-400 font-medium block mb-1">Transform</label>
|
|
234
|
+
<select
|
|
235
|
+
value={t.text_transform || "none"}
|
|
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-[#076bff] focus:outline-none"
|
|
238
|
+
>
|
|
239
|
+
<option value="none">None</option>
|
|
240
|
+
<option value="uppercase">UPPERCASE</option>
|
|
241
|
+
<option value="lowercase">lowercase</option>
|
|
242
|
+
<option value="capitalize">Capitalize</option>
|
|
243
|
+
</select>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
{/* Specs summary line */}
|
|
247
|
+
<p className="mt-3 text-[10px] text-neutral-300 tracking-wide">
|
|
248
|
+
{t.font_family || "Inherit"} · {t.font_size} · {t.font_weight} · {t.line_height} · {t.letter_spacing}
|
|
249
|
+
{t.text_transform && t.text_transform !== "none" ? ` · ${t.text_transform}` : ""}
|
|
250
|
+
</p>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
})}
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
{/* Save footer */}
|
|
260
|
+
<div className="px-8 py-4 md:px-10 bg-neutral-50 border-t border-neutral-100 flex justify-end">
|
|
261
|
+
<SaveButton onClick={handleSave} saving={saving} />
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</Section>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Shared components
|
|
2
|
+
export { Section, ColorField, SaveButton, FieldInput } from "./shared";
|
|
3
|
+
|
|
4
|
+
// Editor components
|
|
5
|
+
export { GridLayoutEditor } from "./GridLayoutEditor";
|
|
6
|
+
export { FontsEditor } from "./FontsEditor";
|
|
7
|
+
export { TypographyEditor } from "./TypographyEditor";
|
|
8
|
+
export { ColorsEditor } from "./ColorsEditor";
|
|
9
|
+
export { LinksButtonsEditor } from "./LinksButtonsEditor";
|
|
@@ -0,0 +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-[#076bff] 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-[#076bff] px-5 py-1.5 text-sm font-medium text-white hover:bg-[#0559d4] 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-[#076bff] focus:outline-none"
|
|
63
|
+
placeholder={placeholder}
|
|
64
|
+
/>
|
|
65
|
+
{helpText && <p className="text-[10px] text-neutral-400 mt-1">{helpText}</p>}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|