diff --git a/frontend.txt b/frontend.txt deleted file mode 100644 index c2430bf..0000000 --- a/frontend.txt +++ /dev/null @@ -1,3895 +0,0 @@ - -// file: ./app/routes/index.tsx -import { redirect } from "react-router"; -import type { Route } from "./+types/index"; - -export function meta({ }: Route.MetaArgs) { - return [ - { title: "New React Router App" }, - ]; -} - -export function clientLoader() { - return redirect("/login"); -} - -export default function Index() { - return <>; -} - -// file: ./app/routes/auth/layout.tsx -import { Outlet } from "react-router"; - -export default function Layout() { - return ( -
-
- -
-
- ); -} - -// file: ./app/routes/auth/login.tsx -import { zodResolver } from "@hookform/resolvers/zod"; -import { AxiosError } from "axios"; -import { useForm } from "react-hook-form"; -import { Link, redirect, useNavigate } from "react-router"; -import { z } from "zod"; -import { useShallow } from "zustand/react/shallow"; -import { PasswordInput } from "~/components/custom-ui/password-input"; -import { ThemeToggle } from "~/components/theme/theme-toggle"; -import { Button, buttonVariants } from "~/components/ui/button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "~/components/ui/form"; -import { Input } from "~/components/ui/input"; -import auth from "~/lib/api/client/auth"; -import { useTokenStore } from "~/stores/token-store"; -import { useUsersStore } from "~/stores/users-store"; - -const schema = z.object({ - username: z.string(), - password: z.string().min(8), -}); - -export async function clientLoader() { - const { token, setToken } = useTokenStore.getState() - - if (token) { - try { - await import("~/lib/api/client/user").then(m => m.default.me()) - - return redirect("/app/@me") - } catch (error) { - const axiosError = error as AxiosError - - if (axiosError.status === 401) { - setToken(undefined) - } - } - } -} - -export default function Login() { - let navigate = useNavigate() - let setToken = useTokenStore(state => state.setToken) - const { setCurrentUserId, addUser } = useUsersStore(useShallow(state => { - return { - setCurrentUserId: state.setCurrentUserId, - addUser: state.addUser - } - })) - - const form = useForm>({ - resolver: zodResolver(schema), - }) - - async function onSubmit(values: z.infer) { - const response = await auth.login(values) - - setToken(response.token) - setCurrentUserId(response.user.id) - addUser(response.user) - - navigate("/app") - } - - return ( - - - Welcome back! - Please sign in to continue. -
- -
-
- -
- - ( - - Username - - - - - - )} - /> - ( - - Password - - - - - - )} - /> - - - -
- -
- Don't have an account? - Register -
-
-
- ); -} -// file: ./app/routes/auth/register.tsx -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { Link, useNavigate } from "react-router"; -import { z } from "zod"; -import { PasswordInput } from "~/components/custom-ui/password-input"; -import { ThemeToggle } from "~/components/theme/theme-toggle"; -import { Button, buttonVariants } from "~/components/ui/button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "~/components/ui/form"; -import { Input } from "~/components/ui/input"; -import auth from "~/lib/api/client/auth"; - -const schema = z.object({ - email: z.string().email(), - displayName: z.string().min(1).optional(), - username: z.string().min(3).regex(/^[a-zA-Z0-9_.]+$/), - password: z.string().min(8), -}); - -export default function Register() { - let navigate = useNavigate() - - const form = useForm>({ - resolver: zodResolver(schema), - }) - - async function onSubmit(values: z.infer) { - await auth.register(values) - - navigate("/login") - } - - return ( - - - Create an account - Please fill out the form below to create an account. -
- -
-
- -
- - ( - - Email - - - - - - )} - /> - ( - - Username - - - - - - )} - /> - ( - - Display Name - - - - - - )} - /> - ( - - Password - - - - - - )} - /> - - - -
- -
- Already have an account? - Log In -
-
-
- ); -} -// file: ./app/routes/app/layout.tsx -import { Outlet, redirect } from "react-router"; -import AppLayout from "~/components/app-layout"; -import { useServerListStore } from "~/stores/server-list-store"; - -export async function clientLoader() { - const { servers, addServers } = useServerListStore.getState() - - try { - if (!servers || Object.values(servers).length === 0) { - const newServers = await import("~/lib/api/client/server").then(m => m.default.list()) - addServers(newServers) - } - - } catch (error) { - return redirect("/login") - } -} - -export default function Layout() { - - return ( - - - - ); -} -// file: ./app/routes/app/index.tsx -import { redirect } from "react-router"; - -export function clientLoader() { - return redirect("/app/@me") -} - -export default function Index() { - return null; -} - -// file: ./app/routes/app/me/index.tsx - -export default function Index() { - return ( - <> - {/*
-
-
-
- { }} - placeholder="Type your message here..." - // Example of custom styling: - // wrapperClassName="bg-gray-700 border-gray-600 rounded-lg" - // inputClassName="text-lg" - aria-label="Message input" /> -
-
-
-
*/} - - ); -} - -// file: ./app/routes/app/me/channel.tsx -import type { Route } from ".react-router/types/app/routes/app/me/+types/channel"; -import { Check } from "lucide-react"; -import { useShallow } from "zustand/react/shallow"; -import ChannelArea from "~/components/channel-area"; -import { Badge } from "~/components/ui/badge"; -import { usePrivateChannelsStore } from "~/stores/private-channels-store"; -import { useUsersStore } from "~/stores/users-store"; - -export default function Channel({ - params -}: Route.ComponentProps) { - const channelId = params.channelId - const currentUserId = useUsersStore(state => state.currentUserId) - - const nativeChannel = usePrivateChannelsStore(useShallow(state => state.channels[channelId])) - const recipients = nativeChannel.recipients.filter(recipient => recipient.id !== currentUserId) - - const renderSystemBadge = recipients.some(recipient => recipient.system) && recipients.length === 1 - - const channel = { - ...nativeChannel, - name: <> -
-
- {recipients.map(recipient => recipient.displayName || recipient.username).join(", ")} -
- {renderSystemBadge && System} -
- - } - - return ( - <> - - - ); -} -// file: ./app/routes/app/me/layout.tsx -import React from "react"; -import { Outlet } from "react-router"; -import { useShallow } from "zustand/react/shallow"; -import PrivateChannelListItem from "~/components/custom-ui/private-channel-list-item"; -import { ScrollArea } from "~/components/ui/scroll-area"; -import { usePrivateChannelsStore } from "~/stores/private-channels-store"; - -export async function clientLoader() { - const { channels, addChannels: setChannels } = usePrivateChannelsStore.getState() - - const channelList = Object.values(channels) - - if (!channels || channelList.length === 0) { - const channels = await import("~/lib/api/client/user").then(m => m.default.channels()) - setChannels(channels) - } -} - -function ListComponent() { - const channels = usePrivateChannelsStore(useShallow(state => Object.values(state.channels))) - - return ( -
-
-
- Private Messages -
-
- - -
- {channels.sort((a, b) => (a.lastMessageId ?? a.id) < (b.lastMessageId ?? b.id) ? 1 : -1).map((channel, _) => ( - - - - ))} -
-
-
- ) -} - -export const handle = { - listComponent: -} - -export default function Layout() { - return ( - <> - - - ); -} - -// file: ./app/routes/app/server/layout.tsx -import React from "react"; -import { Outlet, redirect, useNavigate, useParams, type ShouldRevalidateFunctionArgs } from "react-router"; -import { useShallow } from "zustand/react/shallow"; -import ServerChannelListItem from "~/components/custom-ui/channel-list-item"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "~/components/ui/dropdown-menu"; -import { ScrollArea } from "~/components/ui/scroll-area"; -import type { ServerId } from "~/lib/api/types"; -import { useGatewayStore } from "~/stores/gateway-store"; -import { ModalType, useModalStore } from "~/stores/modal-store"; -import { useServerChannelsStore } from "~/stores/server-channels-store"; -import { useServerListStore } from "~/stores/server-list-store"; -import { useUsersStore } from "~/stores/users-store"; -import type { Route } from "../server/+types/layout"; - -export async function clientLoader({ - params: { serverId } -}: Route.ClientLoaderArgs) { - const { channels, addChannels, addServer } = useServerChannelsStore.getState() - - const server = useServerListStore.getState().servers[serverId as ServerId] || undefined - - if (!server) { - return redirect("/app/@me") - } - - const channelList = channels[serverId as ServerId] - - if (channelList === undefined) { - const channels = await import("~/lib/api/client/server").then(m => m.default.listChannels(serverId as ServerId)) - addServer(serverId as ServerId) - addChannels(channels) - - useGatewayStore.getState().requestVoiceStates(serverId as ServerId) - } -} - -export function shouldRevalidate( - arg: ShouldRevalidateFunctionArgs -) { - return true -} - -function ListComponent() { - const serverId = useParams<{ serverId: ServerId }>().serverId! - const currentUserId = useUsersStore(state => state.currentUserId) - const onOpen = useModalStore(state => state.onOpen) - - const server = useServerListStore(useShallow(state => state.servers[serverId] || null)) - - const channels = Array.from(useServerChannelsStore(useShallow(state => Object.values(state.channels[serverId] || {})))) - - if (!server) { - return null - } - - return ( -
-
- - -
- {server?.name} -
-
- - onOpen(ModalType.CREATE_SERVER_INVITE, { serverId })}>Create invite - onOpen(ModalType.CREATE_SERVER_CHANNEL, { serverId })}>Create channel - - {currentUserId === server.ownerId && - onOpen(ModalType.DELETE_SERVER_CONFIRM, { serverId })}>Delete} - {currentUserId !== server.ownerId && - Leave} - -
-
- - -
- {channels.sort((a, b) => (a.lastMessageId ?? a.id) < (b.lastMessageId ?? b.id) ? 1 : -1).map((channel, _) => ( - - - - ))} -
-
-
- ) -} - -export const handle = { - listComponent: -} - -export default function Layout( - { params: { serverId } }: Route.ComponentProps -) { - const server = useServerListStore(useShallow(state => state.servers[serverId!] || null)) - const navigate = useNavigate() - - if (!server) { - setTimeout(() => navigate("/app/@me"), 0) - return null; - } - - return ( - <> - - - ); -} - -// file: ./app/routes/app/server/index.tsx - -export default function Index() { - return ( - <> - {/*
-
-
-
- { }} - placeholder="Type your message here..." - // Example of custom styling: - // wrapperClassName="bg-gray-700 border-gray-600 rounded-lg" - // inputClassName="text-lg" - aria-label="Message input" /> -
-
-
-
*/} - - ); -} - -// file: ./app/routes/app/server/channel.tsx -import { useShallow } from "zustand/react/shallow"; -import ChannelArea from "~/components/channel-area"; -import { useServerChannelsStore } from "~/stores/server-channels-store"; -import type { Route } from "./+types/channel"; - -export default function Channel( - { params: { serverId, channelId } }: Route.ComponentProps -) { - const channel = useServerChannelsStore(useShallow(state => state.channels[serverId][channelId])) - - return ( - <> - - - ); -} -// file: ./app/routes/app/invite.tsx -import { redirect } from "react-router"; -import type { Route } from "./+types/invite"; - -export async function clientLoader( - { params }: Route.ClientLoaderArgs -) { - const inviteCode = params.inviteCode - - try { - const response = await import("~/lib/api/client/server").then(m => m.default.getInvite(inviteCode)) - - return redirect(`/app/server/${response.id}`) - } catch (error) { - return redirect("/app/@me") - } -} - -export default function Index() { - return null; -} - -// file: ./app/routes/app/providers.tsx -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { Outlet, redirect } from "react-router"; -import { create } from "zustand"; -import { GatewayWebSocketConnectionManager } from "~/components/manager/gateway-websocket-connection-manager"; -import { WebRTCConnectionManager } from "~/components/manager/webrtc-connection-manager"; -import ModalProvider from "~/components/providers/modal-provider"; -import { useUsersStore } from "~/stores/users-store"; - -export async function clientLoader() { - const { currentUserId, setCurrentUserId, addUser } = useUsersStore.getState() - - try { - if (!currentUserId) { - const user = await import("~/lib/api/client/user").then(m => m.default.me()) - setCurrentUserId(user.id) - addUser(user) - } - } catch (error) { - return redirect("/login") - } -} - -const useQueryClient = create(() => new QueryClient()); - -export default function Layout() { - const queryClient = useQueryClient(); - - return ( - - <> - - - - - - - ); -} -// file: ./app/root.tsx -import { - isRouteErrorResponse, - Links, - Meta, - Outlet, - Scripts, - ScrollRestoration, -} from "react-router"; - -import { ThemeProvider } from "~/components/theme/theme-provider"; -import type { Route } from "./+types/root"; - -import "./app.css"; - -export const links: Route.LinksFunction = () => [ - { rel: "preconnect", href: "https://fonts.googleapis.com" }, - { - rel: "preconnect", - href: "https://fonts.gstatic.com", - crossOrigin: "anonymous", - }, - { - rel: "stylesheet", - href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", - }, -]; - -export default function App() { - return ( - - - - ); -} - -export function Layout({ children }: { children: React.ReactNode }) { - return ( - - - - - - - - - {children} - - - - - ); -} - -export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - let message = "Oops!"; - let details = "An unexpected error occurred."; - let stack: string | undefined; - - if (isRouteErrorResponse(error)) { - message = error.status === 404 ? "404" : "Error"; - details = - error.status === 404 - ? "The requested page could not be found." - : error.statusText || details; - } else if (import.meta.env.DEV && error && error instanceof Error) { - details = error.message; - stack = error.stack; - } - - return ( -
-

{message}

-

{details}

- {stack && ( -
-          {stack}
-        
- )} -
- ); -} - -// file: ./app/components/ui/button.tsx -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" -import * as React from "react" - -import { cn } from "~/lib/utils" - -const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", - { - variants: { - variant: { - default: - "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", - destructive: - "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", - outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", - secondary: - "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", - ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - none: "" - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) - -function Button({ - className, - variant, - size, - asChild = false, - ...props -}: React.ComponentProps<"button"> & - VariantProps & { - asChild?: boolean - }) { - const Comp = asChild ? Slot : "button" - - return ( - - ) -} - -export { Button, buttonVariants } - -// file: ./app/components/ui/form.tsx -import * as LabelPrimitive from "@radix-ui/react-label" -import { Slot } from "@radix-ui/react-slot" -import * as React from "react" -import { - Controller, - FormProvider, - useFormContext, - useFormState, - type ControllerProps, - type FieldPath, - type FieldValues, -} from "react-hook-form" - -import { Label } from "~/components/ui/label" -import { cn } from "~/lib/utils" - -const Form = FormProvider - -type FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, -> = { - name: TName, -} - -const FormFieldContext = React.createContext( - {} as FormFieldContextValue -) - -const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, ->({ - ...props -}: ControllerProps) => { - return ( - - - - ) -} - -const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext) - const itemContext = React.useContext(FormItemContext) - const { getFieldState } = useFormContext() - const formState = useFormState({ name: fieldContext.name }) - const fieldState = getFieldState(fieldContext.name, formState) - - if (!fieldContext) { - throw new Error("useFormField should be used within ") - } - - const { id } = itemContext - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - } -} - -type FormItemContextValue = { - id: string, -} - -const FormItemContext = React.createContext( - {} as FormItemContextValue -) - -function FormItem({ className, ...props }: React.ComponentProps<"div">) { - const id = React.useId() - - return ( - -
- - ) -} - -function FormLabel({ - className, - required, - ...props -}: React.ComponentProps & { required?: boolean }) { - const { error, formItemId } = useFormField() - - return ( -
-
- ) -} - -function FormControl({ ...props }: React.ComponentProps) { - const { error, formItemId, formDescriptionId, formMessageId } = useFormField() - - return ( - - ) -} - -function FormDescription({ className, ...props }: React.ComponentProps<"p">) { - const { formDescriptionId } = useFormField() - - return ( -

- ) -} - -function FormMessage({ className, ...props }: React.ComponentProps<"p">) { - const { error, formMessageId } = useFormField() - const body = error ? String(error?.message ?? "") : props.children - - if (!body) { - return null - } - - return ( -

- {body} -

- ) -} - -export { - Form, FormControl, - FormDescription, FormField, FormItem, - FormLabel, FormMessage, useFormField -} - - -// file: ./app/components/ui/label.tsx -"use client" - -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" - -import { cn } from "~/lib/utils" - -function Label({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { Label } - -// file: ./app/components/ui/input.tsx -import * as React from "react" - -import { cn } from "~/lib/utils" - -function Input({ className, type, ...props }: React.ComponentProps<"input">) { - return ( - - ) -} - -export { Input } - -// file: ./app/components/ui/tabs.tsx -import * as React from "react" -import * as TabsPrimitive from "@radix-ui/react-tabs" - -import { cn } from "~/lib/utils" - -function Tabs({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function TabsList({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function TabsTrigger({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function TabsContent({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { Tabs, TabsList, TabsTrigger, TabsContent } - -// file: ./app/components/ui/card.tsx -import * as React from "react" - -import { cn } from "~/lib/utils" - -function Card({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardDescription({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardAction({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardContent({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -export { - Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle -} - - -// file: ./app/components/ui/dropdown-menu.tsx -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" -import * as React from "react" - -import { cn } from "~/lib/utils" - -function DropdownMenu({ - ...props -}: React.ComponentProps) { - return -} - -function DropdownMenuPortal({ - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function DropdownMenuTrigger({ - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function DropdownMenuContent({ - className, - sideOffset = 4, - ...props -}: React.ComponentProps) { - return ( - - - - ) -} - -function DropdownMenuGroup({ - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function DropdownMenuItem({ - className, - inset, - variant = "default", - ...props -}: React.ComponentProps & { - inset?: boolean - variant?: "default" | "destructive" -}) { - return ( - - ) -} - -function DropdownMenuCheckboxItem({ - className, - children, - checked, - ...props -}: React.ComponentProps) { - return ( - - - - - - - {children} - - ) -} - -function DropdownMenuRadioGroup({ - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function DropdownMenuRadioItem({ - className, - children, - ...props -}: React.ComponentProps) { - return ( - - - - - - - {children} - - ) -} - -function DropdownMenuLabel({ - className, - inset, - ...props -}: React.ComponentProps & { - inset?: boolean -}) { - return ( - - ) -} - -function DropdownMenuSeparator({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function DropdownMenuShortcut({ - className, - ...props -}: React.ComponentProps<"span">) { - return ( - - ) -} - -function DropdownMenuSub({ - ...props -}: React.ComponentProps) { - return -} - -function DropdownMenuSubTrigger({ - className, - inset, - children, - ...props -}: React.ComponentProps & { - inset?: boolean -}) { - return ( - - {children} - - - ) -} - -function DropdownMenuSubContent({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { - DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, - DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger -} - - -// file: ./app/components/ui/separator.tsx -import * as React from "react" -import * as SeparatorPrimitive from "@radix-ui/react-separator" - -import { cn } from "~/lib/utils" - -function Separator({ - className, - orientation = "horizontal", - decorative = true, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { Separator } - -// file: ./app/components/ui/avatar.tsx -import * as AvatarPrimitive from "@radix-ui/react-avatar" -import * as React from "react" - -import { cn } from "~/lib/utils" - -function Avatar({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function AvatarImage({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function AvatarFallback({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { Avatar, AvatarFallback, AvatarImage } - - -// file: ./app/components/ui/scroll-area.tsx -import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" -import * as React from "react" - -import { cn } from "~/lib/utils" - -function ScrollArea({ - className, - children, - scrollbarSize, - viewportRef, - ...props -}: React.ComponentProps & { - scrollbarSize?: "default" | "narrow" | "none", - viewportRef?: React.Ref -}) { - return ( - - - {children} - - - - - ) -} - -function ScrollBar({ - className, - orientation = "vertical", - size = "default", - ...props -}: React.ComponentProps & { - size?: "default" | "narrow" | "none" -}) { - const classes = { - vertical: { - className: "h-full border-l border-l-transparent", - size: { - default: "w-2.5", - narrow: "w-1.5", - none: "hidden", - }, - }, - horizontal: { - className: "flex-col border-t border-t-transparent", - size: { - default: "h-2.5", - narrow: "h-1.5", - none: "hidden", - }, - }, - } - - return ( - - - - ) -} - -export { ScrollArea, ScrollBar } - -// file: ./app/components/ui/textarea.tsx -import * as React from "react" - -import { cn } from "~/lib/utils" - -function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { - return ( -