diff --git a/app/blogs/components/FeaturedPost.tsx b/app/blogs/components/FeaturedPost.tsx index 9b23b3a..b7b5149 100644 --- a/app/blogs/components/FeaturedPost.tsx +++ b/app/blogs/components/FeaturedPost.tsx @@ -1,4 +1,5 @@ import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' import type { Post } from '@/lib/blog/types' import { BookOpen02Icon, Megaphone01Icon } from '@hugeicons/core-free-icons' import { HugeiconsIcon } from '@hugeicons/react' @@ -9,16 +10,41 @@ import { TagPill } from './TagPill' interface FeaturedPostProps { post: Post + /** Stretch to parent height (e.g. match adjacent stacked cards). */ + fillHeight?: boolean + /** Hide tags, author, and featured chrome — title + description + CTA only. */ + hideMeta?: boolean } -export function FeaturedPost({ post }: FeaturedPostProps) { +export function FeaturedPost({ + post, + fillHeight = false, + hideMeta = false, +}: FeaturedPostProps) { const href = `/blogs/${post.slug.join('/')}` return ( -
-
- {/* Image side */} -
+
+
+ {/* Image side — narrower than content */} +
{post.coverImage ? ( ) : ( -
+
Featured @@ -59,20 +90,34 @@ export function FeaturedPost({ post }: FeaturedPostProps) {
{/* Content side */} -
- +
+ {!hideMeta && ( + + )} -

+

{post.title}

-

+

{post.description}

-
- -
+ {!hideMeta && ( +
+ +
+ )} +
+
+
+ {loading ? ( +
+
+
+
+
+
+ ) : error ? ( +
{error}
+ ) : tree.length === 0 ? ( +
+ No files generated. +
+ ) : ( + + + + )} +
) } export function CodeEditorWorkspace() { const { config, fontFiles, isHydrated } = useWizard() + const isMobile = useIsMobile() const isValid = React.useMemo( () => scaffoldConfigSchema.safeParse(config).success, [config] @@ -313,6 +413,7 @@ export function CodeEditorWorkspace() { const [loading, setLoading] = React.useState(false) const [pendingSync, setPendingSync] = React.useState(false) const [error, setError] = React.useState(null) + const [explorerOpen, setExplorerOpen] = React.useState(false) const hasLoadedRef = React.useRef(false) const requestIdRef = React.useRef(0) @@ -410,6 +511,10 @@ export function CodeEditorWorkspace() { return () => window.clearTimeout(timer) }, [isHydrated, isValid, configKey, loadScaffold]) + React.useEffect(() => { + if (!isMobile) setExplorerOpen(false) + }, [isMobile]) + const isSyncing = pendingSync || loading const collapseAllFolders = React.useCallback(() => { @@ -417,10 +522,14 @@ export function CodeEditorWorkspace() { setTreeKey((key) => key + 1) }, []) - const openFile = React.useCallback((path: string) => { - setOpenPaths((prev) => (prev.includes(path) ? prev : [...prev, path])) - setActivePath(path) - }, []) + const openFile = React.useCallback( + (path: string) => { + setOpenPaths((prev) => (prev.includes(path) ? prev : [...prev, path])) + setActivePath(path) + if (isMobile) setExplorerOpen(false) + }, + [isMobile] + ) const closeTab = React.useCallback((path: string, event?: React.MouseEvent) => { event?.stopPropagation() @@ -475,6 +584,214 @@ export function CodeEditorWorkspace() { const canPreview = activePath != null && canPreviewAsText(activePath, activeCode) + const explorer = ( + + ) + + const editorColumn = ( +
+ {/* Tabs */} +
+
+ {orderedOpenPaths.length === 0 && !loading ? ( + + No open editors + + ) : null} + {orderedOpenPaths.map((path) => { + const isActive = path === activePath + const isPinned = pinnedPaths.includes(path) + const name = basename(path) + return ( + + +
setActivePath(path)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + setActivePath(path) + } + }} + className={cn( + "group flex h-8 max-w-38 shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 text-[12px] transition-colors duration-150 sm:h-7 sm:w-40 sm:max-w-none", + isActive + ? "bg-primary/10 text-primary" + : "text-muted-foreground hover:bg-muted/70 hover:text-foreground" + )} + > +
+
+ + closeTab(path)}> + Close + + closeAllTabs()} + disabled={orderedOpenPaths.length === 0} + > + Close all + + closeOtherTabs(path)} + disabled={orderedOpenPaths.length <= 1} + > + Close others + + + moveTabToRight(path)}> + Move to right + + togglePinTab(path)}> + {isPinned ? "Unpin" : "Pin"} + + +
+ ) + })} +
+
+ + {/* Breadcrumb */} + {activePath ? ( +
+ {activePath.split("/").map((segment, index, parts) => ( + + {index > 0 && /} + + {segment} + + + ))} +
+ ) : null} + + {/* Code */} +
+ {loading ? ( +
+
+
+
+
+ ) : error ? ( +
+ {error} +
+ ) : !activePath ? ( +
+ +

+ {isMobile + ? "Open the explorer to pick a file." + : "Open a file from the explorer to start."} +

+ {isMobile ? ( + + ) : null} +
+ ) : !canPreview ? ( + + + + + + No preview available + + This file type can't be previewed in the editor. + + + + ) : ( + + )} +
+
+ ) + if (!isHydrated) { return (
@@ -499,27 +816,42 @@ export function CodeEditorWorkspace() { return (
{/* Title bar */} -
- -
+
+
+ {isMobile ? ( + + ) : null} + +
+
{projectName} - + Preview
-
+
@@ -536,251 +868,44 @@ export function CodeEditorWorkspace() {