added search
This commit is contained in:
parent
0a891f065d
commit
fca82def36
107
components/server/server-search.tsx
Normal file
107
components/server/server-search.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Search } from "lucide-react";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandList
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
interface ServerSearchProps {
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
type: "channel" | "member";
|
||||||
|
data: {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
}[] | undefined
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServerSearch = ({
|
||||||
|
data
|
||||||
|
}: ServerSearchProps) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
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 onClick = ({ id, type }: { id: string, type: "channel" | "member"}) =>
|
||||||
|
{
|
||||||
|
setOpen(false);
|
||||||
|
|
||||||
|
if (type === "member") {
|
||||||
|
return router.push(`/servers/${params.serverId}/conversations/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "channel") {
|
||||||
|
return router.push(`/servers/${params.serverId}/channels/${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="group px-2 py-2 rounded-md flex items-center gap-x-2 w-full hover:bg-zinc-700/10 dark:hover:bg-zinc-700/50 transition">
|
||||||
|
<Search className="w-4 h-4 text-zinc-500 dark:text-zinc-400"/>
|
||||||
|
<p className="font-semiboldd text-sm text-zinc-500 dark:text-zinc-400 group-hover:text-zinc-600 dark:group-hover:text-zinc-300 transition">
|
||||||
|
Search
|
||||||
|
</p>
|
||||||
|
<kbd
|
||||||
|
className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-forground ml-auto"
|
||||||
|
>
|
||||||
|
<span className="text-xs">CMD</span>K
|
||||||
|
</kbd>
|
||||||
|
</button>
|
||||||
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
|
<CommandInput placeholder="Search all channels" />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>
|
||||||
|
No Results Found
|
||||||
|
</CommandEmpty>
|
||||||
|
{data.map(({ label, type, data }) => {
|
||||||
|
if (!data?.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandGroup key={label} heading={label}>
|
||||||
|
{data?.map(({ id, icon, name }) => {
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
onSelect={() => onClick({ id, type})}
|
||||||
|
key={id}>
|
||||||
|
{icon}
|
||||||
|
<span>{name}</span>
|
||||||
|
|
||||||
|
</CommandItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</CommandList>
|
||||||
|
|
||||||
|
</CommandDialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,15 +1,31 @@
|
|||||||
import { ChannelType } from "@prisma/client";
|
import { ChannelType, MemberRole } from "@prisma/client";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
import { Hash, Mic, ShieldAlert, ShieldCheck, Video } from "lucide-react";
|
||||||
|
|
||||||
import { currentProfile } from "@/lib/current-profile";
|
import { currentProfile } from "@/lib/current-profile";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
|
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { SeverHeader } from "./server-header";
|
import { SeverHeader } from "./server-header";
|
||||||
|
import { ServerSearch } from "./server-search";
|
||||||
|
|
||||||
|
|
||||||
interface ServerSidebarProps {
|
interface ServerSidebarProps {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const iconMap = {
|
||||||
|
[ChannelType.TEXT]: <Hash className="mr-2 h-4 w-4" />,
|
||||||
|
[ChannelType.AUDIO]: <Mic className="mr-2 h-4 w-4" />,
|
||||||
|
[ChannelType.VIDEO]: <Video className="mr-2 h-4 w-4" />
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleIconMap = {
|
||||||
|
[MemberRole.GUEST]: null,
|
||||||
|
[MemberRole.MODERATOR]: <ShieldCheck className="h-4 w-4 mr-2 text-indigo-500" />,
|
||||||
|
[MemberRole.ADMIN]: <ShieldAlert className="w-4 h-4 mr-2 text-rose-500" />
|
||||||
|
}
|
||||||
|
|
||||||
export const ServerSidebar = async ({
|
export const ServerSidebar = async ({
|
||||||
serverId
|
serverId
|
||||||
}: ServerSidebarProps) => {
|
}: ServerSidebarProps) => {
|
||||||
@ -57,6 +73,50 @@ export const ServerSidebar = async ({
|
|||||||
server={server}
|
server={server}
|
||||||
role={role}
|
role={role}
|
||||||
/>
|
/>
|
||||||
|
<ScrollArea className="flex-1 px-3">
|
||||||
|
<div className="mt-2">
|
||||||
|
<ServerSearch
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
label: "Text Channels",
|
||||||
|
type: "channel",
|
||||||
|
data: textChannels?.map((channel) => ({
|
||||||
|
id: channel.id,
|
||||||
|
name: channel.name,
|
||||||
|
icon: iconMap[channel.type],
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Voice Channels",
|
||||||
|
type: "channel",
|
||||||
|
data: audioChannels?.map((channel) => ({
|
||||||
|
id: channel.id,
|
||||||
|
name: channel.name,
|
||||||
|
icon: iconMap[channel.type],
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Video Channels",
|
||||||
|
type: "channel",
|
||||||
|
data: videoChannels?.map((channel) => ({
|
||||||
|
id: channel.id,
|
||||||
|
name: channel.name,
|
||||||
|
icon: iconMap[channel.type],
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Members",
|
||||||
|
type: "member",
|
||||||
|
data: members?.map((member) => ({
|
||||||
|
id: member.id,
|
||||||
|
name: member.profile.name,
|
||||||
|
icon: roleIconMap[member.role],
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
155
components/ui/command.tsx
Normal file
155
components/ui/command.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { DialogProps } from "@radix-ui/react-dialog"
|
||||||
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
|
import { Search } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||||
|
|
||||||
|
const Command = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Command.displayName = CommandPrimitive.displayName
|
||||||
|
|
||||||
|
interface CommandDialogProps extends DialogProps {}
|
||||||
|
|
||||||
|
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[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-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandInput = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||||
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandInput.displayName = CommandPrimitive.Input.displayName
|
||||||
|
|
||||||
|
const CommandList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandList.displayName = CommandPrimitive.List.displayName
|
||||||
|
|
||||||
|
const CommandEmpty = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||||
|
>((props, ref) => (
|
||||||
|
<CommandPrimitive.Empty
|
||||||
|
ref={ref}
|
||||||
|
className="py-6 text-center text-sm"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
||||||
|
|
||||||
|
const CommandGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground [&_[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-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
||||||
|
|
||||||
|
const CommandSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const CommandItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandItem.displayName = CommandPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const CommandShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CommandShortcut.displayName = "CommandShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandShortcut,
|
||||||
|
CommandSeparator,
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user