impl command menu and change theme colors
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m26s
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m26s
This commit is contained in:
parent
a200fa045c
commit
49daf6f1a4
@ -29,6 +29,7 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clipboard-copy": "^4.0.1",
|
"clipboard-copy": "^4.0.1",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
"cmdk": "^1.0.0",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"lucide-react": "^0.372.0",
|
"lucide-react": "^0.372.0",
|
||||||
"mcutils-library": "^1.2.6",
|
"mcutils-library": "^1.2.6",
|
||||||
|
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@ -71,6 +71,9 @@ dependencies:
|
|||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
cmdk:
|
||||||
|
specifier: ^1.0.0
|
||||||
|
version: 1.0.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
|
||||||
fuse.js:
|
fuse.js:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
@ -3016,6 +3019,21 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/cmdk@1.0.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.0.0
|
||||||
|
react-dom: ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- '@types/react-dom'
|
||||||
|
dev: false
|
||||||
|
|
||||||
/co@4.6.0:
|
/co@4.6.0:
|
||||||
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
||||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
import { capitalizeFirstLetter } from "@/app/common/string-utils";
|
import { capitalizeFirstLetter } from "@/app/common/string-utils";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { GithubLink } from "@/app/components/docs/github-link";
|
import { GithubLink } from "@/app/components/docs/github-link";
|
||||||
|
import { CommandMenu } from "@/app/components/command-menu";
|
||||||
|
|
||||||
type DocumentationPageParams = {
|
type DocumentationPageParams = {
|
||||||
params: {
|
params: {
|
||||||
@ -87,7 +88,9 @@ export default function Page({ params: { slug } }: DocumentationPageParams) {
|
|||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
|
|
||||||
<GithubLink page={page} />
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<GithubLink page={page} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* The documentation page title and description */}
|
{/* The documentation page title and description */}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import React, { ReactElement } from "react";
|
|
||||||
import { Sidebar } from "@/app/components/docs/sidebar";
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>): ReactElement {
|
|
||||||
return (
|
|
||||||
<div className="w-full flex flex-col items-center gap-2 h-full md:flex-row md:items-start">
|
|
||||||
<Sidebar />
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -119,7 +119,7 @@ export function getDocContent(path?: string[]): DocsContentMetadata | undefined
|
|||||||
* @param limit the maximum number of results
|
* @param limit the maximum number of results
|
||||||
*/
|
*/
|
||||||
export function searchDocs(
|
export function searchDocs(
|
||||||
query: string,
|
query?: string,
|
||||||
limit?: number,
|
limit?: number,
|
||||||
): {
|
): {
|
||||||
title: string;
|
title: string;
|
||||||
@ -129,7 +129,7 @@ export function searchDocs(
|
|||||||
if (!limit) {
|
if (!limit) {
|
||||||
limit = 5; // Default to 5 results
|
limit = 5; // Default to 5 results
|
||||||
}
|
}
|
||||||
return fuseIndex.search(query, { limit }).map(result => {
|
return fuseIndex.search(query || "", { limit }).map(result => {
|
||||||
return {
|
return {
|
||||||
title: result.item.title,
|
title: result.item.title,
|
||||||
summary: result.item.summary,
|
summary: result.item.summary,
|
||||||
|
113
src/app/components/command-menu.tsx
Normal file
113
src/app/components/command-menu.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { ReactElement, useState } from "react";
|
||||||
|
import { DocsContentMetadata } from "@/app/common/documentation";
|
||||||
|
import {
|
||||||
|
CommandDialog,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/app/components/ui/command";
|
||||||
|
import { Button, ButtonProps } from "@/app/components/ui/button";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { cn } from "@/app/common/utils";
|
||||||
|
|
||||||
|
export function CommandMenu({ ...props }: ButtonProps): ReactElement {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the search
|
||||||
|
*/
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pages that were found
|
||||||
|
*/
|
||||||
|
const [pages, setPages] = useState<DocsContentMetadata[] | undefined>(undefined);
|
||||||
|
|
||||||
|
// Handle keyboard shortcuts
|
||||||
|
React.useEffect(() => {
|
||||||
|
const down = (e: KeyboardEvent) => {
|
||||||
|
if ((e.key === "k" && (e.metaKey || e.ctrlKey)) || e.key === "/") {
|
||||||
|
if (
|
||||||
|
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
|
||||||
|
e.target instanceof HTMLInputElement ||
|
||||||
|
e.target instanceof HTMLTextAreaElement ||
|
||||||
|
e.target instanceof HTMLSelectElement
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
setOpen(open => !open);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => document.removeEventListener("keydown", down);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search the documentation
|
||||||
|
* for the given query.
|
||||||
|
*
|
||||||
|
* @param query the query to search for
|
||||||
|
*/
|
||||||
|
async function searchDocs(query: string): Promise<void> {
|
||||||
|
// Don't bother searching if the query is less than 3 characters
|
||||||
|
if (query.length < 3) {
|
||||||
|
setPages(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to search for the query
|
||||||
|
const response = await fetch(`/api/docs/search?query=${query}`);
|
||||||
|
const pages: DocsContentMetadata[] = await response.json();
|
||||||
|
setPages(pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
"relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-64",
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Query..."
|
||||||
|
onValueChange={async search => {
|
||||||
|
await searchDocs(search);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
|
{pages && (
|
||||||
|
<CommandGroup heading="Suggestions">
|
||||||
|
{pages.map(page => {
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={page.slug}
|
||||||
|
onSelect={() => {
|
||||||
|
router.push(`/docs/${page.slug}`);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{page.title}
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
|
</CommandList>
|
||||||
|
</CommandDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -16,7 +16,7 @@ export function GithubLink({ page }: GithubLink): ReactElement {
|
|||||||
href={`https://git.fascinated.cc/MinecraftUtilities/Frontend/src/branch/master/documentation/${page.slug}.md`}
|
href={`https://git.fascinated.cc/MinecraftUtilities/Frontend/src/branch/master/documentation/${page.slug}.md`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Image src="/media/github.png" alt="The GitHub logo" width={24} height={24} className="filter dark:invert" />
|
<Image src="/media/github.png" alt="The GitHub logo" width={32} height={32} className="filter dark:invert" />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { ReactElement, useState } from "react";
|
|
||||||
import { Button } from "@/app/components/ui/button";
|
|
||||||
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/app/components/ui/dialog";
|
|
||||||
import { DocsContentMetadata } from "@/app/common/documentation";
|
|
||||||
import { DocumentationPages } from "@/app/components/docs/documentation-pages";
|
|
||||||
import { SearchIcon } from "lucide-react";
|
|
||||||
|
|
||||||
export function Search(): ReactElement {
|
|
||||||
/**
|
|
||||||
* The pages that were found
|
|
||||||
*/
|
|
||||||
const [pages, setPages] = useState<DocsContentMetadata[] | undefined>(undefined);
|
|
||||||
const [continueTyping, setContinueTyping] = useState<boolean>(false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search the documentation
|
|
||||||
* for the given query.
|
|
||||||
*
|
|
||||||
* @param query the query to search for
|
|
||||||
*/
|
|
||||||
async function searchDocs(query: string): Promise<void> {
|
|
||||||
// Don't bother searching if the query is less than 3 characters
|
|
||||||
if (query.length < 3) {
|
|
||||||
if (query.length > 0) {
|
|
||||||
setContinueTyping(true);
|
|
||||||
} else {
|
|
||||||
setContinueTyping(false);
|
|
||||||
}
|
|
||||||
return setPages(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to search for the query
|
|
||||||
const response = await fetch(`/api/docs/search?query=${query}`);
|
|
||||||
const pages: DocsContentMetadata[] = await response.json();
|
|
||||||
setPages(pages);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full md:w-[250px] min-h-fit h-fit bg-card rounded-lg p-3">
|
|
||||||
<div className="flex flex-col w-full">
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button className="flex items-center gap-1">
|
|
||||||
<SearchIcon width={16} height={16} />
|
|
||||||
<p>Search</p>
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="w-screen md:w-[600px]">
|
|
||||||
<DialogTitle>Search Documentation</DialogTitle>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Query..."
|
|
||||||
className="w-full p-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:border-ring"
|
|
||||||
onChange={event => searchDocs(event.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!pages && continueTyping && <p>Continue typing...</p>}
|
|
||||||
|
|
||||||
<DocumentationPages pages={pages} />
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
import { Search } from "@/app/components/docs/search";
|
|
||||||
import { ReactElement } from "react";
|
|
||||||
|
|
||||||
export function Sidebar(): ReactElement {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-row gap-2 h-full md:flex-col">
|
|
||||||
<Search />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -3,8 +3,16 @@
|
|||||||
import { ReactElement, useEffect, useState } from "react";
|
import { ReactElement, useEffect, useState } from "react";
|
||||||
import { Star } from "lucide-react";
|
import { Star } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { cn } from "@/app/common/utils";
|
||||||
|
|
||||||
export function GithubStar(): ReactElement {
|
type GithubStarProps = {
|
||||||
|
/**
|
||||||
|
* The class name for this component.
|
||||||
|
*/
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function GithubStar({ className }: GithubStarProps): ReactElement {
|
||||||
const [starCount, setStarCount] = useState(0);
|
const [starCount, setStarCount] = useState(0);
|
||||||
|
|
||||||
const getStarCount = async () => {
|
const getStarCount = async () => {
|
||||||
@ -19,7 +27,10 @@ export function GithubStar(): ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className="bg-github-green px-2 py-1 rounded-lg items-center gap-1 hover:opacity-85 transform-gpu transition-all hidden md:flex"
|
className={cn(
|
||||||
|
"bg-github-green px-2 py-1 rounded-lg items-center gap-1 hover:opacity-85 transform-gpu transition-all hidden md:flex",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
href="https://github.com/RealFascinated/MinecraftUtilities"
|
href="https://github.com/RealFascinated/MinecraftUtilities"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement, useEffect, useState } from "react";
|
||||||
import { HrefButton } from "./href-button";
|
import { HrefButton } from "./href-button";
|
||||||
import Logo from "./logo";
|
import Logo from "./logo";
|
||||||
import { ToggleThemeButton } from "./theme-toggle-button";
|
import { ToggleThemeButton } from "./theme-toggle-button";
|
||||||
import { GithubStar } from "@/app/components/github-star";
|
import { GithubStar } from "@/app/components/github-star";
|
||||||
import { Card } from "@/app/components/card";
|
import { Card } from "@/app/components/card";
|
||||||
import { cn } from "@/app/common/utils";
|
import { cn } from "@/app/common/utils";
|
||||||
|
import { CommandMenu } from "@/app/components/command-menu";
|
||||||
|
|
||||||
type Page = {
|
type Page = {
|
||||||
/**
|
/**
|
||||||
@ -42,7 +43,8 @@ const pages: Page[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function NavBar(): ReactElement {
|
export default function NavBar(): ReactElement {
|
||||||
const path = usePathname();
|
const path: string = usePathname();
|
||||||
|
const isDocs: boolean = path ? path.includes("/docs") : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@ -50,15 +52,17 @@ export default function NavBar(): ReactElement {
|
|||||||
classNameContent="p-0 relative rounded-lg flex justify-between items-center gap-3 px-3 bg-opacity-85 h-12"
|
classNameContent="p-0 relative rounded-lg flex justify-between items-center gap-3 px-3 bg-opacity-85 h-12"
|
||||||
>
|
>
|
||||||
{/* Left */}
|
{/* Left */}
|
||||||
<div className="z-50">
|
<div className={cn("flex flex-row items-center gap-2 z-50", isDocs ? "w-full md:w-fit" : "w-fit")}>
|
||||||
<Link href="/" className="flex items-center gap-2">
|
<Link href="/" className="flex items-center gap-2">
|
||||||
<Logo />
|
<Logo />
|
||||||
<p className="hidden md:block text-lg font-semibold">Minecraft Utilities</p>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{/* Command Menu */}
|
||||||
|
<CommandMenu className={cn(isDocs ? "" : "hidden md:inline-flex")} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Links */}
|
{/* Links */}
|
||||||
<div className="absolute inset-x-0 flex justify-center">
|
<div className={cn("absolute inset-x-0 justify-center", isDocs ? "hidden md:flex" : "flex")}>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
{pages.map((page, index) => {
|
{pages.map((page, index) => {
|
||||||
const isActive: boolean = path ? path.includes(page.url) : false;
|
const isActive: boolean = path ? path.includes(page.url) : false;
|
||||||
@ -79,7 +83,7 @@ export default function NavBar(): ReactElement {
|
|||||||
{/* Right */}
|
{/* Right */}
|
||||||
<div className="flex gap-4 items-center z-50">
|
<div className="flex gap-4 items-center z-50">
|
||||||
<ToggleThemeButton />
|
<ToggleThemeButton />
|
||||||
<GithubStar />
|
<GithubStar className={isDocs ? "hidden md:flex" : "hidden"} />
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
135
src/app/components/ui/command.tsx
Normal file
135
src/app/components/ui/command.tsx
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { type DialogProps } from "@radix-ui/react-dialog";
|
||||||
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
|
import { Search } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@/app/common/utils";
|
||||||
|
import { Dialog, DialogContent } from "@/app/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,
|
||||||
|
)}
|
||||||
|
shouldFilter={false}
|
||||||
|
{...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='true']:pointer-events-none data-[disabled='true']: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,
|
||||||
|
};
|
@ -2,51 +2,51 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 240 10% 3.9%;
|
--foreground: 0 0% 3.9%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 95%;
|
||||||
--card-foreground: 240 10% 3.9%;
|
--card-foreground: 0 0% 3.9%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 240 10% 3.9%;
|
--popover-foreground: 0 0% 3.9%;
|
||||||
--primary: 221.2 83.2% 53.3%;
|
--primary: 0 0% 9%;
|
||||||
--primary-foreground: 355.7 100% 97.3%;
|
--primary-foreground: 0 0% 98%;
|
||||||
--secondary: 5 5% 95%;
|
--secondary: 0 0% 96.1%;
|
||||||
--secondary-foreground: 240 5.9% 10%;
|
--secondary-foreground: 0 0% 9%;
|
||||||
--muted: 240 4.8% 95.9%;
|
--muted: 0 0% 96.1%;
|
||||||
--muted-foreground: 240 3.8% 46.1%;
|
--muted-foreground: 0 0% 45.1%;
|
||||||
--accent: 240 4.8% 95.9%;
|
--accent: 0 0% 96.1%;
|
||||||
--accent-foreground: 240 5.9% 10%;
|
--accent-foreground: 0 0% 9%;
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 240 5.9% 90%;
|
--border: 0 0% 89.8%;
|
||||||
--input: 240 5.9% 90%;
|
--input: 0 0% 89.8%;
|
||||||
--ring: 221.2 83.2% 53.3%;
|
--ring: 0 0% 3.9%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 20 14.3% 6.5%;
|
--background: 0 0% 3.9%;
|
||||||
--background-accent: 20 14.3% 8.5%;
|
--foreground: 0 0% 98%;
|
||||||
--foreground: 0 0% 95%;
|
--card: 0 0% 8%;
|
||||||
--card: 24 9.8% 10%;
|
--card-foreground: 0 0% 98%;
|
||||||
--card-foreground: 0 0% 95%;
|
--popover: 0 0% 3.9%;
|
||||||
--popover: 0 0% 9%;
|
--popover-foreground: 0 0% 98%;
|
||||||
--popover-foreground: 0 0% 95%;
|
--primary: 0 0% 98%;
|
||||||
--primary: 217.2 91.2% 59.8%;
|
--primary-foreground: 0 0% 9%;
|
||||||
--primary-foreground: 144.9 80.4% 10%;
|
--secondary: 0 0% 14.9%;
|
||||||
--secondary: 240 3.7% 15.9%;
|
|
||||||
--secondary-foreground: 0 0% 98%;
|
--secondary-foreground: 0 0% 98%;
|
||||||
--muted: 0 0% 15%;
|
--muted: 0 0% 14.9%;
|
||||||
--muted-foreground: 240 5% 64.9%;
|
--muted-foreground: 0 0% 63.9%;
|
||||||
--accent: 12 6.5% 75%;
|
--accent: 0 0% 14.9%;
|
||||||
--accent-foreground: 0 0% 98%;
|
--accent-foreground: 0 0% 98%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 85.7% 97.3%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 240 5.9% 30%;
|
--border: 0 0% 14.9%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 0 0% 14.9%;
|
||||||
--ring: 224.3 76.3% 48%;
|
--ring: 0 0% 83.1%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user