Skip to content

Commit d10da4d

Browse files
authored
Merge pull request #6 from pheralb/next
✨ Add search + improve light/dark mode
2 parents de362be + 53addeb commit d10da4d

File tree

10 files changed

+800
-3769
lines changed

10 files changed

+800
-3769
lines changed

docs/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
"@astrojs/mdx": "3.1.3",
1616
"@astrojs/react": "3.6.1",
1717
"@astrojs/tailwind": "5.1.0",
18+
"@radix-ui/react-dialog": "1.1.1",
1819
"@radix-ui/react-dropdown-menu": "2.1.1",
1920
"@radix-ui/react-icons": "1.3.0",
2021
"@radix-ui/react-slot": "1.1.0",
2122
"astro": "4.13.0",
2223
"class-variance-authority": "0.7.0",
2324
"clsx": "2.1.1",
25+
"cmdk": "1.0.0",
2426
"fast-npm-meta": "0.0.1",
2527
"js-confetti": "0.12.0",
2628
"lucide-react": "0.399.0",

docs/src/components/header.astro

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { cn } from '@/utils';
33
import { SocialLinks } from '@/docs.config';
44
55
import { buttonVariants } from '@/ui/button';
6+
67
import { Logo } from '@/components/icons';
78
import MobileMenu from '@/components/mobileMenu';
89
import { ModeToggle } from '@/components/theme/modeToggle';
10+
import SearchModal from '@/components/search/searchModal';
911
---
1012

1113
<nav
@@ -48,7 +50,8 @@ import { ModeToggle } from '@/components/theme/modeToggle';
4850
)),
4951
)
5052
}
51-
<ModeToggle client:load />
53+
<SearchModal client:only="react" />
54+
<ModeToggle client:only="react" />
5255
</div>
5356
</div>
5457
</nav>
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { allRoutes } from '@/docs.config';
2+
import { Button } from '@/ui/button';
3+
4+
import {
5+
CommandDialog,
6+
CommandEmpty,
7+
CommandGroup,
8+
CommandInput,
9+
CommandItem,
10+
CommandList,
11+
} from '@/ui/command';
12+
import { FileIcon, SearchIcon } from 'lucide-react';
13+
import { useEffect, useState } from 'react';
14+
15+
const SearchModal = () => {
16+
const [open, setOpen] = useState<boolean>(false);
17+
18+
useEffect(() => {
19+
const down = (e: KeyboardEvent) => {
20+
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
21+
e.preventDefault();
22+
setOpen((open) => !open);
23+
}
24+
};
25+
document.addEventListener('keydown', down);
26+
return () => document.removeEventListener('keydown', down);
27+
}, []);
28+
29+
return (
30+
<>
31+
<Button
32+
variant="ghost"
33+
size="icon"
34+
title="Search"
35+
onClick={() => setOpen(true)}
36+
>
37+
<SearchIcon size={20} strokeWidth={1.5} />
38+
<span className="sr-only">Toggle theme</span>
39+
</Button>
40+
<CommandDialog open={open} onOpenChange={setOpen}>
41+
<CommandInput placeholder="Search..." />
42+
<CommandList>
43+
<CommandEmpty>No results found.</CommandEmpty>
44+
{allRoutes.map((doc) => (
45+
<>
46+
<CommandGroup
47+
key={doc.category}
48+
heading={doc.category}
49+
title={doc.category}
50+
>
51+
{doc.routes.map((route) => (
52+
<CommandItem
53+
key={route.path}
54+
title={route.title}
55+
onSelect={() => {
56+
setOpen(false);
57+
window.location.href = route.path;
58+
}}
59+
>
60+
{route.icon ? (
61+
<route.icon width={14} height={14} strokeWidth={1.5} />
62+
) : (
63+
<FileIcon size={14} strokeWidth={1.5} />
64+
)}
65+
<span>{route.title}</span>
66+
</CommandItem>
67+
))}
68+
</CommandGroup>
69+
</>
70+
))}
71+
</CommandList>
72+
</CommandDialog>
73+
</>
74+
);
75+
};
76+
77+
export default SearchModal;

docs/src/components/theme/modeToggle.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function ModeToggle() {
3232
const applyTheme = (theme: 'light' | 'dark') => {
3333
setToastTheme(theme);
3434
document.documentElement.classList.toggle('dark', theme === 'dark');
35+
document.documentElement.style.colorScheme = theme;
3536
localStorage.setItem('theme', theme);
3637
};
3738

docs/src/content/config.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { z, defineCollection } from 'astro:content';
22

3+
const docsSchema = z.object({
4+
title: z.string(),
5+
description: z.string(),
6+
category: z.string(),
7+
});
8+
9+
export type Docs = z.infer<typeof docsSchema>;
10+
311
const docsCollection = defineCollection({
412
type: 'content',
5-
schema: z.object({
6-
title: z.string(),
7-
description: z.string(),
8-
category: z.string(),
9-
}),
13+
schema: docsSchema,
1014
});
1115

1216
export const collections = {

docs/src/docs.config.ts

+4-18
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { SVGProps } from 'react';
22
import { X, Github, Nextjs, Remix, Astro } from './components/icons';
33

4-
interface iDocsRoutes {
4+
export interface iDocsRoutes {
55
category: string;
66
routes: {
77
title: string;
@@ -61,23 +61,7 @@ export const SidebarRoutes: iDocsRoutes[] = [
6161
},
6262
],
6363
},
64-
{
65-
category: 'Framework Guides',
66-
routes: [
67-
{
68-
title: 'Astro',
69-
path: '/astro',
70-
},
71-
{
72-
title: 'Next.js',
73-
path: '/nextjs',
74-
},
75-
{
76-
title: 'Remix',
77-
path: '/remix',
78-
},
79-
],
80-
},
64+
...FrameworkGuides,
8165
{
8266
category: 'API',
8367
routes: [
@@ -101,3 +85,5 @@ export const SidebarRoutes: iDocsRoutes[] = [
10185
],
10286
},
10387
];
88+
89+
export const allRoutes = [...SidebarRoutes, ...SocialLinks];

docs/src/ui/command.tsx

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import * as React from 'react';
2+
import { type DialogProps } from '@radix-ui/react-dialog';
3+
import { Command as CommandPrimitive } from 'cmdk';
4+
5+
import { cn } from '@/utils';
6+
import { Dialog, DialogContent } from '@/ui/dialog';
7+
8+
const Command = React.forwardRef<
9+
React.ElementRef<typeof CommandPrimitive>,
10+
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
11+
>(({ className, ...props }, ref) => (
12+
<CommandPrimitive
13+
ref={ref}
14+
className={cn(
15+
'flex h-full w-full flex-col overflow-hidden rounded-md bg-white text-neutral-950 dark:bg-neutral-900 dark:text-neutral-50',
16+
className,
17+
)}
18+
{...props}
19+
/>
20+
));
21+
Command.displayName = CommandPrimitive.displayName;
22+
23+
interface CommandDialogProps extends DialogProps {}
24+
25+
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
26+
return (
27+
<Dialog {...props}>
28+
<DialogContent className="overflow-hidden p-0">
29+
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-neutral-500 dark:[&_[cmdk-group-heading]]:text-neutral-400 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-[18px] [&_[cmdk-item]_svg]:w-[18px]">
30+
{children}
31+
</Command>
32+
</DialogContent>
33+
</Dialog>
34+
);
35+
};
36+
37+
const CommandInput = React.forwardRef<
38+
React.ElementRef<typeof CommandPrimitive.Input>,
39+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
40+
>(({ className, ...props }, ref) => (
41+
<div className="flex items-center px-3" cmdk-input-wrapper="">
42+
<CommandPrimitive.Input
43+
ref={ref}
44+
className={cn(
45+
'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-neutral-500 disabled:cursor-not-allowed disabled:opacity-50 dark:placeholder:text-neutral-400',
46+
className,
47+
)}
48+
{...props}
49+
/>
50+
</div>
51+
));
52+
53+
CommandInput.displayName = CommandPrimitive.Input.displayName;
54+
55+
const CommandList = React.forwardRef<
56+
React.ElementRef<typeof CommandPrimitive.List>,
57+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
58+
>(({ className, ...props }, ref) => (
59+
<CommandPrimitive.List
60+
ref={ref}
61+
className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
62+
{...props}
63+
/>
64+
));
65+
66+
CommandList.displayName = CommandPrimitive.List.displayName;
67+
68+
const CommandEmpty = React.forwardRef<
69+
React.ElementRef<typeof CommandPrimitive.Empty>,
70+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
71+
>((props, ref) => (
72+
<CommandPrimitive.Empty
73+
ref={ref}
74+
className="py-6 text-center text-sm"
75+
{...props}
76+
/>
77+
));
78+
79+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
80+
81+
const CommandGroup = React.forwardRef<
82+
React.ElementRef<typeof CommandPrimitive.Group>,
83+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
84+
>(({ className, ...props }, ref) => (
85+
<CommandPrimitive.Group
86+
ref={ref}
87+
className={cn(
88+
'overflow-hidden p-1 text-neutral-950 dark:text-neutral-50 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-neutral-500 dark:[&_[cmdk-group-heading]]:text-neutral-400',
89+
className,
90+
)}
91+
{...props}
92+
/>
93+
));
94+
95+
CommandGroup.displayName = CommandPrimitive.Group.displayName;
96+
97+
const CommandSeparator = React.forwardRef<
98+
React.ElementRef<typeof CommandPrimitive.Separator>,
99+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
100+
>(({ className, ...props }, ref) => (
101+
<CommandPrimitive.Separator
102+
ref={ref}
103+
className={cn(
104+
'-mx-1 my-2 h-px bg-neutral-200 dark:bg-neutral-800',
105+
className,
106+
)}
107+
{...props}
108+
/>
109+
));
110+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
111+
112+
const CommandItem = React.forwardRef<
113+
React.ElementRef<typeof CommandPrimitive.Item>,
114+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
115+
>(({ className, ...props }, ref) => (
116+
<CommandPrimitive.Item
117+
ref={ref}
118+
className={cn(
119+
'flex cursor-default items-center gap-2 rounded-md px-2 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-neutral-100 data-[selected=true]:text-neutral-900 data-[disabled=true]:opacity-50 dark:data-[selected=true]:bg-neutral-800 dark:data-[selected=true]:text-neutral-50',
120+
className,
121+
)}
122+
{...props}
123+
/>
124+
));
125+
126+
CommandItem.displayName = CommandPrimitive.Item.displayName;
127+
128+
const CommandShortcut = ({
129+
className,
130+
...props
131+
}: React.HTMLAttributes<HTMLSpanElement>) => {
132+
return (
133+
<span
134+
className={cn(
135+
'ml-auto text-xs tracking-widest text-neutral-500 dark:text-neutral-400',
136+
className,
137+
)}
138+
{...props}
139+
/>
140+
);
141+
};
142+
CommandShortcut.displayName = 'CommandShortcut';
143+
144+
export {
145+
Command,
146+
CommandDialog,
147+
CommandInput,
148+
CommandList,
149+
CommandEmpty,
150+
CommandGroup,
151+
CommandItem,
152+
CommandShortcut,
153+
CommandSeparator,
154+
};

0 commit comments

Comments
 (0)