Skip to content

Commit 5405ce2

Browse files
committed
main 🧊 rework header for doc
1 parent 7fb4b32 commit 5405ce2

65 files changed

Lines changed: 2718 additions & 283 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎packages/newdocs/.tmp-header-template.html‎

Lines changed: 807 additions & 0 deletions
Large diffs are not rendered by default.

‎packages/newdocs/ReactUse Header.html‎

Lines changed: 183 additions & 0 deletions
Large diffs are not rendered by default.

‎packages/newdocs/app/(docs)/_components/index.ts‎

Whitespace-only changes.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Icons } from '@docs/components/icons';
2+
import { functionsSource, source } from '@docs/lib/source';
3+
import { CONFIG, LINKS } from '@docs/src/constants';
4+
import fetches from '@siberiacancode/fetches';
5+
import Image from 'next/image';
6+
import Link from 'next/link';
7+
8+
import { Button } from '@/src/components/ui';
9+
10+
import { Burger, Search, ThemeButton } from './components';
11+
12+
const formatStarsCount = (count: number) => {
13+
if (count < 1000) return count;
14+
return `${Math.round(count / 1000)}${count >= 1000 ? 'k' : ''}`;
15+
};
16+
17+
export const Header = async () => {
18+
const repositoryResponse = await fetches.get<{ stargazers_count: number }>(
19+
'https://api.github.com/repos/siberiacancode/reactuse',
20+
{
21+
next: { revalidate: 86400 }
22+
}
23+
);
24+
25+
const formattedCount = formatStarsCount(repositoryResponse.data.stargazers_count);
26+
27+
return (
28+
<header className='bg-background/95 supports-[backdrop-filter]:bg-background/80 sticky top-0 z-50 w-full backdrop-blur'>
29+
<div className='container-wrapper flex h-(--header-height) items-center justify-between gap-3 px-6'>
30+
<Burger
31+
className='lg:hidden'
32+
items={[{ href: '/docs/installation', label: 'Get started' }]}
33+
/>
34+
35+
<div className='hidden min-w-0 items-center justify-between gap-3 lg:flex'>
36+
<Link className='inline-flex items-center gap-2' href='/'>
37+
<Image alt='ReactUse' height={14} src='/logo.svg' width={14} />
38+
39+
<span className='text-foreground text-xl font-semibold tracking-tight'>
40+
{CONFIG.NAME}
41+
</span>
42+
</Link>
43+
44+
<Button asChild className='rounded-full' size='sm' variant='ghost'>
45+
<Link href='/docs/installation'>Docs</Link>
46+
</Button>
47+
</div>
48+
49+
<div className='flex min-w-0 items-center justify-end gap-2'>
50+
<div className='hidden md:block'>
51+
<Search tree={[...source.pageTree.children, ...functionsSource.pageTree.children]} />
52+
</div>
53+
54+
<div className='flex items-center gap-1'>
55+
<Button asChild className='rounded-full' variant='ghost'>
56+
<Link href={LINKS.GITHUB} rel='noreferrer' target='_blank'>
57+
<Icons.gitHub className='size-4.5' />
58+
59+
<span className='text-muted-foreground text-xs tabular-nums'>{formattedCount}</span>
60+
</Link>
61+
</Button>
62+
63+
<Button asChild className='rounded-full' size='icon' variant='ghost'>
64+
<Link href={LINKS.NPM} rel='noreferrer' target='_blank'>
65+
<Icons.npm className='size-4.5' />
66+
</Link>
67+
</Button>
68+
69+
<ThemeButton />
70+
</div>
71+
</div>
72+
</div>
73+
</header>
74+
);
75+
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use client';
2+
3+
import type { ComponentProps } from 'react';
4+
5+
import { TOP_LEVEL_SECTIONS } from '@docs/components/docs-sidebar';
6+
import { cn } from '@docs/lib/utils';
7+
import { Button } from '@docs/ui/button';
8+
import { Popover, PopoverContent, PopoverTrigger } from '@docs/ui/popover';
9+
import { useDisclosure } from '@siberiacancode/reactuse';
10+
import Link from 'next/link';
11+
12+
interface BurgerProps extends ComponentProps<typeof Button> {
13+
items: { href: string; label: string }[];
14+
}
15+
16+
export const Burger = ({ items, className, ...props }: BurgerProps) => {
17+
const burger = useDisclosure(false);
18+
19+
return (
20+
<Popover open={burger.opened} onOpenChange={burger.toggle}>
21+
<PopoverTrigger asChild>
22+
<Button
23+
className={cn(
24+
'extend-touch-target h-8 touch-manipulation items-center justify-start gap-2.5 !p-0 hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 active:bg-transparent dark:hover:bg-transparent',
25+
className
26+
)}
27+
{...props}
28+
variant='ghost'
29+
>
30+
<div className='relative flex h-8 w-4 items-center justify-center'>
31+
<div className='relative size-4'>
32+
<span
33+
className={cn(
34+
'bg-foreground absolute left-0 block h-0.5 w-4 transition-all duration-100',
35+
burger.opened ? 'top-[0.4rem] -rotate-45' : 'top-1'
36+
)}
37+
/>
38+
<span
39+
className={cn(
40+
'bg-foreground absolute left-0 block h-0.5 w-4 transition-all duration-100',
41+
burger.opened ? 'top-[0.4rem] rotate-45' : 'top-2.5'
42+
)}
43+
/>
44+
</div>
45+
<span className='sr-only'>Toggle Menu</span>
46+
</div>
47+
<span className='flex h-8 items-center text-lg leading-none font-medium'>Menu</span>
48+
</Button>
49+
</PopoverTrigger>
50+
<PopoverContent
51+
align='start'
52+
alignOffset={-16}
53+
className='bg-background/90 no-scrollbar h-(--radix-popper-available-height) w-(--radix-popper-available-width) overflow-y-auto rounded-none border-none p-0 shadow-none backdrop-blur duration-100 data-open:animate-none!'
54+
side='bottom'
55+
sideOffset={6}
56+
>
57+
<div className='flex flex-col gap-12 overflow-auto px-6 py-6'>
58+
<div className='flex flex-col gap-4'>
59+
<div className='text-muted-foreground text-sm font-medium'>Menu</div>
60+
<div className='flex flex-col gap-3'>
61+
<Link className='text-2xl font-medium' href='/' onClick={burger.toggle}>
62+
Home
63+
</Link>
64+
{items.map((item) => (
65+
<Link
66+
key={item.href}
67+
className='text-2xl font-medium'
68+
href={item.href}
69+
onClick={burger.toggle}
70+
>
71+
{item.label}
72+
</Link>
73+
))}
74+
</div>
75+
</div>
76+
<div className='flex flex-col gap-4'>
77+
<div className='text-muted-foreground text-sm font-medium'>Sections</div>
78+
<div className='flex flex-col gap-3'>
79+
{TOP_LEVEL_SECTIONS.map(({ name, href }) => (
80+
<Link
81+
key={name}
82+
className='text-2xl font-medium'
83+
href={href}
84+
onClick={burger.toggle}
85+
>
86+
{name}
87+
</Link>
88+
))}
89+
</div>
90+
</div>
91+
</div>
92+
</PopoverContent>
93+
</Popover>
94+
);
95+
};
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
'use client';
2+
3+
import type { source } from '@docs/lib/source';
4+
5+
import { useDisclosure } from '@siberiacancode/reactuse';
6+
import { liteClient } from 'algoliasearch/lite';
7+
import { useDocsSearch } from 'fumadocs-core/search/client';
8+
import { ArrowRightIcon, CircleDashedIcon, Loader2Icon } from 'lucide-react';
9+
import Link from 'next/link';
10+
import { useRouter } from 'next/navigation';
11+
12+
import {
13+
Button,
14+
Command,
15+
CommandEmpty,
16+
CommandGroup,
17+
CommandInput,
18+
CommandItem,
19+
CommandList,
20+
Dialog,
21+
DialogContent,
22+
DialogDescription,
23+
DialogHeader,
24+
DialogTitle,
25+
DialogTrigger
26+
} from '@/src/components/ui';
27+
import { CONFIG } from '@/src/constants';
28+
import { cn } from '@/src/lib';
29+
30+
const client = liteClient(CONFIG.ALGOLIA.APP_ID, CONFIG.ALGOLIA.API_KEY);
31+
32+
interface Props {
33+
tree: typeof source.pageTree.children;
34+
}
35+
36+
export const Search = (props: Props) => {
37+
const dialog = useDisclosure();
38+
const router = useRouter();
39+
const { setSearch, query } = useDocsSearch({
40+
type: 'algolia',
41+
client,
42+
indexName: CONFIG.ALGOLIA.INDEX_NAME,
43+
locale: 'en'
44+
});
45+
46+
return (
47+
<Dialog open={dialog.opened} onOpenChange={dialog.toggle}>
48+
<DialogTrigger asChild>
49+
<Button
50+
className={cn(
51+
'dark:bg-card text-muted-foreground hover:text-foreground hover:bg-muted/70 border-input bg-muted/40 relative h-8 justify-start rounded-lg border px-2.5 font-normal shadow-none md:w-42 lg:w-62'
52+
)}
53+
variant='outline'
54+
onClick={dialog.toggle}
55+
>
56+
<span className='truncate text-sm'>Search docs...</span>
57+
</Button>
58+
</DialogTrigger>
59+
<DialogContent className='rounded-xl border-none bg-clip-padding p-2 pb-11 shadow-2xl ring-4 ring-neutral-200/80 dark:bg-neutral-900 dark:ring-neutral-800'>
60+
<DialogHeader className='sr-only'>
61+
<DialogTitle>Search documentation...</DialogTitle>
62+
<DialogDescription>Search for a command to run...</DialogDescription>
63+
</DialogHeader>
64+
<Command className='**:data-[slot=command-input-wrapper]:bg-input/50 **:data-[slot=command-input-wrapper]:border-input rounded-none bg-transparent **:data-[slot=command-input]:!h-9 **:data-[slot=command-input]:py-0 **:data-[slot=command-input-wrapper]:mb-0 **:data-[slot=command-input-wrapper]:!h-9 **:data-[slot=command-input-wrapper]:rounded-md **:data-[slot=command-input-wrapper]:border'>
65+
<div className='relative'>
66+
<CommandInput placeholder='Search documentation...' onValueChange={setSearch} />
67+
{query.isLoading && (
68+
<div className='pointer-events-none absolute top-1/2 right-3 z-10 flex -translate-y-1/2 items-center justify-center'>
69+
<Loader2Icon className='text-muted-foreground size-4 animate-spin' />
70+
</div>
71+
)}
72+
</div>
73+
<CommandList className='no-scrollbar mt-5 max-h-80 min-h-80 scroll-pt-2 scroll-pb-1.5'>
74+
<CommandEmpty className='text-muted-foreground py-12 text-center text-sm'>
75+
{query.isLoading ? 'Searching...' : 'No results found.'}
76+
</CommandEmpty>
77+
<CommandGroup className='!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1'>
78+
{Array.isArray(query.data) &&
79+
query.data.map((item) => (
80+
<CommandItem
81+
key={item.id}
82+
className='cursor-pointer'
83+
keywords={['nav', 'navigation', item.content.toLowerCase()]}
84+
value={`Navigation ${item.content}`}
85+
onSelect={() => router.push(item.url)}
86+
>
87+
{item.content}
88+
</CommandItem>
89+
))}
90+
</CommandGroup>
91+
{props.tree.map((group) => {
92+
if (group.type !== 'folder') return null;
93+
const isFunction = group.$id === 'helpers' || group.$id === 'hooks';
94+
95+
return (
96+
<CommandGroup
97+
key={group.$id}
98+
className='!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1'
99+
heading={group.name}
100+
>
101+
{group.children
102+
.filter((child) => child.type === 'page')
103+
.map((item) => (
104+
<CommandItem
105+
asChild
106+
key={item.url}
107+
className='cursor-pointer'
108+
value={item.name!.toString()}
109+
>
110+
<div>
111+
{!isFunction && <ArrowRightIcon />}
112+
{isFunction && <CircleDashedIcon />}
113+
<Link href={item.url}>{item.name!.toString()}</Link>
114+
</div>
115+
</CommandItem>
116+
))}
117+
</CommandGroup>
118+
);
119+
})}
120+
</CommandList>
121+
</Command>
122+
</DialogContent>
123+
</Dialog>
124+
);
125+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use client';
2+
3+
import type { ComponentProps, MouseEvent } from 'react';
4+
5+
import { useTheme } from '@/app/_contexts/theme';
6+
import { Button } from '@/src/components/ui';
7+
8+
type ThemeButtonProps = ComponentProps<typeof Button>;
9+
10+
export const ThemeButton = (props: ThemeButtonProps) => {
11+
const theme = useTheme();
12+
13+
const onThemeClick = async (event: MouseEvent<HTMLButtonElement>) => {
14+
console.log('onThemeClick', theme.value);
15+
const x = event.clientX;
16+
const y = event.clientY;
17+
theme.animate(x, y, theme.value === 'dark' ? 'light' : 'dark');
18+
};
19+
20+
return (
21+
<Button className='rounded-full' size='icon' variant='ghost' onClick={onThemeClick} {...props}>
22+
<svg
23+
className='size-5'
24+
fill='none'
25+
height='24'
26+
stroke='currentColor'
27+
strokeLinecap='round'
28+
strokeLinejoin='round'
29+
strokeWidth='2'
30+
viewBox='0 0 24 24'
31+
width='24'
32+
xmlns='http://www.w3.org/2000/svg'
33+
>
34+
<path d='M0 0h24v24H0z' fill='none' stroke='none' />
35+
<path d='M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0' />
36+
<path d='M12 3l0 18' />
37+
<path d='M12 9l4.65 -4.65' />
38+
<path d='M12 14.3l7.37 -7.37' />
39+
<path d='M12 19.6l8.85 -8.85' />
40+
</svg>
41+
<span className='sr-only'>Toggle theme</span>
42+
</Button>
43+
);
44+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './Burger/Burger';
2+
export * from './Search/Search';
3+
export * from './ThemeButton/ThemeButton';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Header/Header';
File renamed without changes.

0 commit comments

Comments
 (0)