use bottom sheet instead of vaul drawer

This commit is contained in:
Milo Schwartz 2024-12-30 15:48:34 -05:00
parent 91d314c4cc
commit e6263567a9
No known key found for this signature in database
13 changed files with 284 additions and 121 deletions

View file

@ -4,7 +4,7 @@ WORKDIR /app
COPY package.json ./ COPY package.json ./
RUN npm install --legacy-peer-deps RUN npm install
COPY . . COPY . .
@ -20,7 +20,7 @@ WORKDIR /app
COPY package.json ./ COPY package.json ./
RUN npm install --omit=dev --legacy-peer-deps RUN npm install --omit=dev
COPY --from=builder /app/.next ./.next COPY --from=builder /app/.next ./.next
COPY --from=builder /app/dist ./dist COPY --from=builder /app/dist ./dist

View file

@ -1,4 +1,4 @@
import express, { Request, Response } from "express"; import express from "express";
import cors from "cors"; import cors from "cors";
import cookieParser from "cookie-parser"; import cookieParser from "cookie-parser";
import config from "@server/config"; import config from "@server/config";

View file

@ -14,7 +14,10 @@ const portSchema = z.number().positive().gt(0).lte(65535);
const environmentSchema = z.object({ const environmentSchema = z.object({
app: z.object({ app: z.object({
base_url: z.string().url().transform((url) => url.toLowerCase()), base_url: z
.string()
.url()
.transform((url) => url.toLowerCase()),
log_level: z.enum(["debug", "info", "warn", "error"]), log_level: z.enum(["debug", "info", "warn", "error"]),
save_logs: z.boolean() save_logs: z.boolean()
}), }),
@ -76,7 +79,8 @@ const environmentSchema = z.object({
.optional() .optional()
}); });
const loadConfig = (configPath: string) => { export function getConfig() {
const loadConfig = (configPath: string) => {
try { try {
const yamlContent = fs.readFileSync(configPath, "utf8"); const yamlContent = fs.readFileSync(configPath, "utf8");
const config = yaml.load(yamlContent); const config = yaml.load(yamlContent);
@ -89,18 +93,18 @@ const loadConfig = (configPath: string) => {
} }
throw error; throw error;
} }
}; };
const configFilePath1 = path.join(APP_PATH, "config.yml"); const configFilePath1 = path.join(APP_PATH, "config.yml");
const configFilePath2 = path.join(APP_PATH, "config.yaml"); const configFilePath2 = path.join(APP_PATH, "config.yaml");
let environment: any; let environment: any;
if (fs.existsSync(configFilePath1)) { if (fs.existsSync(configFilePath1)) {
environment = loadConfig(configFilePath1); environment = loadConfig(configFilePath1);
} else if (fs.existsSync(configFilePath2)) { } else if (fs.existsSync(configFilePath2)) {
environment = loadConfig(configFilePath2); environment = loadConfig(configFilePath2);
} }
if (!environment) { if (!environment) {
const exampleConfigPath = path.join(__DIRNAME, "config.example.yml"); const exampleConfigPath = path.join(__DIRNAME, "config.example.yml");
if (fs.existsSync(exampleConfigPath)) { if (fs.existsSync(exampleConfigPath)) {
try { try {
@ -123,50 +127,54 @@ if (!environment) {
"No configuration file found and no example configuration available" "No configuration file found and no example configuration available"
); );
} }
} }
if (!environment) { if (!environment) {
throw new Error("No configuration file found"); throw new Error("No configuration file found");
} }
const parsedConfig = environmentSchema.safeParse(environment); const parsedConfig = environmentSchema.safeParse(environment);
if (!parsedConfig.success) { if (!parsedConfig.success) {
const errors = fromError(parsedConfig.error); const errors = fromError(parsedConfig.error);
throw new Error(`Invalid configuration file: ${errors}`); throw new Error(`Invalid configuration file: ${errors}`);
} }
const packageJsonPath = path.join(__DIRNAME, "..", "package.json"); const packageJsonPath = path.join(__DIRNAME, "..", "package.json");
let packageJson: any; let packageJson: any;
if (fs.existsSync && fs.existsSync(packageJsonPath)) { if (fs.existsSync && fs.existsSync(packageJsonPath)) {
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8"); const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
packageJson = JSON.parse(packageJsonContent); packageJson = JSON.parse(packageJsonContent);
if (packageJson.version) { if (packageJson.version) {
process.env.APP_VERSION = packageJson.version; process.env.APP_VERSION = packageJson.version;
} }
} }
process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString(); process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
process.env.SERVER_EXTERNAL_PORT = process.env.SERVER_EXTERNAL_PORT =
parsedConfig.data.server.external_port.toString(); parsedConfig.data.server.external_port.toString();
process.env.SERVER_INTERNAL_PORT = process.env.SERVER_INTERNAL_PORT =
parsedConfig.data.server.internal_port.toString(); parsedConfig.data.server.internal_port.toString();
process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
?.require_email_verification ?.require_email_verification
? "true" ? "true"
: "false"; : "false";
process.env.SESSION_COOKIE_NAME = parsedConfig.data.server.session_cookie_name; process.env.SESSION_COOKIE_NAME =
process.env.RESOURCE_SESSION_COOKIE_NAME = parsedConfig.data.server.session_cookie_name;
process.env.RESOURCE_SESSION_COOKIE_NAME =
parsedConfig.data.server.resource_session_cookie_name; parsedConfig.data.server.resource_session_cookie_name;
process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false"; process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags
?.disable_signup_without_invite ?.disable_signup_without_invite
? "true" ? "true"
: "false"; : "false";
process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags
?.disable_user_create_org ?.disable_user_create_org
? "true" ? "true"
: "false"; : "false";
export default parsedConfig.data; return parsedConfig.data;
}
export default getConfig();

View file

@ -68,7 +68,7 @@ export const SendInviteLink = ({
<Section className="text-center my-6"> <Section className="text-center my-6">
<Button <Button
href={inviteLink} href={inviteLink}
className="rounded-lg bg-primary px-[12px] py-[9px] text-center font-semibold text-white cursor-pointer" className="rounded-lg bg-primary px-[12px] py-[9px] text-center font-semibold text-white cursor-pointer text-xl"
> >
Accept invitation to {orgName} Accept invitation to {orgName}
</Button> </Button>

View file

@ -421,6 +421,7 @@ export default function ResourceAuthenticationPage() {
<FormItem className="flex flex-col items-start"> <FormItem className="flex flex-col items-start">
<FormLabel>Roles</FormLabel> <FormLabel>Roles</FormLabel>
<FormControl> <FormControl>
{/* @ts-ignore */}
<TagInput <TagInput
{...field} {...field}
activeTagIndex={ activeTagIndex={
@ -454,9 +455,9 @@ export default function ResourceAuthenticationPage() {
tag: { tag: {
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full" body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
}, },
input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none", input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
inlineTagsContainer: inlineTagsContainer:
"bg-transparent" "bg-transparent p-2"
}} }}
/> />
</FormControl> </FormControl>
@ -476,6 +477,7 @@ export default function ResourceAuthenticationPage() {
<FormItem className="flex flex-col items-start"> <FormItem className="flex flex-col items-start">
<FormLabel>Users</FormLabel> <FormLabel>Users</FormLabel>
<FormControl> <FormControl>
{/* @ts-ignore */}
<TagInput <TagInput
{...field} {...field}
activeTagIndex={ activeTagIndex={
@ -509,9 +511,9 @@ export default function ResourceAuthenticationPage() {
tag: { tag: {
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full" body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
}, },
input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none", input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
inlineTagsContainer: inlineTagsContainer:
"bg-transparent" "bg-transparent p-2"
}} }}
/> />
</FormControl> </FormControl>
@ -649,6 +651,7 @@ export default function ResourceAuthenticationPage() {
Whitelisted Emails Whitelisted Emails
</FormLabel> </FormLabel>
<FormControl> <FormControl>
{/* @ts-ignore */}
<TagInput <TagInput
{...field} {...field}
activeTagIndex={ activeTagIndex={
@ -691,9 +694,9 @@ export default function ResourceAuthenticationPage() {
tag: { tag: {
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full" body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
}, },
input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none", input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
inlineTagsContainer: inlineTagsContainer:
"bg-transparent" "bg-transparent p-2"
}} }}
/> />
</FormControl> </FormControl>

View file

@ -50,7 +50,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
<Breadcrumb> <Breadcrumb>
<BreadcrumbList> <BreadcrumbList>
<BreadcrumbItem> <BreadcrumbItem>
<Link href="../../">Sites</Link> <Link href="../">Sites</Link>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator /> <BreadcrumbSeparator />
<BreadcrumbItem> <BreadcrumbItem>

View file

@ -6,11 +6,11 @@
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 20 5.0% 10.0%; --foreground: 20 0.0% 10.0%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 20 5.0% 10.0%; --card-foreground: 20 0.0% 10.0%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 20 5.0% 10.0%; --popover-foreground: 20 0.0% 10.0%;
--primary: 24.6 95% 53.1%; --primary: 24.6 95% 53.1%;
--primary-foreground: 60 9.1% 97.8%; --primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%; --secondary: 60 4.8% 95.9%;
@ -33,11 +33,11 @@
} }
.dark { .dark {
--background: 20 5.0% 10.0%; --background: 20 0.0% 10.0%;
--foreground: 60 9.1% 97.8%; --foreground: 60 9.1% 97.8%;
--card: 20 5.0% 10.0%; --card: 20 0.0% 10.0%;
--card-foreground: 60 9.1% 97.8%; --card-foreground: 60 9.1% 97.8%;
--popover: 20 5.0% 10.0%; --popover: 20 0.0% 10.0%;
--popover-foreground: 60 9.1% 97.8%; --popover-foreground: 60 9.1% 97.8%;
--primary: 20.5 90.2% 48.2%; --primary: 20.5 90.2% 48.2%;
--primary-foreground: 60 9.1% 97.8%; --primary-foreground: 60 9.1% 97.8%;

View file

@ -24,6 +24,15 @@ import {
DrawerTitle, DrawerTitle,
DrawerTrigger DrawerTrigger
} from "@/components/ui/drawer"; } from "@/components/ui/drawer";
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger
} from "./ui/sheet";
interface BaseProps { interface BaseProps {
children: React.ReactNode; children: React.ReactNode;
@ -44,7 +53,7 @@ const desktop = "(min-width: 768px)";
const Credenza = ({ children, ...props }: RootCredenzaProps) => { const Credenza = ({ children, ...props }: RootCredenzaProps) => {
const isDesktop = useMediaQuery(desktop); const isDesktop = useMediaQuery(desktop);
// const isDesktop = true; // const isDesktop = true;
const Credenza = isDesktop ? Dialog : Drawer; const Credenza = isDesktop ? Dialog : Sheet;
return <Credenza {...props}>{children}</Credenza>; return <Credenza {...props}>{children}</Credenza>;
}; };
@ -53,7 +62,7 @@ const CredenzaTrigger = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop); const isDesktop = useMediaQuery(desktop);
// const isDesktop = true; // const isDesktop = true;
const CredenzaTrigger = isDesktop ? DialogTrigger : DrawerTrigger; const CredenzaTrigger = isDesktop ? DialogTrigger : SheetTrigger;
return ( return (
<CredenzaTrigger className={className} {...props}> <CredenzaTrigger className={className} {...props}>
@ -79,10 +88,14 @@ const CredenzaContent = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop); const isDesktop = useMediaQuery(desktop);
// const isDesktop = true; // const isDesktop = true;
const CredenzaContent = isDesktop ? DialogContent : DrawerContent; const CredenzaContent = isDesktop ? DialogContent : SheetContent;
return ( return (
<CredenzaContent className={className} {...props}> <CredenzaContent
className={cn("overflow-y-auto max-h-screen", className)}
{...props}
side={"bottom"}
>
{children} {children}
</CredenzaContent> </CredenzaContent>
); );
@ -98,7 +111,7 @@ const CredenzaDescription = ({
const CredenzaDescription = isDesktop const CredenzaDescription = isDesktop
? DialogDescription ? DialogDescription
: DrawerDescription; : SheetDescription;
return ( return (
<CredenzaDescription className={className} {...props}> <CredenzaDescription className={className} {...props}>
@ -111,7 +124,7 @@ const CredenzaHeader = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop); const isDesktop = useMediaQuery(desktop);
// const isDesktop = true; // const isDesktop = true;
const CredenzaHeader = isDesktop ? DialogHeader : DrawerHeader; const CredenzaHeader = isDesktop ? DialogHeader : SheetHeader;
return ( return (
<CredenzaHeader className={className} {...props}> <CredenzaHeader className={className} {...props}>
@ -124,7 +137,7 @@ const CredenzaTitle = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop); const isDesktop = useMediaQuery(desktop);
// const isDesktop = true; // const isDesktop = true;
const CredenzaTitle = isDesktop ? DialogTitle : DrawerTitle; const CredenzaTitle = isDesktop ? DialogTitle : SheetTitle;
return ( return (
<CredenzaTitle className={className} {...props}> <CredenzaTitle className={className} {...props}>
@ -134,25 +147,24 @@ const CredenzaTitle = ({ className, children, ...props }: CredenzaProps) => {
}; };
const CredenzaBody = ({ className, children, ...props }: CredenzaProps) => { const CredenzaBody = ({ className, children, ...props }: CredenzaProps) => {
return (
<div className={cn("px-4 md:px-0 mb-4", className)} {...props}>
{children}
</div>
);
// return ( // return (
// <div className={cn("px-0 mb-4", className)} {...props}> // <div className={cn("px-4 md:px-0 mb-4", className)} {...props}>
// {children} // {children}
// </div> // </div>
// ); // );
return (
<div className={cn("px-0 mb-4", className)} {...props}>
{children}
</div>
);
}; };
const CredenzaFooter = ({ className, children, ...props }: CredenzaProps) => { const CredenzaFooter = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop); const isDesktop = useMediaQuery(desktop);
// const isDesktop = true; // const isDesktop = true;
const CredenzaFooter = isDesktop ? DialogFooter : DrawerFooter; const CredenzaFooter = isDesktop ? DialogFooter : SheetFooter;
return ( return (
<CredenzaFooter className={className} {...props}> <CredenzaFooter className={className} {...props}>

View file

@ -224,7 +224,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
<div className="h-[250px] mx-auto flex items-center justify-center"> <div className="h-[250px] mx-auto flex items-center justify-center">
<QRCodeCanvas value={secretUri} size={200} /> <QRCodeCanvas value={secretUri} size={200} />
</div> </div>
<CopyTextBox text={secretKey} wrapText={false} /> <CopyTextBox text={secretUri} wrapText={false} />
<Form {...confirmForm}> <Form {...confirmForm}>
<form <form

140
src/components/ui/sheet.tsx Normal file
View file

@ -0,0 +1,140 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View file

@ -25,7 +25,7 @@ const ToastViewport = React.forwardRef<
ToastViewport.displayName = ToastPrimitives.Viewport.displayName ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva( const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-3 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{ {
variants: { variants: {
variant: { variant: {

View file

@ -6,7 +6,7 @@ import * as React from "react";
import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
const TOAST_LIMIT = 3; const TOAST_LIMIT = 3;
const TOAST_REMOVE_DELAY = 5 * 1000; const TOAST_REMOVE_DELAY = 1 * 1000;
type ToasterToast = ToastProps & { type ToasterToast = ToastProps & {
id: string; id: string;

View file

@ -1,8 +1,8 @@
"use client"; "use client";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes"; import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
type ThemeProviderProps = React.ComponentProps<typeof NextThemesProvider>;
export function ThemeProvider({ children, ...props }: ThemeProviderProps) { export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>; return <NextThemesProvider {...props}>{children}</NextThemesProvider>;