From ee59c47c6f91a2cd6bb764b9167f42a06cdfa641 Mon Sep 17 00:00:00 2001 From: Tyde Hashimoto Date: Wed, 15 Oct 2025 22:08:08 -0700 Subject: [PATCH 1/5] Separated out sidebar --- apps/web/components/Sidebar/Sidebar.tsx | 28 +++ .../seachComponents/SidebarCategories.tsx | 77 +++++++ .../seachComponents/SidebarFrameworks.tsx | 96 +++++++++ .../Sidebar/seachComponents/SidebarSearch.tsx | 53 +++++ .../Sidebar/seachComponents/SidebarTags.tsx | 68 ++++++ apps/web/pages/component/[...path].tsx | 3 +- apps/web/pages/index.tsx | 200 ++++-------------- 7 files changed, 362 insertions(+), 163 deletions(-) create mode 100644 apps/web/components/Sidebar/Sidebar.tsx create mode 100644 apps/web/components/Sidebar/seachComponents/SidebarCategories.tsx create mode 100644 apps/web/components/Sidebar/seachComponents/SidebarFrameworks.tsx create mode 100644 apps/web/components/Sidebar/seachComponents/SidebarSearch.tsx create mode 100644 apps/web/components/Sidebar/seachComponents/SidebarTags.tsx diff --git a/apps/web/components/Sidebar/Sidebar.tsx b/apps/web/components/Sidebar/Sidebar.tsx new file mode 100644 index 0000000..f1f8310 --- /dev/null +++ b/apps/web/components/Sidebar/Sidebar.tsx @@ -0,0 +1,28 @@ +import dynamic from "next/dynamic" + +// Dynamically import ThemeToggle to avoid SSR issues +const ThemeToggle = dynamic(() => import("../ThemeToggle"), { + ssr: false, + loading: () => ( +
+ {/* Loading placeholder */} +
+ ), +}) + +interface SidebarProps { + children?: React.ReactNode +} + +/** + * Sidebar component for filtering and navigating through UI components. + * Hidden below 590px screen width. + * @returns + */ +export default function Sidebar({ children }: SidebarProps) { + return ( + + ) +} diff --git a/apps/web/components/Sidebar/seachComponents/SidebarCategories.tsx b/apps/web/components/Sidebar/seachComponents/SidebarCategories.tsx new file mode 100644 index 0000000..480ee0b --- /dev/null +++ b/apps/web/components/Sidebar/seachComponents/SidebarCategories.tsx @@ -0,0 +1,77 @@ +import { ComponentMetadata } from "@/types" + +interface SidebarCategoriesProps { + categories: string[] + groupedComponents: Record + components: ComponentMetadata[] + selectedCategory: string | null + setSelectedCategory: (category: string | null) => void +} + +/** Sidebar component for filtering by categories. + * To be used within the Sidebar component. + * @example + * {}} + * /> + * + * @param categories - List of available categories. + * @param groupedComponents - Map of category names to their components. + * @param components - List of all components (used to count total components). + * @param selectedCategory - Currently selected category. + * @param setSelectedCategory - Function to update the selected category. + * @returns JSX.Element + */ +export default function SidebarCategories({ + categories, + groupedComponents, + components, + selectedCategory, + setSelectedCategory, +}: SidebarCategoriesProps) { + const clearCategories = () => { + setSelectedCategory(null) + } + + return ( +
+

+ Categories +

+
+ + {categories?.map(category => { + const count = groupedComponents[category]?.length || 0 + return ( + + ) + })} +
+
+ ) +} diff --git a/apps/web/components/Sidebar/seachComponents/SidebarFrameworks.tsx b/apps/web/components/Sidebar/seachComponents/SidebarFrameworks.tsx new file mode 100644 index 0000000..1dfd692 --- /dev/null +++ b/apps/web/components/Sidebar/seachComponents/SidebarFrameworks.tsx @@ -0,0 +1,96 @@ +import { ComponentMetadata } from "@/types" +import { useMemo } from "react" + +interface SidebarFrameworksProps { + frameworks: string[] + components: ComponentMetadata[] + selectedFramework: string | null + setSelectedFramework: (framework: string | null) => void +} + +/** Sidebar component for filtering by front-end frameworks. + * To be used within the Sidebar component. + * + * @example + * {}} + * /> + * + * @param frameworks - List of available frameworks. + * @param components - List of all components (used to count components per framework). + * @param selectedFramework - Currently selected framework. + * @param setSelectedFramework - Function to update the selected framework. + * @returns JSX.Element + */ +export default function SidebarFrameworks({ + frameworks, + components, + selectedFramework, + setSelectedFramework, +}: SidebarFrameworksProps) { + const clearFrameworks = () => { + setSelectedFramework(null) + } + + const onFrameworkSelect = (framework: string) => { + setSelectedFramework(framework) + } + + const lowerSelectedFramework = selectedFramework?.toLowerCase() || null + + const frameworkCount = useMemo(() => { + return frameworks.reduce( + (acc, framework) => { + acc[framework] = components.filter( + component => + (component.framework || "").trim().toLowerCase() === + framework.trim().toLowerCase() + ).length + return acc + }, + {} as Record + ) + }, [components, frameworks]) + + return ( +
+

+ Frameworks +

+
+ + + {frameworks?.map(framework => { + return ( + + ) + })} +
+
+ ) +} diff --git a/apps/web/components/Sidebar/seachComponents/SidebarSearch.tsx b/apps/web/components/Sidebar/seachComponents/SidebarSearch.tsx new file mode 100644 index 0000000..fd963ff --- /dev/null +++ b/apps/web/components/Sidebar/seachComponents/SidebarSearch.tsx @@ -0,0 +1,53 @@ +interface SidebarSearchbarProps { + setSearchQuery: (query: string) => void + searchQuery: string +} + +/** Sidebar search bar component. + * To be used within the Sidebar component. + * @example + * + * @param setSearchQuery - Function to update the search query state. + * @param searchQuery - Current search query state. + * @returns JSX.Element + */ +export default function SidebarSearchbar({ + setSearchQuery, + searchQuery, +}: SidebarSearchbarProps) { + const onChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value) + } + + return ( +
+
+ +
+ + + +
+
+
+ ) +} diff --git a/apps/web/components/Sidebar/seachComponents/SidebarTags.tsx b/apps/web/components/Sidebar/seachComponents/SidebarTags.tsx new file mode 100644 index 0000000..aa798a5 --- /dev/null +++ b/apps/web/components/Sidebar/seachComponents/SidebarTags.tsx @@ -0,0 +1,68 @@ +import TagChip from "@/components/TagChip" +import { ComponentMetadata } from "@/types" +import { Dispatch, SetStateAction } from "react" + +interface SidebarTagsProps { + tags: string[] + selectedTags: string[] + setSelectedTags: Dispatch> +} + +/** Sidebar component for filtering by tags. + * To be used within the Sidebar component. + * @example + * + * @param tags - List of available tags. + * @param selectedTags - Currently selected tags. + * @param setSelectedTags - Function to update the selected tags. + * @returns JSX.Element + */ +export default function SidebarTags({ + tags, + selectedTags, + setSelectedTags, +}: SidebarTagsProps) { + const clearTags = () => { + setSelectedTags([]) + } + + const onTagToggle = (tag: string) => { + setSelectedTags((prev: string[]) => + prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag] + ) + } + + return ( +
+

+ Tags +

+
+ {tags?.map(tag => ( + + ))} +
+ {selectedTags.length > 0 && ( +
+ +
+ )} +
+ ) +} diff --git a/apps/web/pages/component/[...path].tsx b/apps/web/pages/component/[...path].tsx index ad4db04..84fd176 100644 --- a/apps/web/pages/component/[...path].tsx +++ b/apps/web/pages/component/[...path].tsx @@ -4,6 +4,7 @@ import { useRouter } from "next/router" import { useEffect, useState } from "react" import ReactPreview from "../../components/ReactPreview" import type { ComponentWithFiles } from "../../types" +import Sidebar from "@/components/Sidebar/Sidebar" export default function ComponentPage() { const router = useRouter() @@ -152,7 +153,7 @@ export default function ComponentPage() { - +
{/* Preview Section - Full Width */} diff --git a/apps/web/pages/index.tsx b/apps/web/pages/index.tsx index 87e6247..6664e7c 100644 --- a/apps/web/pages/index.tsx +++ b/apps/web/pages/index.tsx @@ -4,6 +4,11 @@ import { useEffect, useState, useMemo } from "react" import Navbar from "../components/Navbar" import type { ComponentMetadata } from "../types" import TagChip from "../components/TagChip" +import Sidebar from "@/components/Sidebar/Sidebar" +import SidebarSearchbar from "@/components/Sidebar/seachComponents/SidebarSearch" +import SidebarTags from "@/components/Sidebar/seachComponents/SidebarTags" +import SidebarCategories from "@/components/Sidebar/seachComponents/SidebarCategories" +import SidebarFrameworks from "@/components/Sidebar/seachComponents/SidebarFrameworks" export default function Home() { const [components, setComponents] = useState([]) @@ -136,170 +141,41 @@ export default function Home() {
-
{/* Sidebar - Hidden below 590px */} - + + {/* Search Bar */} + + {/* Categories */} + + {/* Frameworks */} + {allFrameworks.length > 0 && ( + + )} + {/* Tags */} + {allTags.length > 0 && ( + + )} + {/* Main Content */}
From 5bd73557d0e2a88aa0bcd5bf2a29a783302e3429 Mon Sep 17 00:00:00 2001 From: Tyde Hashimoto Date: Wed, 15 Oct 2025 22:33:51 -0700 Subject: [PATCH 2/5] Started sidebar link component --- .../seachComponents/SidebarComponentLinks.tsx | 45 ++ apps/web/pages/component/[...path].tsx | 762 +++++++++--------- apps/web/pages/index.tsx | 1 - 3 files changed, 442 insertions(+), 366 deletions(-) create mode 100644 apps/web/components/Sidebar/seachComponents/SidebarComponentLinks.tsx diff --git a/apps/web/components/Sidebar/seachComponents/SidebarComponentLinks.tsx b/apps/web/components/Sidebar/seachComponents/SidebarComponentLinks.tsx new file mode 100644 index 0000000..fc49622 --- /dev/null +++ b/apps/web/components/Sidebar/seachComponents/SidebarComponentLinks.tsx @@ -0,0 +1,45 @@ +import Link from "next/link" +import { ComponentMetadata } from "@/types" + +interface SidebarComponentLinksProps { + components: ComponentMetadata[] + currentComponent?: string + componentFilter?: string +} + +/** Sidebar component for listing and selecting components. + * To be used within the Sidebar component. + * @example + * + * @param components - List of all components to display. + * @param currentComponent - Currently selected component name. + * @returns JSX.Element + */ + +export default function SidebarComponentLinks({ + components, + currentComponent, +}: SidebarComponentLinksProps) { + return ( + <> + {components.map(comp => ( +
+ + {comp.name} + +
+ ))} + + ) +} diff --git a/apps/web/pages/component/[...path].tsx b/apps/web/pages/component/[...path].tsx index 84fd176..a5fb535 100644 --- a/apps/web/pages/component/[...path].tsx +++ b/apps/web/pages/component/[...path].tsx @@ -3,8 +3,10 @@ import Head from "next/head" import { useRouter } from "next/router" import { useEffect, useState } from "react" import ReactPreview from "../../components/ReactPreview" -import type { ComponentWithFiles } from "../../types" +import type { ComponentMetadata, ComponentWithFiles } from "../../types" import Sidebar from "@/components/Sidebar/Sidebar" +import SidebarSearchbar from "@/components/Sidebar/seachComponents/SidebarSearch" +import SidebarComponentLinks from "@/components/Sidebar/seachComponents/SidebarComponentLinks" export default function ComponentPage() { const router = useRouter() @@ -16,6 +18,22 @@ export default function ComponentPage() { {} ) const [wordWrap, setWordWrap] = useState(false) + const [searchQuery, setSearchQuery] = useState("") + const [components, setComponents] = useState([]) + + useEffect(() => { + fetch("/api/components") + .then(res => res.json()) + .then(data => { + setComponents(data.components || []) + setLoading(false) + }) + .catch(err => { + // eslint-disable-next-line no-console + console.error("Failed to load components:", err) + setLoading(false) + }) + }, []) const copyToClipboard = async (text: string, key: string) => { try { @@ -153,21 +171,34 @@ export default function ComponentPage() {
- -
-
- {/* Preview Section - Full Width */} -
-

- Preview -

- {isHtmlTailwind && component.files["index.html"] ? ( -
- {/* Subtle checkerboard pattern for better contrast */} -
+ {/* Sidebar - Hidden below 590px */} + + {/* Search Bar */} + + {/* Component Links */} + + +
+
+ {/* Preview Section - Full Width */} +
+

+ Preview +

+ {isHtmlTailwind && component.files["index.html"] ? ( +
+ {/* Subtle checkerboard pattern for better contrast */} +
-