mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 09:49:28 +08:00
move alerts ui to sheet component
This commit is contained in:
@@ -17,7 +17,7 @@ import { $publicKey, pb } from "@/lib/stores"
|
|||||||
import { cn, generateToken, isReadOnlyUser, tokenMap, useLocalStorage } from "@/lib/utils"
|
import { cn, generateToken, isReadOnlyUser, tokenMap, useLocalStorage } from "@/lib/utils"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
|
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
|
||||||
import { memo, useEffect, useRef, useState } from "react"
|
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { $router, basePath, Link, navigate } from "./router"
|
import { $router, basePath, Link, navigate } from "./router"
|
||||||
import { SystemRecord } from "@/types"
|
import { SystemRecord } from "@/types"
|
||||||
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons"
|
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons"
|
||||||
@@ -122,151 +122,154 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return useMemo(
|
||||||
<DialogContent
|
() => (
|
||||||
className="w-[90%] sm:w-auto sm:ns-dialog max-w-full rounded-lg"
|
<DialogContent
|
||||||
onCloseAutoFocus={() => {
|
className="w-[90%] sm:w-auto sm:ns-dialog max-w-full rounded-lg"
|
||||||
setHostValue(system?.host ?? "")
|
onCloseAutoFocus={() => {
|
||||||
}}
|
setHostValue(system?.host ?? "")
|
||||||
>
|
}}
|
||||||
<Tabs defaultValue={tab} onValueChange={setTab}>
|
>
|
||||||
<DialogHeader>
|
<Tabs defaultValue={tab} onValueChange={setTab}>
|
||||||
<DialogTitle className="mb-2">
|
<DialogHeader>
|
||||||
{system ? `${t`Edit`} ${system?.name}` : <Trans>Add New System</Trans>}
|
<DialogTitle className="mb-2">
|
||||||
</DialogTitle>
|
{system ? `${t`Edit`} ${system?.name}` : <Trans>Add New System</Trans>}
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
</DialogTitle>
|
||||||
<TabsTrigger value="docker">Docker</TabsTrigger>
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
<TabsTrigger value="binary">
|
<TabsTrigger value="docker">Docker</TabsTrigger>
|
||||||
<Trans>Binary</Trans>
|
<TabsTrigger value="binary">
|
||||||
</TabsTrigger>
|
<Trans>Binary</Trans>
|
||||||
</TabsList>
|
</TabsTrigger>
|
||||||
</DialogHeader>
|
</TabsList>
|
||||||
{/* Docker (set tab index to prevent auto focusing content in edit system dialog) */}
|
</DialogHeader>
|
||||||
<TabsContent value="docker" tabIndex={-1}>
|
{/* Docker (set tab index to prevent auto focusing content in edit system dialog) */}
|
||||||
<DialogDescription className="mb-3 leading-relaxed w-0 min-w-full">
|
<TabsContent value="docker" tabIndex={-1}>
|
||||||
<Trans>
|
<DialogDescription className="mb-3 leading-relaxed w-0 min-w-full">
|
||||||
Copy the
|
<Trans>
|
||||||
<code className="bg-muted px-1 rounded-sm leading-3">docker-compose.yml</code> content for the agent
|
Copy the
|
||||||
below, or register agents automatically with a{" "}
|
<code className="bg-muted px-1 rounded-sm leading-3">docker-compose.yml</code> content for the agent
|
||||||
<Link
|
below, or register agents automatically with a{" "}
|
||||||
onClick={() => setOpen(false)}
|
<Link
|
||||||
href={getPagePath($router, "settings", { name: "tokens" })}
|
onClick={() => setOpen(false)}
|
||||||
className="link"
|
href={getPagePath($router, "settings", { name: "tokens" })}
|
||||||
>
|
className="link"
|
||||||
universal token
|
>
|
||||||
</Link>
|
universal token
|
||||||
.
|
</Link>
|
||||||
</Trans>
|
.
|
||||||
</DialogDescription>
|
</Trans>
|
||||||
</TabsContent>
|
</DialogDescription>
|
||||||
{/* Binary */}
|
</TabsContent>
|
||||||
<TabsContent value="binary" tabIndex={-1}>
|
{/* Binary */}
|
||||||
<DialogDescription className="mb-3 leading-relaxed w-0 min-w-full">
|
<TabsContent value="binary" tabIndex={-1}>
|
||||||
<Trans>
|
<DialogDescription className="mb-3 leading-relaxed w-0 min-w-full">
|
||||||
Copy the installation command for the agent below, or register agents automatically with a{" "}
|
<Trans>
|
||||||
<Link
|
Copy the installation command for the agent below, or register agents automatically with a{" "}
|
||||||
onClick={() => {
|
<Link
|
||||||
setOpen(false)
|
onClick={() => setOpen(false)}
|
||||||
|
href={getPagePath($router, "settings", { name: "tokens" })}
|
||||||
|
className="link"
|
||||||
|
>
|
||||||
|
universal token
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Trans>
|
||||||
|
</DialogDescription>
|
||||||
|
</TabsContent>
|
||||||
|
<form onSubmit={handleSubmit as any}>
|
||||||
|
<div className="grid xs:grid-cols-[auto_1fr] gap-y-3 gap-x-4 items-center mt-1 mb-4">
|
||||||
|
<Label htmlFor="name" className="xs:text-end">
|
||||||
|
<Trans>Name</Trans>
|
||||||
|
</Label>
|
||||||
|
<Input id="name" name="name" defaultValue={system?.name} required />
|
||||||
|
<Label htmlFor="host" className="xs:text-end">
|
||||||
|
<Trans>Host / IP</Trans>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="host"
|
||||||
|
name="host"
|
||||||
|
value={hostValue}
|
||||||
|
required
|
||||||
|
onChange={(e) => {
|
||||||
|
setHostValue(e.target.value)
|
||||||
}}
|
}}
|
||||||
href={getPagePath($router, "settings", { name: "tokens" })}
|
|
||||||
className="link"
|
|
||||||
>
|
|
||||||
universal token
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</Trans>
|
|
||||||
</DialogDescription>
|
|
||||||
</TabsContent>
|
|
||||||
<form onSubmit={handleSubmit as any}>
|
|
||||||
<div className="grid xs:grid-cols-[auto_1fr] gap-y-3 gap-x-4 items-center mt-1 mb-4">
|
|
||||||
<Label htmlFor="name" className="xs:text-end">
|
|
||||||
<Trans>Name</Trans>
|
|
||||||
</Label>
|
|
||||||
<Input id="name" name="name" defaultValue={system?.name} required />
|
|
||||||
<Label htmlFor="host" className="xs:text-end">
|
|
||||||
<Trans>Host / IP</Trans>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="host"
|
|
||||||
name="host"
|
|
||||||
value={hostValue}
|
|
||||||
required
|
|
||||||
onChange={(e) => {
|
|
||||||
setHostValue(e.target.value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="port" className={cn("xs:text-end", isUnixSocket && "hidden")}>
|
|
||||||
<Trans>Port</Trans>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
ref={port}
|
|
||||||
name="port"
|
|
||||||
id="port"
|
|
||||||
defaultValue={system?.port || "45876"}
|
|
||||||
required={!isUnixSocket}
|
|
||||||
className={cn(isUnixSocket && "hidden")}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="pkey" className="xs:text-end whitespace-pre">
|
|
||||||
<Trans comment="Use 'Key' if your language requires many more characters">Public Key</Trans>
|
|
||||||
</Label>
|
|
||||||
<InputCopy value={publicKey} id="pkey" name="pkey" />
|
|
||||||
<Label htmlFor="tkn" className="xs:text-end whitespace-pre">
|
|
||||||
<Trans>Token</Trans>
|
|
||||||
</Label>
|
|
||||||
<InputCopy value={token} id="tkn" name="tkn" />
|
|
||||||
</div>
|
|
||||||
<DialogFooter className="flex justify-end gap-x-2 gap-y-3 flex-col mt-5">
|
|
||||||
{/* Docker */}
|
|
||||||
<TabsContent value="docker" className="contents">
|
|
||||||
<CopyButton
|
|
||||||
text={t({ message: "Copy docker compose", context: "Button to copy docker compose file content" })}
|
|
||||||
onClick={async () =>
|
|
||||||
copyDockerCompose(isUnixSocket ? hostValue : port.current?.value, publicKey, token)
|
|
||||||
}
|
|
||||||
icon={<DockerIcon className="size-4 -me-0.5" />}
|
|
||||||
dropdownItems={[
|
|
||||||
{
|
|
||||||
text: t({ message: "Copy docker run", context: "Button to copy docker run command" }),
|
|
||||||
onClick: async () =>
|
|
||||||
copyDockerRun(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
|
|
||||||
icons: [DockerIcon],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
<Label htmlFor="port" className={cn("xs:text-end", isUnixSocket && "hidden")}>
|
||||||
{/* Binary */}
|
<Trans>Port</Trans>
|
||||||
<TabsContent value="binary" className="contents">
|
</Label>
|
||||||
<CopyButton
|
<Input
|
||||||
text={t`Copy Linux command`}
|
ref={port}
|
||||||
icon={<TuxIcon className="size-4" />}
|
name="port"
|
||||||
onClick={async () => copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token)}
|
id="port"
|
||||||
dropdownItems={[
|
defaultValue={system?.port || "45876"}
|
||||||
{
|
required={!isUnixSocket}
|
||||||
text: t({ message: "Homebrew command", context: "Button to copy install command" }),
|
className={cn(isUnixSocket && "hidden")}
|
||||||
onClick: async () =>
|
|
||||||
copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token, true),
|
|
||||||
icons: [AppleIcon, TuxIcon],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: t({ message: "Windows command", context: "Button to copy install command" }),
|
|
||||||
onClick: async () =>
|
|
||||||
copyWindowsCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
|
|
||||||
icons: [WindowsIcon],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: t`Manual setup instructions`,
|
|
||||||
url: "https://beszel.dev/guide/agent-installation#binary",
|
|
||||||
icons: [ExternalLinkIcon],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
<Label htmlFor="pkey" className="xs:text-end whitespace-pre">
|
||||||
{/* Save */}
|
<Trans comment="Use 'Key' if your language requires many more characters">Public Key</Trans>
|
||||||
<Button>{system ? <Trans>Save system</Trans> : <Trans>Add system</Trans>}</Button>
|
</Label>
|
||||||
</DialogFooter>
|
<InputCopy value={publicKey} id="pkey" name="pkey" />
|
||||||
</form>
|
<Label htmlFor="tkn" className="xs:text-end whitespace-pre">
|
||||||
</Tabs>
|
<Trans>Token</Trans>
|
||||||
</DialogContent>
|
</Label>
|
||||||
|
<InputCopy value={token} id="tkn" name="tkn" />
|
||||||
|
</div>
|
||||||
|
<DialogFooter className="flex justify-end gap-x-2 gap-y-3 flex-col mt-5">
|
||||||
|
{/* Docker */}
|
||||||
|
<TabsContent value="docker" className="contents">
|
||||||
|
<CopyButton
|
||||||
|
text={t({ message: "Copy docker compose", context: "Button to copy docker compose file content" })}
|
||||||
|
onClick={async () =>
|
||||||
|
copyDockerCompose(isUnixSocket ? hostValue : port.current?.value, publicKey, token)
|
||||||
|
}
|
||||||
|
icon={<DockerIcon className="size-4 -me-0.5" />}
|
||||||
|
dropdownItems={[
|
||||||
|
{
|
||||||
|
text: t({ message: "Copy docker run", context: "Button to copy docker run command" }),
|
||||||
|
onClick: async () =>
|
||||||
|
copyDockerRun(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
|
||||||
|
icons: [DockerIcon],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
{/* Binary */}
|
||||||
|
<TabsContent value="binary" className="contents">
|
||||||
|
<CopyButton
|
||||||
|
text={t`Copy Linux command`}
|
||||||
|
icon={<TuxIcon className="size-4" />}
|
||||||
|
onClick={async () =>
|
||||||
|
copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token)
|
||||||
|
}
|
||||||
|
dropdownItems={[
|
||||||
|
{
|
||||||
|
text: t({ message: "Homebrew command", context: "Button to copy install command" }),
|
||||||
|
onClick: async () =>
|
||||||
|
copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token, true),
|
||||||
|
icons: [AppleIcon, TuxIcon],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t({ message: "Windows command", context: "Button to copy install command" }),
|
||||||
|
onClick: async () =>
|
||||||
|
copyWindowsCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
|
||||||
|
icons: [WindowsIcon],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t`Manual setup instructions`,
|
||||||
|
url: "https://beszel.dev/guide/agent-installation#binary",
|
||||||
|
icons: [ExternalLinkIcon],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
{/* Save */}
|
||||||
|
<Button>{system ? <Trans>Save system</Trans> : <Trans>Add system</Trans>}</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Tabs>
|
||||||
|
</DialogContent>
|
||||||
|
),
|
||||||
|
[]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,56 +2,35 @@ import { t } from "@lingui/core/macro"
|
|||||||
import { memo, useMemo, useState } from "react"
|
import { memo, useMemo, useState } from "react"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { $alerts } from "@/lib/stores"
|
import { $alerts } from "@/lib/stores"
|
||||||
import { Dialog, DialogTrigger, DialogContent } from "@/components/ui/dialog"
|
|
||||||
import { BellIcon } from "lucide-react"
|
import { BellIcon } from "lucide-react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { SystemRecord } from "@/types"
|
import { SystemRecord } from "@/types"
|
||||||
import { AlertDialogContent } from "./alerts-dialog"
|
import { AlertDialogContent } from "./alerts-sheet"
|
||||||
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
|
||||||
|
|
||||||
export default memo(function AlertsButton({ system }: { system: SystemRecord }) {
|
export default memo(function AlertsButton({ system }: { system: SystemRecord }) {
|
||||||
const [opened, setOpened] = useState(false)
|
const [opened, setOpened] = useState(false)
|
||||||
const alerts = useStore($alerts)
|
const alerts = useStore($alerts)
|
||||||
|
|
||||||
const hasSystemAlert = alerts[system.id]?.size > 0
|
const hasSystemAlert = alerts[system.id]?.size > 0
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Dialog>
|
<Sheet>
|
||||||
<DialogTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" aria-label={t`Alerts`} onClick={() => setOpened(true)}>
|
<Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
|
||||||
<BellIcon
|
<BellIcon
|
||||||
className={cn("h-[1.2em] w-[1.2em]", {
|
className={cn("h-[1.2em] w-[1.2em] pointer-events-none", {
|
||||||
"fill-primary": hasSystemAlert,
|
"fill-primary": hasSystemAlert,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</SheetTrigger>
|
||||||
<DialogContent className="max-h-full sm:max-h-[95svh] overflow-auto max-w-148">
|
<SheetContent className="max-h-full overflow-auto w-145 !max-w-full p-4 sm:p-6">
|
||||||
{opened && <AlertDialogContent system={system} />}
|
{opened && <AlertDialogContent system={system} />}
|
||||||
</DialogContent>
|
</SheetContent>
|
||||||
</Dialog>
|
</Sheet>
|
||||||
),
|
),
|
||||||
[opened, hasSystemAlert]
|
[opened, hasSystemAlert]
|
||||||
)
|
)
|
||||||
|
|
||||||
// return useMemo(
|
|
||||||
// () => (
|
|
||||||
// <Sheet>
|
|
||||||
// <SheetTrigger asChild>
|
|
||||||
// <Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
|
|
||||||
// <BellIcon
|
|
||||||
// className={cn("h-[1.2em] w-[1.2em] pointer-events-none", {
|
|
||||||
// "fill-primary": hasAlert,
|
|
||||||
// })}
|
|
||||||
// />
|
|
||||||
// </Button>
|
|
||||||
// </SheetTrigger>
|
|
||||||
// <SheetContent className="max-h-full overflow-auto w-[35em] p-4 sm:p-5">
|
|
||||||
// {opened && <AlertDialogContent system={system} />}
|
|
||||||
// </SheetContent>
|
|
||||||
// </Sheet>
|
|
||||||
// ),
|
|
||||||
// [opened, hasAlert]
|
|
||||||
// )
|
|
||||||
})
|
})
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Trans } from "@lingui/react/macro";
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { useState, lazy, Suspense } from "react"
|
import { useState, lazy, Suspense } from "react"
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
|
@@ -45,7 +45,7 @@ export function Link(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
|
|||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
window.open(href, "_blank")
|
window.open(href, "_blank")
|
||||||
} else {
|
} else {
|
||||||
$router.open(href)
|
navigate(href)
|
||||||
props.onClick?.(e)
|
props.onClick?.(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@@ -111,21 +111,18 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
Icon: ServerIcon,
|
Icon: ServerIcon,
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const { name } = info.row.original
|
const { name } = info.row.original
|
||||||
return useMemo(
|
return (
|
||||||
() => (
|
<>
|
||||||
<>
|
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1 md:pe-5">
|
||||||
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1 md:pe-5">
|
<IndicatorDot system={info.row.original} />
|
||||||
<IndicatorDot system={info.row.original} />
|
{name}
|
||||||
{name}
|
</span>
|
||||||
</span>
|
<Link
|
||||||
<Link
|
href={getPagePath($router, "system", { name })}
|
||||||
href={getPagePath($router, "system", { name })}
|
className="inset-0 absolute size-full"
|
||||||
className="inset-0 absolute size-full"
|
aria-label={name}
|
||||||
aria-label={name}
|
></Link>
|
||||||
></Link>
|
</>
|
||||||
</>
|
|
||||||
),
|
|
||||||
[name]
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
header: sortableHeader,
|
header: sortableHeader,
|
||||||
|
101
beszel/site/src/components/ui/sheet.tsx
Normal file
101
beszel/site/src/components/ui/sheet.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { XIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||||
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||||
|
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||||
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||||
|
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
data-slot="sheet-overlay"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=open]:animate-in duration-500 isolate data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/40",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
side = "right",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||||
|
side?: "top" | "right" | "bottom" | "left"
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
data-slot="sheet-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-[400ms]",
|
||||||
|
side === "right" &&
|
||||||
|
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
||||||
|
side === "left" &&
|
||||||
|
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
||||||
|
side === "top" &&
|
||||||
|
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||||
|
side === "bottom" &&
|
||||||
|
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||||
|
<XIcon className="size-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
data-slot="sheet-title"
|
||||||
|
className={cn("text-foreground font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
data-slot="sheet-description"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription }
|
@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs cursor-pointer",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
Reference in New Issue
Block a user