From e79a9548b3054ec123ce7c3e0796f4a0de51f216 Mon Sep 17 00:00:00 2001 From: SwasthK Date: Thu, 20 Nov 2025 23:19:23 +0530 Subject: [PATCH 1/4] feat: add cmdk global search --- components/cmdk.tsx | 252 ++++++++++++++++++++++ components/editor/theme-preset-select.tsx | 80 +------ components/get-pro-cta.tsx | 2 +- components/header.tsx | 15 +- components/search/color-box.tsx | 7 + components/search/theme-colors.tsx | 20 ++ components/ui/command.tsx | 8 +- components/ui/kbd.tsx | 28 +++ lib/search/constants/navigation.ts | 54 +++++ lib/search/filter-presets.ts | 15 ++ lib/search/is-theme-new.ts | 9 + lib/search/sort-themes.ts | 13 ++ pnpm-lock.yaml | 24 +-- 13 files changed, 425 insertions(+), 102 deletions(-) create mode 100644 components/cmdk.tsx create mode 100644 components/search/color-box.tsx create mode 100644 components/search/theme-colors.tsx create mode 100644 components/ui/kbd.tsx create mode 100644 lib/search/constants/navigation.ts create mode 100644 lib/search/filter-presets.ts create mode 100644 lib/search/is-theme-new.ts create mode 100644 lib/search/sort-themes.ts diff --git a/components/cmdk.tsx b/components/cmdk.tsx new file mode 100644 index 000000000..fa326c1a2 --- /dev/null +++ b/components/cmdk.tsx @@ -0,0 +1,252 @@ +"use client"; + +import * as React from "react"; +import { Check, CornerDownLeft, Search } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Input } from "@/components/ui/input"; +import { Kbd, KbdGroup } from "./ui/kbd"; +import { Button } from "./ui/button"; +import { useEditorStore } from "@/store/editor-store"; +import { useThemePresetStore } from "@/store/theme-preset-store"; +import { authClient } from "@/lib/auth-client"; +import { Badge } from "./ui/badge"; +import { isThemeNew } from "@/lib/search/is-theme-new"; +import { ThemeColors } from "./search/theme-colors"; +import { NAVIGATION_ITEMS } from "@/lib/search/constants/navigation"; +import { filterPresets } from "@/lib/search/filter-presets"; +import { sortThemes } from "@/lib/search/sort-themes"; +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandItem, + CommandList, + CommandSeparator, +} from "@/components/ui/command"; + +export function CmdK() { + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(""); + const router = useRouter(); + + const themeState = useEditorStore((store) => store.themeState); + const applyThemePreset = useEditorStore((store) => store.applyThemePreset); + const currentPreset = themeState.preset; + const mode = themeState.currentMode; + + const presets = useThemePresetStore((store) => store.getAllPresets()); + const loadSavedPresets = useThemePresetStore((store) => store.loadSavedPresets); + const unloadSavedPresets = useThemePresetStore((store) => store.unloadSavedPresets); + + const { data: session } = authClient.useSession(); + + useEffect(() => { + if (session?.user) { + loadSavedPresets(); + } else { + unloadSavedPresets(); + } + }, [loadSavedPresets, unloadSavedPresets, session?.user]); + + const isSavedTheme = useCallback( + (presetId: string) => { + return presets[presetId]?.source === "SAVED"; + }, + [presets] + ); + + const presetNames = useMemo(() => ["default", ...Object.keys(presets)], [presets]); + + const filteredPresets = useMemo(() => { + const filteredList = filterPresets(presetNames, presets, search); + + // Separate saved and default themes + const savedThemesList = filteredList.filter((name) => name !== "default" && isSavedTheme(name)); + const defaultThemesList = filteredList.filter((name) => !savedThemesList.includes(name)); + + return [...sortThemes(savedThemesList, presets), ...sortThemes(defaultThemesList, presets)]; + }, [presetNames, search, presets, isSavedTheme]); + + const filteredSavedThemes = useMemo(() => { + return filteredPresets.filter((name) => name !== "default" && isSavedTheme(name)); + }, [filteredPresets, isSavedTheme]); + + const filteredDefaultThemes = useMemo(() => { + return filteredPresets.filter((name) => name === "default" || !isSavedTheme(name)); + }, [filteredPresets, isSavedTheme]); + + const filteredNavigation = useMemo(() => { + if (search.trim() === "") { + return NAVIGATION_ITEMS; + } + const searchLower = search.toLowerCase(); + return NAVIGATION_ITEMS.filter((item) => { + const matchesLabel = item.label.toLowerCase().includes(searchLower); + const matchesKeywords = item.keywords?.some((keyword) => + keyword.toLowerCase().includes(searchLower) + ); + return matchesLabel || matchesKeywords; + }); + }, [search]); + + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; + + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); + + const onThemeSelect = (presetName: string) => { + applyThemePreset(presetName); + setOpen(false); + setSearch(""); + }; + + const onNavigationSelect = (href: string) => { + router.push(href); + setOpen(false); + setSearch(""); + }; + + return ( + <> + + +
+
+ + setSearch(e.target.value)} + /> +
+ + +

No results found.

+
+ + {/* Navigation Group */} + {filteredNavigation.length > 0 && ( + <> + + {filteredNavigation.map((item) => { + const Icon = item.icon; + return ( + onNavigationSelect(item.href)} + className="flex items-center gap-2" + > + + {item.label} + + ); + })} + + {(filteredSavedThemes.length > 0 || filteredDefaultThemes.length > 0) && ( + + )} + + )} + + {/* Saved Themes Group */} + {filteredSavedThemes.length > 0 && ( + <> + + {filteredSavedThemes.map((presetName, index) => ( + onThemeSelect(presetName)} + className="flex items-center gap-2" + > + + +
+ + {presets[presetName]?.label || presetName} + + {presets[presetName] && isThemeNew(presets[presetName]) && ( + + New + + )} +
+ {presetName === currentPreset && ( + + )} +
+ ))} +
+ + + )} + + {/* Built-in Themes Group */} + {filteredDefaultThemes.length > 0 && ( + + {filteredDefaultThemes.map((presetName, index) => ( + onThemeSelect(presetName)} + className="flex items-center gap-2" + > + +
+ + {presets[presetName]?.label || presetName} + + {presets[presetName] && isThemeNew(presets[presetName]) && ( + + New + + )} +
+ {presetName === currentPreset && ( + + )} +
+ ))} +
+ )} +
+
+ +
+ + ); +} + +const CommandFooter = () => { + return ( +
+ + + +

Go to page

+
+ ); +}; diff --git a/components/editor/theme-preset-select.tsx b/components/editor/theme-preset-select.tsx index 375d8a6c1..6d2126364 100644 --- a/components/editor/theme-preset-select.tsx +++ b/components/editor/theme-preset-select.tsx @@ -10,8 +10,14 @@ import { authClient } from "@/lib/auth-client"; import { cn } from "@/lib/utils"; import { useEditorStore } from "@/store/editor-store"; import { useThemePresetStore } from "@/store/theme-preset-store"; -import { ThemePreset } from "@/types/theme"; -import { getPresetThemeStyles } from "@/utils/theme-preset-helper"; +import Link from "next/link"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { ThemeToggle } from "../theme-toggle"; +import { TooltipWrapper } from "../tooltip-wrapper"; +import { ThemeColors } from "../search/theme-colors"; +import { filterPresets } from "@/lib/search/filter-presets"; +import { sortThemes } from "@/lib/search/sort-themes"; +import { isThemeNew } from "@/lib/search/is-theme-new"; import { ArrowLeft, ArrowRight, @@ -22,48 +28,11 @@ import { Settings, Shuffle, } from "lucide-react"; -import Link from "next/link"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { ThemeToggle } from "../theme-toggle"; -import { TooltipWrapper } from "../tooltip-wrapper"; interface ThemePresetSelectProps extends React.ComponentProps { withCycleThemes?: boolean; } -interface ColorBoxProps { - color: string; -} - -const ColorBox: React.FC = ({ color }) => ( -
-); - -interface ThemeColorsProps { - presetName: string; - mode: "light" | "dark"; -} - -const ThemeColors: React.FC = ({ presetName, mode }) => { - const styles = getPresetThemeStyles(presetName)[mode]; - return ( -
- - - - -
- ); -}; - -const isThemeNew = (preset: ThemePreset) => { - if (!preset.createdAt) return false; - const createdAt = new Date(preset.createdAt); - const timePeriod = new Date(); - timePeriod.setDate(timePeriod.getDate() - 5); - return createdAt > timePeriod; -}; - const ThemeControls = () => { const applyThemePreset = useEditorStore((store) => store.applyThemePreset); const presets = useThemePresetStore((store) => store.getAllPresets()); @@ -211,35 +180,13 @@ const ThemePresetSelect: React.FC = ({ const currentPresetName = presetNames?.find((name) => name === currentPreset); const filteredPresets = useMemo(() => { - const filteredList = - search.trim() === "" - ? presetNames - : presetNames.filter((name) => { - if (name === "default") { - return "default".toLowerCase().includes(search.toLowerCase()); - } - return presets[name]?.label?.toLowerCase().includes(search.toLowerCase()); - }); + const filteredList = filterPresets(presetNames, presets, search); // Separate saved and default themes const savedThemesList = filteredList.filter((name) => name !== "default" && isSavedTheme(name)); const defaultThemesList = filteredList.filter((name) => !savedThemesList.includes(name)); - // Sort each list, with "default" at the top for default themes - const sortThemes = (list: string[]) => { - const defaultTheme = list.filter((name) => name === "default"); - const otherThemes = list - .filter((name) => name !== "default") - .sort((a, b) => { - const labelA = presets[a]?.label || a; - const labelB = presets[b]?.label || b; - return labelA.localeCompare(labelB); - }); - return [...defaultTheme, ...otherThemes]; - }; - - // Combine saved themes first, then default themes - return [...sortThemes(savedThemesList), ...sortThemes(defaultThemesList)]; + return [...sortThemes(savedThemesList, presets), ...sortThemes(defaultThemesList, presets)]; }, [presetNames, search, presets, isSavedTheme]); const filteredSavedThemes = useMemo(() => { @@ -260,12 +207,7 @@ const ThemePresetSelect: React.FC = ({ {...props} >
-
- - - - -
+ {currentPresetName !== "default" && currentPresetName && isSavedTheme(currentPresetName) && diff --git a/components/get-pro-cta.tsx b/components/get-pro-cta.tsx index aa0eda8bd..761eb4120 100644 --- a/components/get-pro-cta.tsx +++ b/components/get-pro-cta.tsx @@ -28,7 +28,7 @@ export function GetProCTA({ className, ...props }: GetProCTAProps) { > - Get Pro + Get Pro ); diff --git a/components/header.tsx b/components/header.tsx index 0435f99d6..2c80859c8 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -15,6 +15,7 @@ import { formatCompactNumber } from "@/utils/format"; import Link from "next/link"; import { useState } from "react"; import { GetProCTA } from "./get-pro-cta"; +import { CmdK } from "./cmdk"; export function Header() { const { stargazersCount } = useGithubStars("jnsahaj", "tweakcn"); @@ -26,21 +27,23 @@ export function Header() {
- tweakcn + tweakcn
+ + {stargazersCount > 0 && formatCompactNumber(stargazersCount)} - -
+ +
@@ -50,14 +53,14 @@ export function Header() {
- +
diff --git a/components/search/color-box.tsx b/components/search/color-box.tsx new file mode 100644 index 000000000..8f56d74e2 --- /dev/null +++ b/components/search/color-box.tsx @@ -0,0 +1,7 @@ +interface ColorBoxProps { + color: string; +} + +export const ColorBox: React.FC = ({ color }) => ( +
+); diff --git a/components/search/theme-colors.tsx b/components/search/theme-colors.tsx new file mode 100644 index 000000000..aef8d43d5 --- /dev/null +++ b/components/search/theme-colors.tsx @@ -0,0 +1,20 @@ +import { getPresetThemeStyles } from "@/utils/theme-preset-helper"; +import { ColorBox } from "./color-box"; +import { useMemo } from "react"; + +interface ThemeColorsProps { + presetName: string; + mode: "light" | "dark"; +} + +export const ThemeColors: React.FC = ({ presetName, mode }) => { + const styles = useMemo(() => getPresetThemeStyles(presetName)[mode], [presetName, mode]); + return ( +
+ + + + +
+ ); +}; diff --git a/components/ui/command.tsx b/components/ui/command.tsx index 035363fed..738ec9457 100644 --- a/components/ui/command.tsx +++ b/components/ui/command.tsx @@ -21,12 +21,14 @@ const Command = React.forwardRef< )); Command.displayName = CommandPrimitive.displayName; -interface CommandDialogProps extends DialogProps {} +interface CommandDialogProps extends DialogProps { + title?: string; +} -const CommandDialog = ({ children, ...props }: CommandDialogProps) => { +const CommandDialog = ({ children, title, ...props }: CommandDialogProps) => { return ( - + {children} diff --git a/components/ui/kbd.tsx b/components/ui/kbd.tsx new file mode 100644 index 000000000..253c69f3d --- /dev/null +++ b/components/ui/kbd.tsx @@ -0,0 +1,28 @@ +import { cn } from "@/lib/utils" + +function Kbd({ className, ...props }: React.ComponentProps<"kbd">) { + return ( + + ) +} + +function KbdGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( + + ) +} + +export { Kbd, KbdGroup } diff --git a/lib/search/constants/navigation.ts b/lib/search/constants/navigation.ts new file mode 100644 index 000000000..ecb0e940b --- /dev/null +++ b/lib/search/constants/navigation.ts @@ -0,0 +1,54 @@ +import { CreditCard, Sparkles, Palette, Figma, Settings, ChartNoAxesCombined } from "lucide-react"; + +interface NavigationItem { + id: string; + label: string; + href: string; + icon: React.ComponentType<{ className?: string }>; + keywords?: string[]; +} + +export const NAVIGATION_ITEMS: NavigationItem[] = [ + { + id: "editor", + label: "Editor", + href: "/editor/theme", + icon: Palette, + keywords: ["theme", "edit", "create"], + }, + { + id: "pricing", + label: "Pricing", + href: "/pricing", + icon: CreditCard, + keywords: ["pro", "subscribe", "upgrade"], + }, + { + id: "ai", + label: "AI Generator", + href: "/ai", + icon: Sparkles, + keywords: ["generate", "ai", "create"], + }, + { + id: "figma", + label: "Figma", + href: "/figma", + icon: Figma, + keywords: ["figma", "design", "import"], + }, + { + id: "settings-themes", + label: "Themes Settings", + href: "/settings/themes", + icon: Settings, + keywords: ["settings", "themes", "manage"], + }, + { + id: "settings-usage", + label: "AI Usage", + href: "/settings/usage", + icon: ChartNoAxesCombined, + keywords: ["settings", "usage", "stats"], + }, +]; diff --git a/lib/search/filter-presets.ts b/lib/search/filter-presets.ts new file mode 100644 index 000000000..04740ec6d --- /dev/null +++ b/lib/search/filter-presets.ts @@ -0,0 +1,15 @@ +import { ThemePreset } from "@/types/theme"; + +export const filterPresets = (presetNames: string[], presets: Record, search: string) => { + const filteredList = + search.trim() === "" + ? presetNames + : presetNames.filter((name) => { + if (name === "default") { + return "default".toLowerCase().includes(search.toLowerCase()); + } + return presets[name]?.label?.toLowerCase().includes(search.toLowerCase()); + }); + + return filteredList; +}; diff --git a/lib/search/is-theme-new.ts b/lib/search/is-theme-new.ts new file mode 100644 index 000000000..c46882997 --- /dev/null +++ b/lib/search/is-theme-new.ts @@ -0,0 +1,9 @@ +import { ThemePreset } from "@/types/theme"; + +export const isThemeNew = (preset: ThemePreset) => { + if (!preset.createdAt) return false; + const createdAt = new Date(preset.createdAt); + const timePeriod = new Date(); + timePeriod.setDate(timePeriod.getDate() - 5); + return createdAt > timePeriod; +}; \ No newline at end of file diff --git a/lib/search/sort-themes.ts b/lib/search/sort-themes.ts new file mode 100644 index 000000000..3ad4c3ba6 --- /dev/null +++ b/lib/search/sort-themes.ts @@ -0,0 +1,13 @@ +import { ThemePreset } from "@/types/theme"; + +export const sortThemes = (list: string[], presets: Record) => { + const defaultTheme = list.filter((name) => name === "default"); + const otherThemes = list + .filter((name) => name !== "default") + .sort((a, b) => { + const labelA = presets[a]?.label || a; + const labelB = presets[b]?.label || b; + return labelA.localeCompare(labelB); + }); + return [...defaultTheme, ...otherThemes]; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f4b5bcb3..742218ab8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2260,19 +2260,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.0.3': - resolution: {integrity: sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-primitive@2.1.0': resolution: {integrity: sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==} peerDependencies: @@ -8842,15 +8829,6 @@ snapshots: '@types/react': 19.1.2 '@types/react-dom': 19.1.2(@types/react@19.1.2) - '@radix-ui/react-primitive@2.0.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - '@radix-ui/react-primitive@2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0) @@ -10315,7 +10293,7 @@ snapshots: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) '@radix-ui/react-dialog': 1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-primitive': 2.0.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) transitivePeerDependencies: From 5d5ba9b9f0c054ecef7b9142ea23bd11af8d5bb1 Mon Sep 17 00:00:00 2001 From: SwasthK Date: Fri, 21 Nov 2025 11:14:37 +0530 Subject: [PATCH 2/4] add minor fixes --- components/cmdk.tsx | 16 +++++++++------- components/editor/theme-preset-select.tsx | 6 +++--- components/header.tsx | 3 ++- components/ui/kbd.tsx | 2 +- {lib => utils}/search/constants/navigation.ts | 3 ++- {lib => utils}/search/filter-presets.ts | 6 ++++-- {lib => utils}/search/is-theme-new.ts | 0 {lib => utils}/search/sort-themes.ts | 0 utils/theme-preset-helper.ts | 1 - 9 files changed, 21 insertions(+), 16 deletions(-) rename {lib => utils}/search/constants/navigation.ts (94%) rename {lib => utils}/search/filter-presets.ts (61%) rename {lib => utils}/search/is-theme-new.ts (100%) rename {lib => utils}/search/sort-themes.ts (100%) diff --git a/components/cmdk.tsx b/components/cmdk.tsx index fa326c1a2..abbdb0f01 100644 --- a/components/cmdk.tsx +++ b/components/cmdk.tsx @@ -11,11 +11,11 @@ import { useEditorStore } from "@/store/editor-store"; import { useThemePresetStore } from "@/store/theme-preset-store"; import { authClient } from "@/lib/auth-client"; import { Badge } from "./ui/badge"; -import { isThemeNew } from "@/lib/search/is-theme-new"; +import { isThemeNew } from "@/utils/search/is-theme-new"; import { ThemeColors } from "./search/theme-colors"; -import { NAVIGATION_ITEMS } from "@/lib/search/constants/navigation"; -import { filterPresets } from "@/lib/search/filter-presets"; -import { sortThemes } from "@/lib/search/sort-themes"; +import { NAVIGATION_ITEMS } from "@/utils/search/constants/navigation"; +import { filterPresets } from "@/utils/search/filter-presets"; +import { sortThemes } from "@/utils/search/sort-themes"; import { CommandDialog, CommandEmpty, @@ -120,10 +120,12 @@ export function CmdK() { variant={"outline"} className="flex h-8 items-center justify-between gap-6" onClick={() => setOpen(true)} + aria-label="Open search" > - - + + @@ -243,7 +245,7 @@ export function CmdK() { const CommandFooter = () => { return (
- +

Go to page

diff --git a/components/editor/theme-preset-select.tsx b/components/editor/theme-preset-select.tsx index 6d2126364..e3ddbe0ce 100644 --- a/components/editor/theme-preset-select.tsx +++ b/components/editor/theme-preset-select.tsx @@ -15,9 +15,9 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { ThemeToggle } from "../theme-toggle"; import { TooltipWrapper } from "../tooltip-wrapper"; import { ThemeColors } from "../search/theme-colors"; -import { filterPresets } from "@/lib/search/filter-presets"; -import { sortThemes } from "@/lib/search/sort-themes"; -import { isThemeNew } from "@/lib/search/is-theme-new"; +import { filterPresets } from "@/utils/search/filter-presets"; +import { sortThemes } from "@/utils/search/sort-themes"; +import { isThemeNew } from "@/utils/search/is-theme-new"; import { ArrowLeft, ArrowRight, diff --git a/components/header.tsx b/components/header.tsx index 2c80859c8..7d2611946 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -59,8 +59,9 @@ export function Header() { variant="outline" className="flex h-8 items-center gap-2" > - +
diff --git a/components/ui/kbd.tsx b/components/ui/kbd.tsx index 253c69f3d..add40e8ea 100644 --- a/components/ui/kbd.tsx +++ b/components/ui/kbd.tsx @@ -17,7 +17,7 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) { function KbdGroup({ className, ...props }: React.ComponentProps<"div">) { return ( - ; + icon: LucideIcon; keywords?: string[]; } diff --git a/lib/search/filter-presets.ts b/utils/search/filter-presets.ts similarity index 61% rename from lib/search/filter-presets.ts rename to utils/search/filter-presets.ts index 04740ec6d..e0b75ed93 100644 --- a/lib/search/filter-presets.ts +++ b/utils/search/filter-presets.ts @@ -1,14 +1,16 @@ import { ThemePreset } from "@/types/theme"; export const filterPresets = (presetNames: string[], presets: Record, search: string) => { + const searchLower = search.toLowerCase(); const filteredList = search.trim() === "" ? presetNames : presetNames.filter((name) => { if (name === "default") { - return "default".toLowerCase().includes(search.toLowerCase()); + return "default".includes(searchLower); } - return presets[name]?.label?.toLowerCase().includes(search.toLowerCase()); + const label = presets[name]?.label; + return label ? label.toLowerCase().includes(searchLower) : false; }); return filteredList; diff --git a/lib/search/is-theme-new.ts b/utils/search/is-theme-new.ts similarity index 100% rename from lib/search/is-theme-new.ts rename to utils/search/is-theme-new.ts diff --git a/lib/search/sort-themes.ts b/utils/search/sort-themes.ts similarity index 100% rename from lib/search/sort-themes.ts rename to utils/search/sort-themes.ts diff --git a/utils/theme-preset-helper.ts b/utils/theme-preset-helper.ts index 5d4602c1f..fc680bd5b 100644 --- a/utils/theme-preset-helper.ts +++ b/utils/theme-preset-helper.ts @@ -21,7 +21,6 @@ export function getPresetThemeStyles(name: string): ThemeStyles { }, dark: { ...defaultTheme.dark, - ...(preset.styles.light || {}), ...(preset.styles.dark || {}), }, }; From b3d1ed23196fea8f65452b91f2783e29895a422f Mon Sep 17 00:00:00 2001 From: SwasthK Date: Sun, 23 Nov 2025 11:15:16 +0530 Subject: [PATCH 3/4] remove duplicate default preset in the list --- components/cmdk.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cmdk.tsx b/components/cmdk.tsx index abbdb0f01..cd99cbb64 100644 --- a/components/cmdk.tsx +++ b/components/cmdk.tsx @@ -56,7 +56,7 @@ export function CmdK() { [presets] ); - const presetNames = useMemo(() => ["default", ...Object.keys(presets)], [presets]); + const presetNames = useMemo(() => Array.from(new Set(["default", ...Object.keys(presets)])), [presets]); const filteredPresets = useMemo(() => { const filteredList = filterPresets(presetNames, presets, search); From 325c16096e23f39ecdc9a5a90b5effd45c3b3354 Mon Sep 17 00:00:00 2001 From: SwasthK Date: Mon, 8 Dec 2025 00:30:20 +0530 Subject: [PATCH 4/4] add tooltip to search component --- components/cmdk.tsx | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/components/cmdk.tsx b/components/cmdk.tsx index cd99cbb64..b92ca33e2 100644 --- a/components/cmdk.tsx +++ b/components/cmdk.tsx @@ -5,7 +5,7 @@ import { Check, CornerDownLeft, Search } from "lucide-react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Input } from "@/components/ui/input"; -import { Kbd, KbdGroup } from "./ui/kbd"; +import { Kbd } from "./ui/kbd"; import { Button } from "./ui/button"; import { useEditorStore } from "@/store/editor-store"; import { useThemePresetStore } from "@/store/theme-preset-store"; @@ -24,6 +24,7 @@ import { CommandList, CommandSeparator, } from "@/components/ui/command"; +import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; export function CmdK() { const [open, setOpen] = useState(false); @@ -116,22 +117,21 @@ export function CmdK() { return ( <> - + + + + + +

Search (⌘K)

+
+