Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit 87f23cc

Browse files
committed
feat: add search to header
1 parent 977b3d8 commit 87f23cc

File tree

5 files changed

+95
-96
lines changed

5 files changed

+95
-96
lines changed

src/components/header.tsx

+8-70
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { Center, Container, createStyles, Group, Header, Menu, Title, UnstyledButton } from '@mantine/core';
2-
import { NextLink } from '@mantine/next';
1+
import { Box, Container, createStyles, Group, Header, Title, UnstyledButton } from '@mantine/core';
32
import Image from 'next/image';
43
import Link from 'next/link';
5-
import { AiOutlineDown } from 'react-icons/ai';
4+
import { SearchControl } from './headerSearch';
65

76
const useStyles = createStyles((theme) => ({
87
header: {
@@ -25,66 +24,15 @@ const useStyles = createStyles((theme) => ({
2524
marginTop: '10px',
2625
color: theme.colors.gray[0],
2726
},
28-
29-
links: {},
30-
31-
link: {
32-
display: 'block',
33-
lineHeight: 1,
34-
padding: '8px 12px',
35-
borderRadius: theme.radius.sm,
36-
textDecoration: 'none',
37-
color: theme.white,
38-
fontSize: theme.fontSizes.sm,
39-
fontWeight: 500,
40-
41-
'&:hover': {
42-
backgroundColor: theme.fn.lighten(theme.colors.red[8], 0.2),
43-
},
44-
},
45-
46-
linkLabel: {
47-
marginRight: 5,
48-
},
4927
}));
5028

51-
interface HeaderSearchProps {
52-
links: { link: string; label: string; links?: { link: string; label: string }[] }[];
53-
}
54-
55-
export function KaizokuHeader({ links }: HeaderSearchProps) {
29+
export function KaizokuHeader() {
5630
const { classes } = useStyles();
5731

58-
const items = links.map((link) => {
59-
const menuItems = link.links?.map((item) => <Menu.Item key={item.link}>{item.label}</Menu.Item>);
60-
61-
if (menuItems) {
62-
return (
63-
<Menu key={link.label} trigger="hover" exitTransitionDuration={0}>
64-
<Menu.Target>
65-
<a href={link.link} className={classes.link} onClick={(event) => event.preventDefault()}>
66-
<Center>
67-
<span className={classes.linkLabel}>{link.label}</span>
68-
<AiOutlineDown size={12} strokeWidth={1.5} />
69-
</Center>
70-
</a>
71-
</Menu.Target>
72-
<Menu.Dropdown>{menuItems}</Menu.Dropdown>
73-
</Menu>
74-
);
75-
}
76-
77-
return (
78-
<NextLink target="_blank" key={link.label} href={link.link} className={classes.link}>
79-
{link.label}
80-
</NextLink>
81-
);
82-
});
83-
8432
return (
8533
<Header height={56} className={classes.header} mb={120}>
8634
<Container fluid>
87-
<div className={classes.inner}>
35+
<Box className={classes.inner}>
8836
<Link href="/">
8937
<UnstyledButton component="a">
9038
<Group spacing={10}>
@@ -95,22 +43,12 @@ export function KaizokuHeader({ links }: HeaderSearchProps) {
9543
</Group>
9644
</UnstyledButton>
9745
</Link>
98-
<Group spacing={5} className={classes.links}>
99-
{items}
46+
47+
<Group spacing={5}>
48+
<SearchControl />
10049
</Group>
101-
</div>
50+
</Box>
10251
</Container>
10352
</Header>
10453
);
10554
}
106-
107-
export const KaizokuLinks = [
108-
{
109-
link: '/admin/queues/queue/downloadQueue?status=completed',
110-
label: 'Downloads',
111-
},
112-
{
113-
link: '/',
114-
label: 'Settings',
115-
},
116-
];

src/components/headerSearch.tsx

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { createStyles, Grid, Group, Image, Kbd, Text, UnstyledButton } from '@mantine/core';
2+
import { openSpotlight, SpotlightAction, SpotlightProvider } from '@mantine/spotlight';
3+
import { IconSearch } from '@tabler/icons';
4+
import { useRouter } from 'next/router';
5+
import { useEffect, useState } from 'react';
6+
import { trpc } from '../utils/trpc';
7+
8+
const useStyles = createStyles((theme, { disabled }: { disabled: boolean }) => ({
9+
root: {
10+
height: 34,
11+
width: 250,
12+
paddingLeft: theme.spacing.sm,
13+
paddingRight: 10,
14+
borderRadius: theme.radius.sm,
15+
color: theme.colors.gray[5],
16+
backgroundColor: disabled ? theme.colors.gray[3] : theme.white,
17+
cursor: disabled ? 'not-allowed' : 'pointer',
18+
outline: '0 !important',
19+
},
20+
}));
21+
22+
export function SearchControl() {
23+
const [actions, setActions] = useState<SpotlightAction[]>([]);
24+
const router = useRouter();
25+
const mangaQuery = trpc.manga.query.useQuery();
26+
const isDisabled = !mangaQuery.data || mangaQuery.data.length === 0;
27+
const { classes, cx } = useStyles({ disabled: isDisabled });
28+
29+
useEffect(() => {
30+
if (mangaQuery.data) {
31+
setActions(
32+
mangaQuery.data.map((m) => ({
33+
title: m.title,
34+
description: m.title,
35+
group: m.source,
36+
icon: (
37+
<Image
38+
withPlaceholder
39+
placeholder={<Image src="/cover-not-found.jpg" alt={m.title} width={63} height={96} />}
40+
src={m.cover}
41+
width={42}
42+
height={64}
43+
/>
44+
),
45+
closeOnTrigger: true,
46+
onTrigger: () => router.push(`/manga/${m.id}`),
47+
})),
48+
);
49+
}
50+
}, [mangaQuery.data, router]);
51+
52+
return (
53+
<SpotlightProvider
54+
actions={actions}
55+
searchIcon={<IconSearch size={18} />}
56+
highlightQuery
57+
disabled={isDisabled}
58+
searchPlaceholder="Search..."
59+
shortcut="ctrl + p"
60+
nothingFoundMessage="Nothing found..."
61+
>
62+
<UnstyledButton className={cx(classes.root)} onClick={() => openSpotlight()} disabled={isDisabled}>
63+
<Grid gutter={5}>
64+
<Grid.Col span="content" style={{ display: 'flex', alignItems: 'center' }}>
65+
<IconSearch size={14} stroke={1.5} />
66+
</Grid.Col>
67+
<Grid.Col span="auto" style={{ display: 'flex', alignItems: 'center' }}>
68+
<Text size="sm" color="dimmed">
69+
Search
70+
</Text>
71+
</Grid.Col>
72+
<Grid.Col span="content">
73+
<Group spacing={5}>
74+
<Kbd py={0}>Ctrl</Kbd>+<Kbd py={0}>P</Kbd>
75+
</Group>
76+
</Grid.Col>
77+
</Grid>
78+
</UnstyledButton>
79+
</SpotlightProvider>
80+
);
81+
}

src/pages/_app.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ModalsProvider } from '@mantine/modals';
33
import { NotificationsProvider } from '@mantine/notifications';
44
import type { AppProps } from 'next/app';
55
import Head from 'next/head';
6-
import { KaizokuHeader, KaizokuLinks } from '../components/header';
6+
import { KaizokuHeader } from '../components/header';
77
import { KaizokuNavbar } from '../components/navbar';
88
import '../styles/globals.css';
99
import { trpc } from '../utils/trpc';
@@ -29,7 +29,7 @@ function MyApp({ Component, pageProps }: AppProps) {
2929
<AppShell
3030
padding="md"
3131
navbar={<KaizokuNavbar />}
32-
header={<KaizokuHeader links={KaizokuLinks} />}
32+
header={<KaizokuHeader />}
3333
styles={(theme) => ({
3434
main: { backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0] },
3535
})}

src/pages/index.tsx

+1-11
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,7 @@ export default function IndexPage() {
1313
const mangaRemove = trpc.manga.remove.useMutation();
1414
const router = useRouter();
1515

16-
const libraryId = libraryQuery.data?.id;
17-
18-
const mangaQuery = trpc.manga.query.useQuery(
19-
{
20-
library: libraryId!,
21-
},
22-
{
23-
staleTime: Infinity,
24-
enabled: libraryId !== undefined,
25-
},
26-
);
16+
const mangaQuery = trpc.manga.query.useQuery();
2717

2818
if (mangaQuery.isLoading || libraryQuery.isLoading) {
2919
return (

src/server/trpc/router/manga.ts

+3-13
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,9 @@ import { getAvailableSources, getMangaDetail, removeManga, search } from '../../
88
import { t } from '../trpc';
99

1010
export const mangaRouter = t.router({
11-
query: t.procedure
12-
.input(
13-
z.object({
14-
library: z.number(),
15-
}),
16-
)
17-
.query(async ({ input, ctx }) => {
18-
return ctx.prisma.manga.findMany({
19-
where: {
20-
libraryId: input.library,
21-
},
22-
});
23-
}),
11+
query: t.procedure.query(async ({ ctx }) => {
12+
return ctx.prisma.manga.findMany();
13+
}),
2414
sources: t.procedure.query(async () => {
2515
return getAvailableSources();
2616
}),

0 commit comments

Comments
 (0)