feat: navigation for command palette (#150)

* feat: add dotenv to dev dep

* chore: jest use env

* feat

* feat: command palette navigation
This commit is contained in:
Aslam
2024-09-08 05:43:44 +07:00
committed by GitHub
parent a87c27d91f
commit f005101d91
8 changed files with 185 additions and 13 deletions

View File

@@ -1,12 +1,14 @@
import { icons } from "lucide-react"
import { useCommandActions } from "./hooks/use-command-actions"
import { LaAccount } from "@/lib/schema"
import { HTMLLikeElement } from "@/lib/utils"
export type CommandAction = string | (() => void)
export type CommandItemType = {
icon?: keyof typeof icons
label: string
value: string
label: HTMLLikeElement | string
action: CommandAction
payload?: any
shortcut?: string
@@ -25,9 +27,16 @@ export const createCommandGroups = (
{
heading: "General",
items: [
{ icon: "SunMoon", label: "Change Theme...", action: "CHANGE_PAGE", payload: "changeTheme" },
{
icon: "SunMoon",
value: "Change Theme...",
label: "Change Theme...",
action: "CHANGE_PAGE",
payload: "changeTheme"
},
{
icon: "Copy",
value: "Copy Current URL",
label: "Copy Current URL",
action: actions.copyCurrentURL
}
@@ -36,20 +45,88 @@ export const createCommandGroups = (
{
heading: "Personal Links",
items: [
{ icon: "TextSearch", label: "Search Links...", action: "CHANGE_PAGE", payload: "searchLinks" },
{ icon: "Plus", label: "Create New Link...", action: () => actions.navigateTo("/") }
{
icon: "TextSearch",
value: "Search Links...",
label: "Search Links...",
action: "CHANGE_PAGE",
payload: "searchLinks"
},
{
icon: "Plus",
value: "Create New Link...",
label: "Create New Link...",
action: () => actions.navigateTo("/")
}
]
},
{
heading: "Personal Pages",
items: [
{ icon: "FileSearch", label: "Search Pages...", action: "CHANGE_PAGE", payload: "searchPages" },
{
icon: "FileSearch",
value: "Search Pages...",
label: "Search Pages...",
action: "CHANGE_PAGE",
payload: "searchPages"
},
{
icon: "Plus",
value: "Create New Page...",
label: "Create New Page...",
action: () => actions.createNewPage(me)
}
]
},
{
heading: "Navigation",
items: [
{
icon: "ArrowRight",
value: "Go to Links",
label: {
tag: "span",
children: ["Go to ", { tag: "span", attributes: { className: "font-semibold" }, children: ["links"] }]
},
action: () => actions.navigateTo("/links")
},
{
icon: "ArrowRight",
value: "Go to Pages",
label: {
tag: "span",
children: ["Go to ", { tag: "span", attributes: { className: "font-semibold" }, children: ["pages"] }]
},
action: () => actions.navigateTo("/pages")
},
{
icon: "ArrowRight",
value: "Go to Search",
label: {
tag: "span",
children: ["Go to ", { tag: "span", attributes: { className: "font-semibold" }, children: ["search"] }]
},
action: () => actions.navigateTo("/search")
},
{
icon: "ArrowRight",
value: "Go to Profile",
label: {
tag: "span",
children: ["Go to ", { tag: "span", attributes: { className: "font-semibold" }, children: ["profile"] }]
},
action: () => actions.navigateTo("/profile")
},
{
icon: "ArrowRight",
value: "Go to Settings",
label: {
tag: "span",
children: ["Go to ", { tag: "span", attributes: { className: "font-semibold" }, children: ["settings"] }]
},
action: () => actions.navigateTo("/settings")
}
]
}
],
searchLinks: [],
@@ -58,9 +135,24 @@ export const createCommandGroups = (
changeTheme: [
{
items: [
{ icon: "Moon", label: "Change Theme to Dark", action: () => actions.changeTheme("dark") },
{ icon: "Sun", label: "Change Theme to Light", action: () => actions.changeTheme("light") },
{ icon: "Monitor", label: "Change Theme to System", action: () => actions.changeTheme("system") }
{
icon: "Moon",
value: "Change Theme to Dark",
label: "Change Theme to Dark",
action: () => actions.changeTheme("dark")
},
{
icon: "Sun",
value: "Change Theme to Light",
label: "Change Theme to Light",
action: () => actions.changeTheme("light")
},
{
icon: "Monitor",
value: "changeThemeToSystem",
label: "Change Theme to System",
action: () => actions.changeTheme("system")
}
]
}
]

View File

@@ -2,16 +2,21 @@ import { Command } from "cmdk"
import { CommandSeparator, CommandShortcut } from "@/components/ui/command"
import { LaIcon } from "../la-icon"
import { CommandItemType, CommandAction } from "./command-data"
import { HTMLLikeElement, renderHTMLLikeElement } from "@/lib/utils"
export interface CommandItemProps extends Omit<CommandItemType, "action"> {
action: CommandAction
handleAction: (action: CommandAction, payload?: any) => void
}
const HTMLLikeRenderer: React.FC<{ content: HTMLLikeElement | string }> = ({ content }) => {
return <>{renderHTMLLikeElement(content)}</>
}
export const CommandItem: React.FC<CommandItemProps> = ({ icon, label, action, payload, shortcut, handleAction }) => (
<Command.Item onSelect={() => handleAction(action, payload)}>
{icon && <LaIcon name={icon} />}
<span>{label}</span>
<HTMLLikeRenderer content={label} />
{shortcut && <CommandShortcut>{shortcut}</CommandShortcut>}
</Command.Item>
)

View File

@@ -14,7 +14,7 @@ import { useCommandActions } from "./hooks/use-command-actions"
let graph_data_promise = import("@/components/routes/public/graph-data.json").then(a => a.default)
const filterItems = (items: CommandItemType[], searchRegex: RegExp) =>
items.filter(item => searchRegex.test(item.label)).slice(0, 6)
items.filter(item => searchRegex.test(item.value)).slice(0, 6)
export function CommandPalette() {
const { me } = useAccount({ root: { personalLinks: [], personalPages: [] } })
@@ -81,6 +81,7 @@ export function CommandPalette() {
heading: "Topics",
items: raw_graph_data.map(topic => ({
icon: "Circle" as const,
value: topic?.prettyName || "",
label: topic?.prettyName || "",
action: () => actions.navigateTo(`/${topic?.name}`)
}))
@@ -94,6 +95,7 @@ export function CommandPalette() {
items:
me?.root.personalLinks?.map(link => ({
icon: "Link" as const,
value: link?.title || "Untitled",
label: link?.title || "Untitled",
action: () => actions.openLinkInNewTab(link?.url || "#")
})) || []
@@ -107,6 +109,7 @@ export function CommandPalette() {
items:
me?.root.personalPages?.map(page => ({
icon: "FileText" as const,
value: page?.title || "Untitled",
label: page?.title || "Untitled",
action: () => actions.navigateTo(`/pages/${page?.id}`)
})) || []

View File

@@ -1,11 +1,14 @@
import type { Config } from "jest"
import nextJest from "next/jest.js"
import dotenv from "dotenv"
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: "./"
})
dotenv.config({ path: ".env.local" })
// Add any custom config to be passed to Jest
const config: Config = {
coverageProvider: "v8",
@@ -13,7 +16,8 @@ const config: Config = {
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true
clearMocks: true,
moduleDirectories: ["node_modules", __dirname]
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async

View File

@@ -0,0 +1,45 @@
import React from "react"
import { render } from "@testing-library/react"
import { HTMLLikeElement, renderHTMLLikeElement } from "./htmlLikeElementUtil"
const HTMLLikeRenderer: React.FC<{ content: HTMLLikeElement | string }> = ({ content }) => {
return <>{renderHTMLLikeElement(content)}</>
}
describe("HTML-like Element Utility", () => {
test("HTMLLikeRenderer renders simple string content", () => {
const { getByText } = render(<HTMLLikeRenderer content="Hello, World!" />)
expect(getByText("Hello, World!")).toBeTruthy()
})
test("HTMLLikeRenderer renders HTML-like structure", () => {
const content: HTMLLikeElement = {
tag: "div",
attributes: { className: "test-class" },
children: ["Hello, ", { tag: "strong", children: ["World"] }, "!"]
}
const { container, getByText } = render(<HTMLLikeRenderer content={content} />)
expect(container.firstChild).toHaveProperty("className", "test-class")
const strongElement = getByText("World")
expect(strongElement.tagName.toLowerCase()).toBe("strong")
})
test("HTMLLikeRenderer handles multiple attributes", () => {
const content: HTMLLikeElement = {
tag: "div",
attributes: {
className: "test-class",
id: "test-id",
"data-testid": "custom-element",
style: { color: "red", fontSize: "16px" }
},
children: ["Test Content"]
}
const { getByTestId } = render(<HTMLLikeRenderer content={content} />)
const element = getByTestId("custom-element")
expect(element.className).toBe("test-class")
expect(element.id).toBe("test-id")
expect(element.style.color).toBe("red")
expect(element.style.fontSize).toBe("16px")
})
})

View File

@@ -0,0 +1,21 @@
import React from "react"
export type HTMLAttributes = React.HTMLAttributes<HTMLElement> & {
[key: string]: any
}
export type HTMLLikeElement = {
tag: keyof JSX.IntrinsicElements
attributes?: HTMLAttributes
children?: (HTMLLikeElement | string)[]
}
export const renderHTMLLikeElement = (element: HTMLLikeElement | string): React.ReactNode => {
if (typeof element === "string") {
return element
}
const { tag, attributes = {}, children = [] } = element
return React.createElement(tag, attributes, ...children.map(child => renderHTMLLikeElement(child)))
}

View File

@@ -37,3 +37,4 @@ export function shuffleArray<T>(array: T[]): T[] {
export * from "./urls"
export * from "./slug"
export * from "./keyboard"
export * from "./htmlLikeElementUtil"

View File

@@ -70,8 +70,8 @@
"date-fns": "^3.6.0",
"framer-motion": "^11.5.4",
"geist": "^1.3.1",
"jazz-react": "0.7.35-guest-auth.5",
"jazz-browser-auth-clerk": "0.7.35-guest-auth.5",
"jazz-react": "0.7.35-guest-auth.5",
"jazz-react-auth-clerk": "0.7.35-guest-auth.5",
"jazz-tools": "0.7.35-guest-auth.5",
"jotai": "^2.9.3",
@@ -91,7 +91,6 @@
"streaming-markdown": "^0.0.14",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"ts-node": "^10.9.2",
"zod": "^3.23.8",
"zsa": "^0.6.0"
},
@@ -102,6 +101,7 @@
"@types/node": "^22.5.4",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"eslint-config-next": "14.2.5",
"jest": "^29.7.0",
@@ -110,6 +110,7 @@
"prettier-plugin-tailwindcss": "^0.6.6",
"tailwindcss": "^3.4.10",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.5.4"
}
}