mobile responsiveness pass + connections reauth fix (#959)

Nova mobile pass
- viewport: viewportFit cover for iOS safe-area-inset
- safe-area utilities (pb-safe, pt-safe, bottom-safe-5, scroll-fade-x) in globals.css
- chat FAB pinned above iPhone home indicator; chat sidebar widths responsive across sm/md/lg with min() clamps
- chat input CoT panel max-h capped via min(60dvh, 420px)
- header tab strip swapped from visible scrollbar to scroll-fade-x mask + snap-x
- nova empty state uses svh on mobile, dvh from sm up

Add-memory modal rebuilt for mobile
- mobile shell switched from fullscreen Dialog to vaul Drawer at 85svh with swipe-down dismissal and scaled background
- in-modal header removed; tabs moved to the bottom of the sheet for thumb reach
- four tab compactLabels: Note, Links, Files, Connections
- desktop tabs now render only when !isMobile (no DOM duplication)
- note/link content state lifted to parent so switching tabs preserves typed input
- NoteContent snapshots initialContent via lazy useState so the editor isn't reset on every keystroke
- shared Drawer base uses rounded-t-xl
- removed legacy pt-4 on tab content for mobile

Connections — replace expiresAt with sync-run health
- new useConnectionHealth hook reads the latest sync run and matches auth-failure patterns; backend errorKind field still needed (TODO)
- regex tightened so 401/403 require co-occurring auth/token/grant context; refresh_token requires expired/revoked/invalid/missing qualifier
- badge label changed Disconnected -> Needs reauth
- Reconnect button replaces the sync action when needsReauth, kicks off the same OAuth flow
- per-row reconnect tracking via mutation.variables instead of a single shared id (no race when multiple rows clicked)
- fallback toast when authLink is missing so the spinner can't get stuck
- sync history panel timeline capped at max-h-260 with internal scroll
- useSyncRuns no longer refetches on mount; cache (30s) actually applies, cutting N requests per modal open
This commit is contained in:
MaheshtheDev 2026-05-17 21:49:23 +00:00
parent 36ecf47110
commit 1706752668
81 changed files with 1321 additions and 1127 deletions

View file

@ -1,4 +1,4 @@
import type { Metadata } from "next"
import type { Metadata, Viewport } from "next"
import { Space_Grotesk } from "next/font/google"
import "../globals.css"
import "@ui/globals.css"
@ -34,6 +34,12 @@ export const metadata: Metadata = {
title: "supermemory app",
}
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
viewportFit: "cover",
}
export default function RootLayout({
children,
}: Readonly<{

View file

@ -34,6 +34,7 @@ import {
import { RemoveConnectionDialog } from "@/components/remove-connection-dialog"
import { SyncStatusBadge } from "@/components/settings/sync-status-badge"
import { SyncHistoryPanel } from "@/components/settings/sync-history-panel"
import { useConnectionHealth } from "@/hooks/use-connection-health"
import { useTriggerSync } from "@/hooks/use-trigger-sync"
import { formatRelativeTime } from "@/components/settings/sync-utils"
import type { ImportProvider } from "@/components/settings/sync-utils"
@ -89,11 +90,6 @@ function getConnectionMeta(connection: Connection) {
}
}
/** Check if a connection's auth token has expired. */
function isConnectionExpired(connection: Connection): boolean {
return !!connection.expiresAt && new Date(connection.expiresAt) <= new Date()
}
function ConnectionRow({
connection,
onDelete,
@ -101,6 +97,8 @@ function ConnectionRow({
projects,
onTriggerSync,
isSyncing,
onReconnect,
isReconnecting,
}: {
connection: Connection
onDelete: () => void
@ -108,14 +106,17 @@ function ConnectionRow({
projects: Project[]
onTriggerSync: () => void
isSyncing: boolean
onReconnect: () => void
isReconnecting: boolean
}) {
const [historyOpen, setHistoryOpen] = useState(false)
const config = CONNECTORS[connection.provider as ConnectorProvider]
const { needsReauth } = useConnectionHealth(connection.id)
if (!config) return null
const Icon = config.icon
const meta = getConnectionMeta(connection)
const expired = isConnectionExpired(connection)
const expired = needsReauth
const getProjectName = (tag: string): string => {
if (tag === DEFAULT_PROJECT_ID) return "Default"
@ -137,14 +138,14 @@ function ConnectionRow({
)}
>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-4">
<div className="flex items-center gap-3">
<Icon className="size-6 shrink-0" />
<div className="flex-1 flex flex-col gap-1">
<div className="flex items-center gap-3">
<div className="min-w-0 flex-1 flex flex-col gap-1">
<div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1">
<span
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] text-[#FAFAFA]",
"truncate font-medium text-[16px] text-[#FAFAFA]",
)}
>
{config.title}
@ -156,41 +157,54 @@ function ConnectionRow({
/>
</div>
<span
className={cn(dmSans125ClassName(), "text-[14px] text-[#737373]")}
className={cn(
dmSans125ClassName(),
"truncate text-[14px] text-[#737373]",
)}
>
{connection.email || "Unknown"}
</span>
</div>
<div className="flex items-center gap-0.5">
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onTriggerSync()
}}
disabled={isSyncing || expired}
className="text-[#737373] hover:text-[#4BA0FA] transition-colors disabled:opacity-50 disabled:cursor-not-allowed p-1.5 rounded-lg hover:bg-white/5"
aria-label={
expired
? "Connection expired"
: isSyncing
? "Sync in progress"
: "Sync now"
}
title={
expired
? "Reconnect to sync"
: isSyncing
? "Sync in progress"
: "Sync now"
}
>
{isSyncing ? (
<Loader2 className="size-[18px] animate-spin" />
) : (
<Play className="size-[18px]" />
)}
</button>
<div className="flex shrink-0 items-center gap-0.5">
{expired ? (
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onReconnect()
}}
disabled={isReconnecting}
className={cn(
dmSans125ClassName(),
"flex items-center gap-1.5 rounded-full bg-[#EF4444]/15 px-3 py-1.5 text-[12px] font-medium text-[#EF4444] transition-colors hover:bg-[#EF4444]/25 disabled:opacity-60 disabled:cursor-not-allowed",
)}
aria-label="Reconnect"
>
{isReconnecting ? (
<Loader2 className="size-[14px] animate-spin" />
) : (
"Reconnect"
)}
</button>
) : (
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onTriggerSync()
}}
disabled={isSyncing}
className="text-[#737373] hover:text-[#4BA0FA] transition-colors disabled:opacity-50 disabled:cursor-not-allowed p-1.5 rounded-lg hover:bg-white/5"
aria-label={isSyncing ? "Sync in progress" : "Sync now"}
title={isSyncing ? "Sync in progress" : "Sync now"}
>
{isSyncing ? (
<Loader2 className="size-[18px] animate-spin" />
) : (
<Play className="size-[18px]" />
)}
</button>
)}
<button
type="button"
onClick={(e) => {
@ -411,6 +425,42 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
},
})
const reconnectMutation = useMutation({
mutationFn: async ({
connectionId: _connectionId,
provider,
containerTags,
}: {
connectionId: string
provider: ConnectorProvider
containerTags: string[] | undefined
}) => {
const response = await $fetch("@post/connections/:provider", {
params: { provider },
body: {
redirectUrl: window.location.href,
containerTags: containerTags ?? [selectedProject],
},
})
if ("data" in response && response.data && !("error" in response.data)) {
return response.data
}
throw new Error(response.error?.message || "Failed to reconnect")
},
onSuccess: (data) => {
if (data?.authLink) {
window.location.href = data.authLink
return
}
toast.error("Reconnect link missing — try again.")
},
onError: (error) => {
toast.error("Failed to reconnect", {
description: error instanceof Error ? error.message : "Unknown error",
})
},
})
const deleteConnectionMutation = useMutation({
mutationFn: async ({
connectionId,
@ -454,7 +504,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
connectingProvider !== null || addConnectionMutation.isPending
return (
<div className="h-full flex flex-col pt-4 space-y-4">
<div className="h-full flex flex-col pt-0 space-y-4 md:pt-4">
{/* Top header — only when empty; once connected, the Add CTA moves into the list header below */}
{!hasConnections && (
<div className="flex items-center justify-between px-2">
@ -571,13 +621,16 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
{/* Connected list - rich rows with status / project / last sync / doc count */}
{hasConnections && (
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between gap-3 px-1">
<div className="flex flex-col gap-0.5">
<div className="flex items-center justify-between gap-2 px-1">
<div className="flex min-w-0 flex-col gap-0.5">
<div className="flex items-center gap-2">
<p className="text-[16px] font-semibold">
Connected to Supermemory
<p className="truncate text-[16px] font-semibold">
<span className="hidden sm:inline">
Connected to Supermemory
</span>
<span className="sm:hidden">Connections</span>
</p>
<span className="bg-[#4BA0FA] text-black text-[10px] font-bold px-1 py-[2px] rounded-[3px]">
<span className="shrink-0 bg-[#4BA0FA] text-black text-[10px] font-bold px-1 py-[2px] rounded-[3px]">
PRO
</span>
</div>
@ -593,13 +646,16 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
<button
type="button"
disabled={!isProUser || isAnyConnecting}
className="flex items-center gap-1.5 bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 disabled:opacity-50 disabled:cursor-not-allowed text-[13px] font-medium rounded-full h-8 px-3 transition-colors shrink-0"
className="flex shrink-0 items-center gap-1.5 bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 disabled:opacity-50 disabled:cursor-not-allowed text-[13px] font-medium rounded-full h-8 px-3 transition-colors"
>
{isAnyConnecting ? (
<Loader className="size-3.5 animate-spin" />
) : (
<>
<span>+ Add a connection</span>
<span className="hidden sm:inline">
+ Add a connection
</span>
<span className="sm:hidden">+ Add</span>
<ChevronDown className="size-3" />
</>
)}
@ -722,6 +778,17 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
triggerSync.variables?.connectionId === connection.id) ||
getConnectionMeta(connection).syncInProgress
}
onReconnect={() => {
reconnectMutation.mutate({
connectionId: connection.id,
provider: connection.provider as ConnectorProvider,
containerTags: connection.containerTags,
})
}}
isReconnecting={
reconnectMutation.isPending &&
reconnectMutation.variables?.connectionId === connection.id
}
/>
))}
</div>

View file

@ -187,7 +187,12 @@ export function FileContent({
const hasItems = data.items.length > 0
return (
<div className={cn("h-full flex flex-col gap-6 pt-4", dmSansClassName())}>
<div
className={cn(
"h-full flex flex-col gap-6 pt-0 md:pt-4",
dmSansClassName(),
)}
>
<div className="flex flex-col gap-2">
<p className="text-[16px] font-medium pl-2">
Upload files (images, PDF, documents, sheets, markdown)

View file

@ -3,9 +3,10 @@
import { useState, useEffect, useCallback, useRef } from "react"
import { useQueryState } from "nuqs"
import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog"
import { Drawer, DrawerContent, DrawerTitle } from "@repo/ui/components/drawer"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { FileTextIcon, GlobeIcon, ZapIcon, Loader2, XIcon } from "lucide-react"
import { FileTextIcon, GlobeIcon, ZapIcon, Loader2 } from "lucide-react"
import { Button } from "@ui/components/button"
import { ConnectContent } from "./connections"
import { NoteContent } from "./note"
@ -31,14 +32,36 @@ interface AddDocumentModalProps {
export function AddDocumentModal({ isOpen, onClose }: AddDocumentModalProps) {
const isMobile = useIsMobile()
if (isMobile) {
return (
<Drawer
open={isOpen}
onOpenChange={(open: boolean) => !open && onClose()}
shouldScaleBackground
>
<DrawerContent
className={cn(
"flex flex-col gap-0 border-none bg-[#1B1F24] p-0",
"h-[85svh] max-h-[85svh] overflow-hidden",
"[&>div:first-child]:bg-[#3A4252] [&>div:first-child]:h-1 [&>div:first-child]:w-9 [&>div:first-child]:mt-2.5 [&>div:first-child]:mb-1",
dmSansClassName(),
)}
>
<DrawerTitle className="sr-only">Add Document</DrawerTitle>
<div className="min-h-0 flex-1 overflow-hidden">
<AddDocument onClose={onClose} isOpen={isOpen} />
</div>
</DrawerContent>
</Drawer>
)
}
return (
<Dialog open={isOpen} onOpenChange={(open: boolean) => !open && onClose()}>
<DialogContent
className={cn(
"border-none bg-[#1B1F24] flex flex-col",
isMobile
? "top-2! left-2! translate-x-0! translate-y-0! w-[calc(100vw-1rem)]! h-[calc(100dvh-1rem)]! max-w-none! max-h-none! rounded-[18px] p-0 gap-0 overflow-hidden"
: "w-[80%]! max-w-[1000px]! h-[80%]! max-h-[800px]! rounded-[22px] p-4 gap-3",
"w-[80%]! max-w-[1000px]! h-[80%]! max-h-[800px]! rounded-[22px] p-4 gap-3",
dmSansClassName(),
)}
style={{
@ -61,24 +84,28 @@ const tabs = [
id: "note" as const,
icon: FileTextIcon,
title: "Write a note",
compactLabel: "Note",
description: "Save your thoughts, notes and summaries, as memories",
},
{
id: "link" as const,
icon: GlobeIcon,
title: "Save a link",
compactLabel: "Links",
description: "Add any webpage into your searchable knowledge base",
},
{
id: "file" as const,
icon: FileTextIcon,
title: "Upload files",
compactLabel: "Files",
description: "Turn images, PDFs, documents, and markdown into memories",
},
{
id: "connect" as const,
icon: ZapIcon,
title: "Connect knowledge bases",
compactLabel: "Connections",
description: "Sync with Google Drive, Notion and OneDrive and import data",
isPro: true,
},
@ -141,6 +168,8 @@ export function AddDocument({
useEffect(() => {
if (!isOpen) {
setFileData({ items: [], title: "", description: "" })
setNoteContent("")
setLinkData({ url: "", title: "", description: "" })
}
}, [isOpen])
@ -257,112 +286,56 @@ export function AddDocument({
return (
<div className="flex h-full min-h-0 flex-col overflow-hidden text-white md:flex-row md:space-x-5">
<div
className={cn(
"flex flex-col justify-between",
isMobile
? "w-full shrink-0 border-b border-[#0F1621] bg-[#1B1F24] px-3 pt-3 pb-3"
: "w-1/3",
)}
>
{isMobile && (
<div className="mb-3 flex items-center justify-between">
<div>
<p
className={cn(
"text-sm font-medium text-white",
dmSansClassName(),
)}
>
Add memory
</p>
<p className="text-xs text-[#737373]">
Save something to recall later
</p>
</div>
<button
type="button"
onClick={onClose}
disabled={isSubmitting}
className="flex size-9 items-center justify-center rounded-full border border-[#1F2937] bg-[#0D121A] text-[#8B8B8B] transition-colors hover:text-white disabled:opacity-50"
aria-label="Close add memory"
>
<XIcon className="size-4" />
</button>
</div>
)}
<div
className={cn(
isMobile ? "grid grid-cols-4 gap-1" : "flex flex-col gap-1",
)}
>
{tabs.map((tab) => (
<TabButton
key={tab.id}
active={activeTab === tab.id}
onClick={() => setActiveTab(tab.id)}
icon={tab.icon}
title={tab.title}
description={tab.description}
isPro={tab.isPro}
compact={isMobile}
/>
))}
</div>
{isMobile && (
<div className="mt-3 flex flex-col gap-2">
<div className="flex justify-between items-center">
<span
className={cn(
"text-[#FAFAFA] text-sm font-medium",
dmSansClassName(),
)}
>
Plan usage
</span>
<span
className={cn(
"text-sm font-medium tabular-nums",
hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]",
dmSansClassName(),
)}
>
{isLoadingUsage
? "…"
: `${planUsagePct < 1 && planUsagePct > 0 ? "< 1" : Math.round(planUsagePct)}% used`}
</span>
</div>
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full rounded-[40px]"
style={{
width: `${planUsagePct}%`,
background:
planUsagePct > 80
? "#ef4444"
: hasPaidPlan
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
: "#0054AD",
}}
title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`}
/>
</div>
{!isLoadingUsage && (
<p
className={cn(
"text-xs text-[#737373] tabular-nums",
dmSansClassName(),
)}
>
{formatUsageNumber(tokensUsed)} tokens ·{" "}
{formatUsageNumber(searchesUsed)} queries
</p>
{isMobile && !hasPaidPlan && (
<div className="flex shrink-0 justify-end px-4 pb-2">
<button
type="button"
onClick={async () => {
setIsUpgrading(true)
try {
const result = await autumn.attach({
planId: "api_pro",
successUrl: `${window.location.origin}/settings#account`,
})
if (result?.paymentUrl) {
window.open(result.paymentUrl, "_self")
return
}
autumn.refetch?.()
} catch (error) {
console.error(error)
toast.error("Failed to start checkout. Please try again.")
} finally {
setIsUpgrading(false)
}
}}
disabled={isUpgrading}
className={cn(
"shrink-0 cursor-pointer rounded-full bg-[#0054AD]/30 px-2.5 py-1 text-[11px] font-medium text-[#4BA0FA] transition-colors hover:bg-[#0054AD]/50 disabled:opacity-60",
dmSansClassName(),
)}
>
{isUpgrading ? "Upgrading…" : "Upgrade"}
</button>
</div>
)}
{!isMobile && (
<div className="flex w-1/3 flex-col justify-between">
<div className="flex flex-col gap-1">
{tabs.map((tab) => (
<TabButton
key={tab.id}
active={activeTab === tab.id}
onClick={() => setActiveTab(tab.id)}
icon={tab.icon}
title={tab.title}
compactLabel={tab.compactLabel}
description={tab.description}
isPro={tab.isPro}
/>
))}
</div>
)}
{!isMobile && (
<div data-testid="usage-counter" className="flex flex-col gap-3 mr-4">
<div className="flex flex-col gap-2">
<div className="flex justify-between items-center">
@ -463,13 +436,13 @@ export function AddDocument({
</button>
)}
</div>
)}
</div>
</div>
)}
<div
className={cn(
"flex min-h-0 flex-1 flex-col",
isMobile ? "w-full px-3 pt-3" : "w-2/3 px-1",
isMobile ? "w-full px-4 pt-1" : "w-2/3 px-1",
)}
>
<div className="min-h-0 flex-1 overflow-auto scrollbar-thin">
@ -479,6 +452,7 @@ export function AddDocument({
onContentChange={handleNoteContentChange}
isSubmitting={noteMutation.isPending}
isOpen={isOpen}
initialContent={noteContent}
/>
)}
{activeTab === "link" && (
@ -487,6 +461,7 @@ export function AddDocument({
onDataChange={handleLinkDataChange}
isSubmitting={linkMutation.isPending}
isOpen={isOpen}
initialData={linkData}
/>
)}
{activeTab === "file" && (
@ -506,12 +481,29 @@ export function AddDocument({
</div>
<div
className={cn(
"flex shrink-0 gap-2",
"flex shrink-0",
isMobile
? "mx-[-0.75rem] mt-3 border-t border-[#0F1621] bg-[#1B1F24] px-3 py-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]"
: "justify-between pt-3",
? "mx-[-1rem] mt-3 flex-col gap-3 border-t border-[#0F1621] bg-[#1B1F24] px-4 pt-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]"
: "justify-between gap-2 pt-3",
)}
>
{isMobile && (
<div className="flex h-10 w-full shrink-0 items-center overflow-hidden rounded-full border border-[#1F2937] bg-[#0D121A] p-1">
{tabs.map((tab) => (
<TabButton
key={tab.id}
active={activeTab === tab.id}
onClick={() => setActiveTab(tab.id)}
icon={tab.icon}
title={tab.title}
compactLabel={tab.compactLabel}
description={tab.description}
isPro={tab.isPro}
compact
/>
))}
</div>
)}
{!isMobile && (
<SpaceSelector
selectedProjects={[localSelectedProject]}
@ -524,20 +516,19 @@ export function AddDocument({
<div
className={cn(
"flex items-center gap-2",
isMobile && "w-full justify-end",
isMobile ? "w-full" : "justify-end",
)}
>
<Button
variant="ghost"
onClick={onClose}
disabled={isSubmitting}
className={cn(
"cursor-pointer rounded-full text-[#737373]",
isMobile && "h-11 px-4",
)}
>
Cancel
</Button>
{!isMobile && (
<Button
variant="ghost"
onClick={onClose}
disabled={isSubmitting}
className="cursor-pointer rounded-full text-[#737373]"
>
Cancel
</Button>
)}
{activeTab !== "connect" && (
<Button
variant="insideOut"
@ -545,7 +536,7 @@ export function AddDocument({
disabled={
activeTab === "file" ? fileTabSubmitDisabled : isSubmitting
}
className={cn(isMobile && "h-11 min-w-[8rem] px-5")}
className={cn(isMobile && "h-12 w-full px-5 text-[15px]")}
>
{isSubmitting ? (
<>
@ -581,6 +572,7 @@ function TabButton({
onClick,
icon: Icon,
title,
compactLabel,
description,
isPro,
compact,
@ -589,6 +581,7 @@ function TabButton({
onClick: () => void
icon: React.ComponentType<{ className?: string }>
title: string
compactLabel?: string
description: string
isPro?: boolean
compact?: boolean
@ -599,26 +592,27 @@ function TabButton({
type="button"
onClick={onClick}
className={cn(
"relative flex h-14 min-w-0 flex-col items-center justify-center gap-1 rounded-xl px-1 text-center transition-colors focus:outline-none focus:ring-0",
"relative flex h-full min-w-0 flex-1 basis-0 cursor-pointer items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-1 text-center transition-colors focus:outline-none focus:ring-0",
active
? "bg-[#0F141B] text-white shadow-inside-out ring-1 ring-[#2261CA33]"
: "text-[#8B8B8B] hover:bg-[#14161A]/50",
? "border-[#2261CA33] bg-[#00173C] text-white"
: "text-[#8B8B8B] hover:bg-white/5",
dmSansClassName(),
)}
>
<Icon className="size-3.5 shrink-0" />
<span
className={cn(
"min-w-0 truncate text-xs font-medium leading-none",
"min-w-0 truncate text-[13px] font-medium",
dmSansClassName(),
)}
>
{title.split(" ")[0]}
{compactLabel ?? title.split(" ")[0]}
</span>
{isPro && (
<span className="absolute top-1 right-1 rounded bg-[#4BA0FA] px-1 py-0.5 text-[7px] font-semibold leading-none text-black">
PRO
</span>
<span
role="img"
aria-label="Pro"
className="size-1.5 shrink-0 rounded-full bg-[#4BA0FA]"
/>
)}
</button>
)

View file

@ -20,6 +20,7 @@ interface LinkContentProps {
onDataChange?: (data: LinkData) => void
isSubmitting?: boolean
isOpen?: boolean
initialData?: LinkData
}
export function LinkContent({
@ -27,11 +28,12 @@ export function LinkContent({
onDataChange,
isSubmitting,
isOpen,
initialData,
}: LinkContentProps) {
const [url, setUrl] = useState("")
const [title, setTitle] = useState("")
const [description, setDescription] = useState("")
const [image, setImage] = useState<string | undefined>(undefined)
const [url, setUrl] = useState(initialData?.url ?? "")
const [title, setTitle] = useState(initialData?.title ?? "")
const [description, setDescription] = useState(initialData?.description ?? "")
const [image, setImage] = useState<string | undefined>(initialData?.image)
const [isPreviewLoading, setIsPreviewLoading] = useState(false)
const canSubmit = url.trim().length > 0 && !isSubmitting
@ -148,7 +150,12 @@ export function LinkContent({
}, [isOpen, onDataChange])
return (
<div className={cn("flex flex-col space-y-4 pt-4 mb-4", dmSansClassName())}>
<div
className={cn(
"flex flex-col space-y-4 pt-0 mb-4 md:pt-4",
dmSansClassName(),
)}
>
<div>
<p
className={cn("text-[16px] font-medium pl-2 pb-2", dmSansClassName())}

View file

@ -1,6 +1,6 @@
"use client"
import { useState, useEffect } from "react"
import { useState } from "react"
import { TextEditor } from "../text-editor"
interface NoteContentProps {
@ -8,15 +8,17 @@ interface NoteContentProps {
onContentChange?: (content: string) => void
isSubmitting?: boolean
isOpen?: boolean
initialContent?: string
}
export function NoteContent({
onSubmit,
onContentChange,
isSubmitting,
isOpen,
initialContent,
}: NoteContentProps) {
const [content, setContent] = useState("")
const [content, setContent] = useState(initialContent ?? "")
const [seededContent] = useState(initialContent || undefined)
const canSubmit = content.trim().length > 0 && !isSubmitting
@ -31,18 +33,10 @@ export function NoteContent({
onContentChange?.(newContent)
}
// Reset content when modal closes
useEffect(() => {
if (!isOpen) {
setContent("")
onContentChange?.("")
}
}, [isOpen, onContentChange])
return (
<div className="flex h-full min-h-[45dvh] w-full flex-1 overflow-y-auto rounded-[14px] bg-[#10151C] p-3 shadow-inside-out ring-1 ring-[#202A36] md:mb-4! md:bg-[#14161A] md:p-4 md:ring-0">
<TextEditor
content={undefined}
content={seededContent}
onContentChange={handleContentChange}
onSubmit={handleSubmit}
debounceMs={0}

View file

@ -139,7 +139,7 @@ export function ChatLaunchFab({
className={cn(
"flex items-start justify-start pointer-events-none",
isMobile
? "fixed bottom-5 right-0 left-0 z-50 justify-center items-center"
? "fixed bottom-safe-5 right-0 left-0 z-50 justify-center items-center pl-safe pr-safe"
: "fixed z-20 top-24 right-4 md:right-6",
dmSansClassName(),
)}
@ -793,7 +793,7 @@ export function ChatSidebar({
<SheetContent
side="right"
className={cn(
"flex h-full max-h-dvh w-full flex-col gap-0 overflow-hidden border-[#17181AB2] bg-[#0A0E14] p-0 text-white sm:max-w-md",
"flex h-full max-h-dvh w-[min(100%,92vw)] flex-col gap-0 overflow-hidden border-[#17181AB2] bg-[#0A0E14] p-0 pb-safe text-white sm:max-w-md",
"[&>button]:text-[#FAFAFA]",
dmSansClassName(),
)}
@ -1185,10 +1185,10 @@ export function ChatSidebar({
className={cn(
"relative flex flex-col backdrop-blur-md",
isMobile
? "fixed inset-0 z-50 m-0 h-dvh w-full rounded-none"
? "fixed inset-0 z-50 m-0 h-dvh w-full rounded-none pb-safe"
: isPageDesktop
? "flex h-full min-h-0 w-full min-w-0 flex-1 flex-col basis-0 rounded-none border-x-0"
: "m-4 mt-2 w-[450px] rounded-2xl",
: "m-4 mt-2 w-[min(450px,calc(100vw-2rem))] md:w-[380px] lg:w-[450px] rounded-2xl",
dmSansClassName(),
)}
style={
@ -1226,9 +1226,9 @@ export function ChatSidebar({
chatProject === AUTO_CHAT_SPACE_ID ? null : [chatProject]
}
/>
<div className="flex h-full min-h-0 w-full min-w-0 max-w-[720px] shrink-0 basis-[min(720px,50vw)] flex-col">
<div className="flex h-full min-h-0 w-full min-w-0 max-w-[min(720px,100%)] shrink-0 basis-[min(720px,50vw)] flex-col">
{pageDesktopToolbarRow}
<div className="relative mx-auto flex h-full min-h-0 w-full min-w-0 max-w-[720px] flex-1 flex-col">
<div className="relative mx-auto flex h-full min-h-0 w-full min-w-0 max-w-[min(720px,100%)] flex-1 flex-col px-3 sm:px-4 md:px-0">
{shell}
</div>
</div>

View file

@ -89,7 +89,7 @@ export default function ChatInput({
className={cn(
"absolute bottom-full left-0 right-0 overflow-hidden transition-all duration-300 ease-out bg-[#000B1B]",
isExpanded
? "max-h-[60vh] opacity-100 overflow-y-auto pt-1.5 pb-2 rounded-t-xl px-4"
? "max-h-[min(60dvh,420px)] opacity-100 overflow-y-auto pt-1.5 pb-2 rounded-t-xl px-4"
: "max-h-0 opacity-0",
)}
style={{

View file

@ -173,7 +173,7 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
role="tablist"
aria-label="Content"
aria-orientation="horizontal"
className="text-muted-foreground z-10! inline-flex h-10 w-fit min-w-0 max-w-full items-center justify-center gap-0.5 overflow-x-auto rounded-full border border-[#161F2C] bg-muted p-1 [scrollbar-width:thin]"
className="text-muted-foreground z-10! inline-flex h-10 w-fit min-w-0 max-w-full items-center justify-center gap-0.5 overflow-x-auto snap-x snap-mandatory scroll-fade-x rounded-full border border-[#161F2C] bg-muted p-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
{(
[
@ -210,7 +210,7 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
}
onClick={() => void setViewMode(mode)}
className={cn(
"inline-flex h-[calc(100%-1px)] min-h-0 cursor-pointer items-center justify-center gap-1 rounded-full border border-transparent px-2.5 text-xs font-medium whitespace-nowrap transition-colors sm:gap-1.5 sm:px-3 sm:text-sm",
"inline-flex h-[calc(100%-1px)] min-h-0 cursor-pointer snap-start items-center justify-center gap-1 rounded-full border border-transparent px-2.5 text-xs font-medium whitespace-nowrap transition-colors sm:gap-1.5 sm:px-3 sm:text-sm",
(
mode === "integrations"
? [

View file

@ -39,7 +39,7 @@ export function NovaEmptyState({
return (
<div
id="nova-empty-state"
className="min-h-[calc(100dvh-12rem)] flex items-center justify-center p-6 md:p-8 opacity-50 hover:opacity-100 transition-opacity duration-300"
className="min-h-[calc(100svh-12rem)] sm:min-h-[calc(100dvh-12rem)] flex items-center justify-center p-4 sm:p-6 md:p-8 opacity-50 hover:opacity-100 transition-opacity duration-300"
>
<div className="max-w-xl w-full flex flex-col items-center text-center">
<NovaOrb size={80} className="blur-[2px]! mb-4" />
@ -119,7 +119,7 @@ export function NovaEmptyState({
</>
)}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 w-full mb-4">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 sm:gap-3 w-full mb-4">
<button
type="button"
onClick={() => onAddMemory("link")}

View file

@ -31,6 +31,7 @@ import { DEFAULT_PROJECT_ID } from "@lib/constants"
import type { Project } from "@lib/types"
import { SyncStatusBadge } from "@/components/settings/sync-status-badge"
import { SyncHistoryPanel } from "@/components/settings/sync-history-panel"
import { useConnectionHealth } from "@/hooks/use-connection-health"
import { useTriggerSync } from "@/hooks/use-trigger-sync"
import { formatRelativeTime } from "@/components/settings/sync-utils"
import type { ImportProvider } from "@/components/settings/sync-utils"
@ -48,11 +49,6 @@ function getConnectionMeta(connection: Connection) {
}
}
/** Check if a connection's auth token has expired. */
function isConnectionExpired(connection: Connection): boolean {
return !!connection.expiresAt && new Date(connection.expiresAt) <= new Date()
}
const CONNECTORS = {
"google-drive": {
title: "Google Drive",
@ -166,6 +162,8 @@ function ConnectionRow({
projects,
onTriggerSync,
isSyncing,
onReconnect,
isReconnecting,
}: {
connection: Connection
onDelete: () => void
@ -174,14 +172,17 @@ function ConnectionRow({
projects: Project[]
onTriggerSync: () => void
isSyncing: boolean
onReconnect: () => void
isReconnecting: boolean
}) {
const [historyOpen, setHistoryOpen] = useState(false)
const config = CONNECTORS[connection.provider as ConnectorProvider]
const { needsReauth } = useConnectionHealth(connection.id)
if (!config) return null
const Icon = config.icon
const meta = getConnectionMeta(connection)
const expired = isConnectionExpired(connection)
const expired = needsReauth
const getProjectDisplayName = (containerTag: string): string => {
if (containerTag === DEFAULT_PROJECT_ID) return "Default Project"
@ -234,35 +235,45 @@ function ConnectionRow({
</span>
</div>
<div className="flex items-center gap-0.5">
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onTriggerSync()
}}
disabled={isSyncing || disabled || expired}
className="text-[#737373] hover:text-[#4BA0FA] transition-colors disabled:opacity-50 disabled:cursor-not-allowed p-1.5 rounded-lg hover:bg-white/5"
aria-label={
expired
? "Connection expired"
: isSyncing
? "Sync in progress"
: "Sync now"
}
title={
expired
? "Reconnect to sync"
: isSyncing
? "Sync in progress"
: "Sync now"
}
>
{isSyncing ? (
<Loader2 className="size-[18px] animate-spin" />
) : (
<Play className="size-[18px]" />
)}
</button>
{expired ? (
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onReconnect()
}}
disabled={isReconnecting || disabled}
className={cn(
dmSans125ClassName(),
"flex items-center gap-1.5 rounded-full bg-[#EF4444]/15 px-3 py-1.5 text-[12px] font-medium text-[#EF4444] transition-colors hover:bg-[#EF4444]/25 disabled:opacity-60 disabled:cursor-not-allowed",
)}
aria-label="Reconnect"
>
{isReconnecting ? (
<Loader2 className="size-[14px] animate-spin" />
) : (
"Reconnect"
)}
</button>
) : (
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onTriggerSync()
}}
disabled={isSyncing || disabled}
className="text-[#737373] hover:text-[#4BA0FA] transition-colors disabled:opacity-50 disabled:cursor-not-allowed p-1.5 rounded-lg hover:bg-white/5"
aria-label={isSyncing ? "Sync in progress" : "Sync now"}
title={isSyncing ? "Sync in progress" : "Sync now"}
>
{isSyncing ? (
<Loader2 className="size-[18px] animate-spin" />
) : (
<Play className="size-[18px]" />
)}
</button>
)}
<button
type="button"
onClick={(e) => {
@ -461,6 +472,42 @@ export default function ConnectionsMCP() {
}
}, [connectionsError])
const reconnectMutation = useMutation({
mutationFn: async ({
connectionId: _connectionId,
provider,
containerTags,
}: {
connectionId: string
provider: ConnectorProvider
containerTags: string[] | undefined
}) => {
const response = await $fetch("@post/connections/:provider", {
params: { provider },
body: {
redirectUrl: window.location.href,
containerTags: containerTags ?? [],
},
})
if ("data" in response && response.data && !("error" in response.data)) {
return response.data
}
throw new Error(response.error?.message || "Failed to reconnect")
},
onSuccess: (data) => {
if (data?.authLink) {
window.location.href = data.authLink
return
}
toast.error("Reconnect link missing — try again.")
},
onError: (error) => {
toast.error("Failed to reconnect", {
description: error instanceof Error ? error.message : "Unknown error",
})
},
})
const deleteConnectionMutation = useMutation({
mutationFn: async ({
connectionId,
@ -580,6 +627,18 @@ export default function ConnectionsMCP() {
connection.id) ||
getConnectionMeta(connection).syncInProgress
}
onReconnect={() => {
reconnectMutation.mutate({
connectionId: connection.id,
provider: connection.provider as ConnectorProvider,
containerTags: connection.containerTags,
})
}}
isReconnecting={
reconnectMutation.isPending &&
reconnectMutation.variables?.connectionId ===
connection.id
}
/>
))
) : (

View file

@ -259,7 +259,9 @@ export function SyncHistoryPanel({
{hasRuns && (
<>
<SummaryStats runs={syncRuns} />
<Timeline runs={syncRuns} />
<div className="max-h-[260px] overflow-y-auto scrollbar-thin pr-1 -mr-1">
<Timeline runs={syncRuns} />
</div>
</>
)}
</div>

View file

@ -77,7 +77,7 @@ export function SyncStatusBadge({
"font-medium text-[13px] tracking-[-0.13px] text-[#EF4444]",
)}
>
Disconnected
Needs reauth
</span>
)}
{status === "idle" && (

View file

@ -81,6 +81,44 @@
display: none;
}
.pb-safe {
padding-bottom: max(0px, env(safe-area-inset-bottom));
}
.pt-safe {
padding-top: max(0px, env(safe-area-inset-top));
}
.pl-safe {
padding-left: max(0px, env(safe-area-inset-left));
}
.pr-safe {
padding-right: max(0px, env(safe-area-inset-right));
}
.bottom-safe-5 {
bottom: max(1.25rem, env(safe-area-inset-bottom));
}
/* hide scrollbar but keep edge-fade affordance for horizontal nav */
.scroll-fade-x {
-webkit-mask-image: linear-gradient(
to right,
transparent 0,
#000 12px,
#000 calc(100% - 12px),
transparent 100%
);
mask-image: linear-gradient(
to right,
transparent 0,
#000 12px,
#000 calc(100% - 12px),
transparent 100%
);
}
.sm-tweet-theme .react-tweet-theme {
--tweet-container-margin: 0px;
font-size: inherit !important;

View file

@ -0,0 +1,29 @@
"use client"
import { useSyncRuns, type SyncRun } from "@/hooks/use-sync-runs"
// TODO: replace string matching with a discriminated `errorKind` from the backend.
// 403 alone matches per-file ACL denials; 401 alone matches transient retries.
// Require the status code to co-occur with explicit auth/token/grant context.
const AUTH_ERROR_PATTERNS = [
/invalid[_\s-]?grant/i,
/unauthorized[_\s-]?client/i,
/\bunauthenticated\b/i,
/needs?[_\s-]?reauth/i,
/no\s+refresh[_\s-]?token/i,
/(?:access|refresh)[_\s-]?token[^\n]{0,40}(?:expired|revoked|invalid|missing)/i,
/\b(?:401|403)\b[^\n]{0,80}(?:auth|token|grant|credentials?)/i,
/(?:auth|token|grant|credentials?)[^\n]{0,80}\b(?:401|403)\b/i,
]
function isAuthFailure(run: SyncRun): boolean {
if (run.status !== "failed" || !run.error) return false
return AUTH_ERROR_PATTERNS.some((p) => p.test(run.error ?? ""))
}
export function useConnectionHealth(connectionId: string) {
const { data: runs, isLoading } = useSyncRuns(connectionId)
const latest = runs?.[0] ?? null
const needsReauth = !!latest && isAuthFailure(latest)
return { needsReauth, latestRun: latest, isLoading }
}

View file

@ -34,7 +34,6 @@ export function useSyncRuns(connectionId: string) {
},
enabled: !!connectionId,
staleTime: 30 * 1000,
refetchOnMount: "always",
refetchInterval: (query) => {
const runs = query.state.data as SyncRun[] | undefined
if (runs?.some((r) => r.status === "running")) {

View file

@ -2,8 +2,8 @@ export const Logo = ({
className,
id,
}: {
className?: string;
id?: string;
className?: string
id?: string
}) => {
return (
<svg
@ -19,15 +19,15 @@ export const Logo = ({
fill="#ffffff"
/>
</svg>
);
};
)
}
export const LogoFull = ({
className,
id,
}: {
className?: string;
id?: string;
className?: string
id?: string
}) => {
return (
<svg
@ -47,8 +47,8 @@ export const LogoFull = ({
</clipPath>
</defs>
</svg>
);
};
)
}
export const GradientLogo = ({ className = "" }: { className?: string }) => {
return (
@ -106,8 +106,8 @@ export const GradientLogo = ({ className = "" }: { className?: string }) => {
</clipPath>
</defs>
</svg>
);
};
)
}
export const LogoBgGradient = ({ className = "" }: { className?: string }) => {
return (
@ -311,5 +311,5 @@ export const LogoBgGradient = ({ className = "" }: { className?: string }) => {
</filter>
</defs>
</svg>
);
};
)
}

View file

@ -22,7 +22,7 @@ export const OneDrive = ({ className }: { className?: string }) => (
fill="#28A8EA"
/>
</svg>
);
)
export const GoogleDrive = ({ className }: { className?: string }) => (
<svg
@ -56,7 +56,7 @@ export const GoogleDrive = ({ className }: { className?: string }) => (
fill="#FFBA00"
/>
</svg>
);
)
export const Notion = ({ className }: { className?: string }) => (
<svg
@ -71,7 +71,7 @@ export const Notion = ({ className }: { className?: string }) => (
/>
<path d="M164.09.608L16.092 11.538C4.155 12.573 0 20.374 0 29.726v162.245c0 7.284 2.585 13.516 8.826 21.843l34.789 45.237c5.715 7.284 10.912 8.844 21.825 8.327l171.864-10.404c14.532-1.035 18.696-7.801 18.696-19.24V55.207c0-5.911-2.336-7.614-9.21-12.66l-1.185-.856L198.37 8.409C186.94.1 182.27-.952 164.09.608M69.327 52.22c-14.033.945-17.216 1.159-25.186-5.323L23.876 30.778c-2.06-2.086-1.026-4.69 4.163-5.207l142.274-10.395c11.947-1.043 18.17 3.12 22.842 6.758l24.401 17.68c1.043.525 3.638 3.637.517 3.637L71.146 52.095zm-16.36 183.954V81.222c0-6.767 2.077-9.887 8.3-10.413L230.02 60.93c5.724-.517 8.31 3.12 8.31 9.879v153.917c0 6.767-1.044 12.49-10.387 13.008l-161.487 9.361c-9.343.517-13.489-2.594-13.489-10.921M212.377 89.53c1.034 4.681 0 9.362-4.681 9.897l-7.783 1.542v114.404c-6.758 3.637-12.981 5.715-18.18 5.715c-8.308 0-10.386-2.604-16.609-10.396l-50.898-80.079v77.476l16.1 3.646s0 9.362-12.989 9.362l-35.814 2.077c-1.043-2.086 0-7.284 3.63-8.318l9.351-2.595V109.823l-12.98-1.052c-1.044-4.68 1.55-11.439 8.826-11.965l38.426-2.585l52.958 81.113v-71.76l-13.498-1.552c-1.043-5.733 3.111-9.896 8.3-10.404z" />
</svg>
);
)
export const GoogleDocs = ({ className }: { className?: string }) => (
<svg
@ -85,7 +85,7 @@ export const GoogleDocs = ({ className }: { className?: string }) => (
fill="currentColor"
/>
</svg>
);
)
export const GoogleSheets = ({ className }: { className?: string }) => (
<svg
@ -99,7 +99,7 @@ export const GoogleSheets = ({ className }: { className?: string }) => (
fill="currentColor"
/>
</svg>
);
)
export const GoogleSlides = ({ className }: { className?: string }) => (
<svg
@ -113,7 +113,7 @@ export const GoogleSlides = ({ className }: { className?: string }) => (
fill="currentColor"
/>
</svg>
);
)
export const NotionDoc = ({ className }: { className?: string }) => (
<svg
@ -127,7 +127,7 @@ export const NotionDoc = ({ className }: { className?: string }) => (
fill="currentColor"
/>
</svg>
);
)
export const MicrosoftWord = ({ className }: { className?: string }) => (
<svg
@ -141,7 +141,7 @@ export const MicrosoftWord = ({ className }: { className?: string }) => (
fill="currentColor"
/>
</svg>
);
)
export const MicrosoftExcel = ({ className }: { className?: string }) => (
<svg
@ -155,7 +155,7 @@ export const MicrosoftExcel = ({ className }: { className?: string }) => (
fill="currentColor"
/>
</svg>
);
)
export const MicrosoftPowerpoint = ({ className }: { className?: string }) => (
<svg
@ -169,7 +169,7 @@ export const MicrosoftPowerpoint = ({ className }: { className?: string }) => (
fill="currentColor"
/>
</svg>
);
)
export const MicrosoftOneNote = ({ className }: { className?: string }) => (
<svg
@ -183,7 +183,7 @@ export const MicrosoftOneNote = ({ className }: { className?: string }) => (
fill="currentColor"
/>
</svg>
);
)
export const PDF = ({ className }: { className?: string }) => (
<svg
@ -205,7 +205,7 @@ export const PDF = ({ className }: { className?: string }) => (
fill="#DC2626"
/>
</svg>
);
)
export const SyncLogoIcon = ({ className }: { className?: string }) => {
return (
@ -258,8 +258,8 @@ export const SyncLogoIcon = ({ className }: { className?: string }) => {
</clipPath>
</defs>
</svg>
);
};
)
}
export const MCPIcon = ({ className }: { className?: string }) => {
return (
@ -323,8 +323,8 @@ export const MCPIcon = ({ className }: { className?: string }) => {
</linearGradient>
</defs>
</svg>
);
};
)
}
export const ClaudeDesktopIcon = ({ className }: { className?: string }) => {
return (
@ -360,5 +360,5 @@ export const ClaudeDesktopIcon = ({ className }: { className?: string }) => {
/>
</defs>
</svg>
);
};
)
}

View file

@ -1,11 +1,11 @@
import { cn } from "@lib/utils";
import { Button } from "@ui/components/button";
import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"
export type ExternalAuthButtonProps = React.ComponentProps<"button"> &
React.ComponentProps<typeof Button> & {
authProvider: string;
authIcon: React.ReactNode;
};
authProvider: string
authIcon: React.ReactNode
}
export function ExternalAuthButton({
authProvider,
@ -34,5 +34,5 @@ export function ExternalAuthButton({
Continue with {authProvider}
</span>
</Button>
);
)
}

View file

@ -1,14 +1,14 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon } from "lucide-react"
import type * as React from "react"
function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}
function AccordionItem({
@ -21,7 +21,7 @@ function AccordionItem({
data-slot="accordion-item"
{...props}
/>
);
)
}
function AccordionTrigger({
@ -43,7 +43,7 @@ function AccordionTrigger({
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
);
)
}
function AccordionContent({
@ -59,7 +59,7 @@ function AccordionContent({
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
);
)
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View file

@ -1,14 +1,14 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { buttonVariants } from "@ui/components/button";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { buttonVariants } from "@ui/components/button"
import type * as React from "react"
function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
}
function AlertDialogTrigger({
@ -16,7 +16,7 @@ function AlertDialogTrigger({
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
);
)
}
function AlertDialogPortal({
@ -24,7 +24,7 @@ function AlertDialogPortal({
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
)
}
function AlertDialogOverlay({
@ -40,7 +40,7 @@ function AlertDialogOverlay({
data-slot="alert-dialog-overlay"
{...props}
/>
);
)
}
function AlertDialogContent({
@ -59,7 +59,7 @@ function AlertDialogContent({
{...props}
/>
</AlertDialogPortal>
);
)
}
function AlertDialogHeader({
@ -72,7 +72,7 @@ function AlertDialogHeader({
data-slot="alert-dialog-header"
{...props}
/>
);
)
}
function AlertDialogFooter({
@ -88,7 +88,7 @@ function AlertDialogFooter({
data-slot="alert-dialog-footer"
{...props}
/>
);
)
}
function AlertDialogTitle({
@ -101,7 +101,7 @@ function AlertDialogTitle({
data-slot="alert-dialog-title"
{...props}
/>
);
)
}
function AlertDialogDescription({
@ -114,7 +114,7 @@ function AlertDialogDescription({
data-slot="alert-dialog-description"
{...props}
/>
);
)
}
function AlertDialogAction({
@ -126,7 +126,7 @@ function AlertDialogAction({
className={cn(buttonVariants(), className)}
{...props}
/>
);
)
}
function AlertDialogCancel({
@ -138,7 +138,7 @@ function AlertDialogCancel({
className={cn(buttonVariants({ variant: "outline" }), className)}
{...props}
/>
);
)
}
export {
@ -153,4 +153,4 @@ export {
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};
}

View file

@ -1,8 +1,8 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import type * as React from "react"
function Avatar({
className,
@ -17,7 +17,7 @@ function Avatar({
data-slot="avatar"
{...props}
/>
);
)
}
function AvatarImage({
@ -30,7 +30,7 @@ function AvatarImage({
data-slot="avatar-image"
{...props}
/>
);
)
}
function AvatarFallback({
@ -46,7 +46,7 @@ function AvatarFallback({
data-slot="avatar-fallback"
{...props}
/>
);
)
}
export { Avatar, AvatarImage, AvatarFallback };
export { Avatar, AvatarImage, AvatarFallback }

View file

@ -1,7 +1,7 @@
import { cn } from "@lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@lib/utils"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import type * as React from "react"
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
@ -22,7 +22,7 @@ const badgeVariants = cva(
variant: "default",
},
},
);
)
function Badge({
className,
@ -38,7 +38,7 @@ function Badge({
data-slot="badge"
{...(props as any)}
/>
);
)
}
return (
@ -47,7 +47,7 @@ function Badge({
data-slot="badge"
{...props}
/>
);
)
}
export { Badge, badgeVariants };
export { Badge, badgeVariants }

View file

@ -1,10 +1,10 @@
import { cn } from "@lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import type * as React from "react";
import { cn } from "@lib/utils"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import type * as React from "react"
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
@ -17,7 +17,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
data-slot="breadcrumb-list"
{...props}
/>
);
)
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
@ -27,7 +27,7 @@ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
data-slot="breadcrumb-item"
{...props}
/>
);
)
}
function BreadcrumbLink({
@ -35,7 +35,7 @@ function BreadcrumbLink({
className,
...props
}: React.ComponentProps<"a"> & {
asChild?: boolean;
asChild?: boolean
}) {
if (asChild) {
return (
@ -44,7 +44,7 @@ function BreadcrumbLink({
data-slot="breadcrumb-link"
{...(props as any)}
/>
);
)
}
return (
@ -53,7 +53,7 @@ function BreadcrumbLink({
data-slot="breadcrumb-link"
{...props}
/>
);
)
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
@ -67,7 +67,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
tabIndex={0}
{...props}
/>
);
)
}
function BreadcrumbSeparator({
@ -85,7 +85,7 @@ function BreadcrumbSeparator({
>
{children ?? <ChevronRight />}
</li>
);
)
}
function BreadcrumbEllipsis({
@ -103,7 +103,7 @@ function BreadcrumbEllipsis({
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
</span>
);
)
}
export {
@ -114,4 +114,4 @@ export {
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
}

View file

@ -1,7 +1,7 @@
import { cn } from "@lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@lib/utils"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import type * as React from "react"
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-2 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@ -42,7 +42,7 @@ const buttonVariants = cva(
size: "default",
},
},
);
)
function Button({
className,
@ -52,7 +52,7 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
asChild?: boolean
}) {
if (asChild) {
return (
@ -61,7 +61,7 @@ function Button({
data-slot="button"
{...(props as any)}
/>
);
)
}
return (
@ -70,7 +70,7 @@ function Button({
data-slot="button"
{...props}
/>
);
)
}
export { Button, buttonVariants };
export { Button, buttonVariants }

View file

@ -1,5 +1,5 @@
import { cn } from "@lib/utils";
import type * as React from "react";
import { cn } from "@lib/utils"
import type * as React from "react"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
@ -11,7 +11,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card"
{...props}
/>
);
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
@ -24,7 +24,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-header"
{...props}
/>
);
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
@ -34,7 +34,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-title"
{...props}
/>
);
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
@ -44,7 +44,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-description"
{...props}
/>
);
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
@ -57,7 +57,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-action"
{...props}
/>
);
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
@ -67,7 +67,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-content"
{...props}
/>
);
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
@ -77,7 +77,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-footer"
{...props}
/>
);
)
}
export {
@ -88,4 +88,4 @@ export {
CardAction,
CardDescription,
CardContent,
};
}

View file

@ -1,44 +1,44 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import { Button } from "@ui/components/button";
import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import * as React from "react";
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import * as React from "react"
type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void;
};
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
} & CarouselProps;
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext);
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />");
throw new Error("useCarousel must be used within a <Carousel />")
}
return context;
return context
}
function Carousel({
@ -56,52 +56,52 @@ function Carousel({
axis: orientation === "horizontal" ? "x" : "y",
},
plugins,
);
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false);
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return;
setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev();
}, [api]);
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext();
}, [api]);
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
scrollPrev();
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault();
scrollNext();
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext],
);
)
React.useEffect(() => {
if (!api || !setApi) return;
setApi(api);
}, [api, setApi]);
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) return;
onSelect(api);
api.on("reInit", onSelect);
api.on("select", onSelect);
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect);
};
}, [api, onSelect]);
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
@ -126,11 +126,11 @@ function Carousel({
{children}
</section>
</CarouselContext.Provider>
);
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
const { carouselRef, orientation } = useCarousel();
const { carouselRef, orientation } = useCarousel()
return (
<div
@ -147,11 +147,11 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
{...props}
/>
</div>
);
)
}
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
const { orientation } = useCarousel();
const { orientation } = useCarousel()
return (
<div
@ -165,7 +165,7 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
role="group"
{...props}
/>
);
)
}
function CarouselPrevious({
@ -174,7 +174,7 @@ function CarouselPrevious({
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
@ -195,7 +195,7 @@ function CarouselPrevious({
<ArrowLeft />
<span className="sr-only">Previous slide</span>
</Button>
);
)
}
function CarouselNext({
@ -204,7 +204,7 @@ function CarouselNext({
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel();
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
@ -225,7 +225,7 @@ function CarouselNext({
<ArrowRight />
<span className="sr-only">Next slide</span>
</Button>
);
)
}
export {
@ -235,4 +235,4 @@ export {
CarouselItem,
CarouselPrevious,
CarouselNext,
};
}

View file

@ -1,36 +1,36 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as React from "react";
import * as RechartsPrimitive from "recharts";
import { cn } from "@lib/utils"
import * as React from "react"
import * as RechartsPrimitive from "recharts"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const;
const THEMES = { light: "", dark: ".dark" } as const
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode;
icon?: React.ComponentType;
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
);
};
)
}
type ChartContextProps = {
config: ChartConfig;
};
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null);
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext);
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />");
throw new Error("useChart must be used within a <ChartContainer />")
}
return context;
return context
}
function ChartContainer({
@ -40,13 +40,13 @@ function ChartContainer({
config,
...props
}: React.ComponentProps<"div"> & {
config: ChartConfig;
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"];
>["children"]
}) {
const uniqueId = React.useId();
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
@ -65,16 +65,16 @@ function ChartContainer({
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
);
)
}
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color,
);
)
if (!colorConfig.length) {
return null;
return null
}
return (
@ -89,8 +89,8 @@ ${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color;
return color ? ` --color-${key}: ${color};` : null;
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
@ -99,10 +99,10 @@ ${colorConfig
.join("\n"),
}}
/>
);
};
)
}
const ChartTooltip = RechartsPrimitive.Tooltip;
const ChartTooltip = RechartsPrimitive.Tooltip
function ChartTooltipContent({
active,
@ -120,40 +120,40 @@ function ChartTooltipContent({
labelKey,
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean;
hideIndicator?: boolean;
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}) {
const { config } = useChart();
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null;
return null
}
const [item] = payload;
const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const [item] = payload
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label;
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
);
)
}
if (!value) {
return null;
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
return <div className={cn("font-medium", labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
@ -162,13 +162,13 @@ function ChartTooltipContent({
labelClassName,
config,
labelKey,
]);
])
if (!active || !payload?.length) {
return null;
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot";
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
@ -180,9 +180,9 @@ function ChartTooltipContent({
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const indicatorColor = color || item.payload.fill || item.color;
const key = `${nameKey || item.name || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
@ -241,14 +241,14 @@ function ChartTooltipContent({
</>
)}
</div>
);
)
})}
</div>
</div>
);
)
}
const ChartLegend = RechartsPrimitive.Legend;
const ChartLegend = RechartsPrimitive.Legend
function ChartLegendContent({
className,
@ -258,13 +258,13 @@ function ChartLegendContent({
nameKey,
}: React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean;
nameKey?: string;
hideIcon?: boolean
nameKey?: string
}) {
const { config } = useChart();
const { config } = useChart()
if (!payload?.length) {
return null;
return null
}
return (
@ -276,8 +276,8 @@ function ChartLegendContent({
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
@ -298,10 +298,10 @@ function ChartLegendContent({
)}
{itemConfig?.label}
</div>
);
)
})}
</div>
);
)
}
// Helper to extract item config from a payload.
@ -311,7 +311,7 @@ function getPayloadConfigFromPayload(
key: string,
) {
if (typeof payload !== "object" || payload === null) {
return undefined;
return undefined
}
const payloadPayload =
@ -319,15 +319,15 @@ function getPayloadConfigFromPayload(
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined;
: undefined
let configLabelKey: string = key;
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string;
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
@ -335,12 +335,12 @@ function getPayloadConfigFromPayload(
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string;
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config];
: config[key as keyof typeof config]
}
export {
@ -350,4 +350,4 @@ export {
ChartLegend,
ChartLegendContent,
ChartStyle,
};
}

View file

@ -1,9 +1,9 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "lucide-react"
import type * as React from "react"
function Checkbox({
className,
@ -25,7 +25,7 @@ function Checkbox({
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
);
)
}
export { Checkbox };
export { Checkbox }

View file

@ -1,11 +1,11 @@
"use client";
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
function Collapsible({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({
@ -16,7 +16,7 @@ function CollapsibleTrigger({
data-slot="collapsible-trigger"
{...props}
/>
);
)
}
function CollapsibleContent({
@ -27,7 +27,7 @@ function CollapsibleContent({
data-slot="collapsible-content"
{...props}
/>
);
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View file

@ -1,7 +1,7 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import { Button } from "@ui/components/button";
import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"
import {
Command,
CommandEmpty,
@ -9,29 +9,25 @@ import {
CommandInput,
CommandItem,
CommandList,
} from "@ui/components/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@ui/components/popover";
import { Check, ChevronsUpDown, X } from "lucide-react";
import * as React from "react";
} from "@ui/components/command"
import { Popover, PopoverContent, PopoverTrigger } from "@ui/components/popover"
import { Check, ChevronsUpDown, X } from "lucide-react"
import * as React from "react"
interface Option {
value: string;
label: string;
value: string
label: string
}
interface ComboboxProps {
options: Option[];
onSelect: (value: string) => void;
onSubmit: (newName: string) => void;
selectedValues: string[];
setSelectedValues: React.Dispatch<React.SetStateAction<string[]>>;
className?: string;
placeholder?: string;
triggerClassName?: string;
options: Option[]
onSelect: (value: string) => void
onSubmit: (newName: string) => void
selectedValues: string[]
setSelectedValues: React.Dispatch<React.SetStateAction<string[]>>
className?: string
placeholder?: string
triggerClassName?: string
}
export function Combobox({
@ -44,38 +40,36 @@ export function Combobox({
placeholder = "Select...",
triggerClassName,
}: ComboboxProps) {
const [open, setOpen] = React.useState(false);
const [inputValue, setInputValue] = React.useState("");
const [open, setOpen] = React.useState(false)
const [inputValue, setInputValue] = React.useState("")
const handleSelect = (value: string) => {
onSelect(value);
setOpen(false);
setInputValue("");
};
onSelect(value)
setOpen(false)
setInputValue("")
}
const handleCreate = () => {
if (inputValue.trim()) {
onSubmit(inputValue);
setOpen(false);
setInputValue("");
onSubmit(inputValue)
setOpen(false)
setInputValue("")
}
};
}
const handleRemove = (valueToRemove: string) => {
setSelectedValues((prev) =>
prev.filter((value) => value !== valueToRemove),
);
};
setSelectedValues((prev) => prev.filter((value) => value !== valueToRemove))
}
const filteredOptions = options.filter(
(option) => !selectedValues.includes(option.value),
);
)
const isNewValue =
inputValue.trim() &&
!options.some(
(option) => option.label.toLowerCase() === inputValue.toLowerCase(),
);
)
return (
<Popover onOpenChange={setOpen} open={open}>
@ -93,7 +87,7 @@ export function Combobox({
<div className="flex flex-wrap gap-1 items-center w-full">
{selectedValues.length > 0 ? (
selectedValues.map((value) => {
const option = options.find((opt) => opt.value === value);
const option = options.find((opt) => opt.value === value)
return (
<span
className="inline-flex items-center gap-1 px-2 py-0.5 bg-secondary text-sm rounded-md"
@ -103,15 +97,15 @@ export function Combobox({
<button
className="hover:text-destructive"
onClick={(e) => {
e.stopPropagation();
handleRemove(value);
e.stopPropagation()
handleRemove(value)
}}
type="button"
>
<X className="h-3 w-3" />
</button>
</span>
);
)
})
) : (
<span className="text-muted-foreground">{placeholder}</span>
@ -163,5 +157,5 @@ export function Combobox({
</Command>
</PopoverContent>
</Popover>
);
)
}

View file

@ -1,16 +1,16 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import { cn } from "@lib/utils"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@ui/components/dialog";
import { Command as CommandPrimitive } from "cmdk";
import { SearchIcon } from "lucide-react";
import type * as React from "react";
} from "@ui/components/dialog"
import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react"
import type * as React from "react"
function Command({
className,
@ -25,7 +25,7 @@ function Command({
data-slot="command"
{...props}
/>
);
)
}
function CommandDialog({
@ -36,10 +36,10 @@ function CommandDialog({
showCloseButton = true,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string;
description?: string;
className?: string;
showCloseButton?: boolean;
title?: string
description?: string
className?: string
showCloseButton?: boolean
}) {
return (
<Dialog {...props}>
@ -56,7 +56,7 @@ function CommandDialog({
</Command>
</DialogContent>
</Dialog>
);
)
}
function CommandInput({
@ -78,7 +78,7 @@ function CommandInput({
{...props}
/>
</div>
);
)
}
function CommandList({
@ -94,7 +94,7 @@ function CommandList({
data-slot="command-list"
{...props}
/>
);
)
}
function CommandEmpty({
@ -106,7 +106,7 @@ function CommandEmpty({
data-slot="command-empty"
{...props}
/>
);
)
}
function CommandGroup({
@ -122,7 +122,7 @@ function CommandGroup({
data-slot="command-group"
{...props}
/>
);
)
}
function CommandSeparator({
@ -135,7 +135,7 @@ function CommandSeparator({
data-slot="command-separator"
{...props}
/>
);
)
}
function CommandItem({
@ -151,7 +151,7 @@ function CommandItem({
data-slot="command-item"
{...props}
/>
);
)
}
function CommandShortcut({
@ -167,7 +167,7 @@ function CommandShortcut({
data-slot="command-shortcut"
{...props}
/>
);
)
}
export {
@ -180,4 +180,4 @@ export {
CommandItem,
CommandShortcut,
CommandSeparator,
};
}

View file

@ -1,32 +1,32 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import type * as React from "react"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
@ -42,7 +42,7 @@ function DialogOverlay({
data-slot="dialog-overlay"
{...props}
/>
);
)
}
function DialogContent({
@ -51,7 +51,7 @@ function DialogContent({
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
@ -76,7 +76,7 @@ function DialogContent({
)}
</DialogPrimitive.Content>
</DialogPortal>
);
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
@ -86,7 +86,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="dialog-header"
{...props}
/>
);
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
@ -99,7 +99,7 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="dialog-footer"
{...props}
/>
);
)
}
function DialogTitle({
@ -112,7 +112,7 @@ function DialogTitle({
data-slot="dialog-title"
{...props}
/>
);
)
}
function DialogDescription({
@ -125,7 +125,7 @@ function DialogDescription({
data-slot="dialog-description"
{...props}
/>
);
)
}
export {
@ -139,4 +139,4 @@ export {
DialogPortal,
DialogTitle,
DialogTrigger,
};
}

View file

@ -1,31 +1,31 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import type * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@lib/utils"
import type * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
function Drawer({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
}
function DrawerTrigger({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
}
function DrawerPortal({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
}
function DrawerClose({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
}
function DrawerOverlay({
@ -41,7 +41,7 @@ function DrawerOverlay({
data-slot="drawer-overlay"
{...props}
/>
);
)
}
function DrawerContent({
@ -56,7 +56,7 @@ function DrawerContent({
className={cn(
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-xl data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
className,
@ -68,7 +68,7 @@ function DrawerContent({
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
);
)
}
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
@ -81,7 +81,7 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="drawer-header"
{...props}
/>
);
)
}
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
@ -91,7 +91,7 @@ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="drawer-footer"
{...props}
/>
);
)
}
function DrawerTitle({
@ -104,7 +104,7 @@ function DrawerTitle({
data-slot="drawer-title"
{...props}
/>
);
)
}
function DrawerDescription({
@ -117,7 +117,7 @@ function DrawerDescription({
data-slot="drawer-description"
{...props}
/>
);
)
}
export {
@ -131,4 +131,4 @@ export {
DrawerFooter,
DrawerTitle,
DrawerDescription,
};
}

View file

@ -1,14 +1,14 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import type * as React from "react"
function DropdownMenu({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}
function DropdownMenuPortal({
@ -16,7 +16,7 @@ function DropdownMenuPortal({
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
);
)
}
function DropdownMenuTrigger({
@ -27,7 +27,7 @@ function DropdownMenuTrigger({
data-slot="dropdown-menu-trigger"
{...props}
/>
);
)
}
function DropdownMenuContent({
@ -47,7 +47,7 @@ function DropdownMenuContent({
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
)
}
function DropdownMenuGroup({
@ -55,7 +55,7 @@ function DropdownMenuGroup({
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
);
)
}
function DropdownMenuItem({
@ -64,8 +64,8 @@ function DropdownMenuItem({
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<DropdownMenuPrimitive.Item
@ -78,7 +78,7 @@ function DropdownMenuItem({
data-variant={variant}
{...props}
/>
);
)
}
function DropdownMenuCheckboxItem({
@ -104,7 +104,7 @@ function DropdownMenuCheckboxItem({
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
)
}
function DropdownMenuRadioGroup({
@ -115,7 +115,7 @@ function DropdownMenuRadioGroup({
data-slot="dropdown-menu-radio-group"
{...props}
/>
);
)
}
function DropdownMenuRadioItem({
@ -139,7 +139,7 @@ function DropdownMenuRadioItem({
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
)
}
function DropdownMenuLabel({
@ -147,7 +147,7 @@ function DropdownMenuLabel({
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.Label
@ -159,7 +159,7 @@ function DropdownMenuLabel({
data-slot="dropdown-menu-label"
{...props}
/>
);
)
}
function DropdownMenuSeparator({
@ -172,7 +172,7 @@ function DropdownMenuSeparator({
data-slot="dropdown-menu-separator"
{...props}
/>
);
)
}
function DropdownMenuShortcut({
@ -188,13 +188,13 @@ function DropdownMenuShortcut({
data-slot="dropdown-menu-shortcut"
{...props}
/>
);
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
@ -203,7 +203,7 @@ function DropdownMenuSubTrigger({
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.SubTrigger
@ -218,7 +218,7 @@ function DropdownMenuSubTrigger({
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
);
)
}
function DropdownMenuSubContent({
@ -234,7 +234,7 @@ function DropdownMenuSubContent({
data-slot="dropdown-menu-sub-content"
{...props}
/>
);
)
}
export {
@ -253,4 +253,4 @@ export {
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
};
}

View file

@ -1,11 +1,11 @@
interface PlusPatternBackgroundProps {
plusSize?: number;
plusColor?: string;
backgroundColor?: string;
className?: string;
style?: React.CSSProperties;
fade?: boolean;
[key: string]: any;
plusSize?: number
plusColor?: string
backgroundColor?: string
className?: string
style?: React.CSSProperties
fade?: boolean
[key: string]: any
}
export const BackgroundPlus: React.FC<PlusPatternBackgroundProps> = ({
@ -17,21 +17,21 @@ export const BackgroundPlus: React.FC<PlusPatternBackgroundProps> = ({
style,
...props
}) => {
const encodedPlusColor = encodeURIComponent(plusColor);
const encodedPlusColor = encodeURIComponent(plusColor)
const maskStyle: React.CSSProperties = fade
? {
maskImage: "radial-gradient(circle, white 10%, transparent 90%)",
WebkitMaskImage: "radial-gradient(circle, white 10%, transparent 90%)",
}
: {};
: {}
const backgroundStyle: React.CSSProperties = {
backgroundColor,
backgroundImage: `url("data:image/svg+xml,%3Csvg width='${plusSize}' height='${plusSize}' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='${encodedPlusColor}' fill-opacity='0.2'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
...maskStyle,
...style,
};
}
return (
<div
@ -39,7 +39,7 @@ export const BackgroundPlus: React.FC<PlusPatternBackgroundProps> = ({
style={backgroundStyle}
{...props}
/>
);
};
)
}
export default BackgroundPlus;
export default BackgroundPlus

View file

@ -1,13 +1,13 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import type * as React from "react"
function HoverCard({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
}
function HoverCardTrigger({
@ -15,7 +15,7 @@ function HoverCardTrigger({
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
return (
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
);
)
}
function HoverCardContent({
@ -37,7 +37,7 @@ function HoverCardContent({
{...props}
/>
</HoverCardPrimitive.Portal>
);
)
}
export { HoverCard, HoverCardTrigger, HoverCardContent };
export { HoverCard, HoverCardTrigger, HoverCardContent }

View file

@ -1,5 +1,5 @@
import { cn } from "@lib/utils";
import type * as React from "react";
import { cn } from "@lib/utils"
import type * as React from "react"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
@ -14,7 +14,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
{...props}
/>
);
)
}
export { Input };
export { Input }

View file

@ -1,8 +1,8 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as LabelPrimitive from "@radix-ui/react-label";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as LabelPrimitive from "@radix-ui/react-label"
import type * as React from "react"
function Label({
className,
@ -17,7 +17,7 @@ function Label({
data-slot="label"
{...props}
/>
);
)
}
export { Label };
export { Label }

View file

@ -1,19 +1,19 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import type * as React from "react"
function Popover({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
function PopoverContent({
@ -35,13 +35,13 @@ function PopoverContent({
{...props}
/>
</PopoverPrimitive.Portal>
);
)
}
function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

View file

@ -1,8 +1,8 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import type * as React from "react"
function Progress({
className,
@ -24,7 +24,7 @@ function Progress({
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
);
)
}
export { Progress };
export { Progress }

View file

@ -1,8 +1,8 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import type * as React from "react"
function ScrollArea({
className,
@ -24,7 +24,7 @@ function ScrollArea({
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
);
)
}
function ScrollBar({
@ -51,7 +51,7 @@ function ScrollBar({
data-slot="scroll-area-thumb"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
)
}
export { ScrollArea, ScrollBar };
export { ScrollArea, ScrollBar }

View file

@ -1,26 +1,26 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import type * as React from "react"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />;
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
@ -29,7 +29,7 @@ function SelectTrigger({
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default";
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
@ -46,7 +46,7 @@ function SelectTrigger({
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
)
}
function SelectContent({
@ -81,7 +81,7 @@ function SelectContent({
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
)
}
function SelectLabel({
@ -94,7 +94,7 @@ function SelectLabel({
data-slot="select-label"
{...props}
/>
);
)
}
function SelectItem({
@ -118,7 +118,7 @@ function SelectItem({
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);
)
}
function SelectSeparator({
@ -131,7 +131,7 @@ function SelectSeparator({
data-slot="select-separator"
{...props}
/>
);
)
}
function SelectScrollUpButton({
@ -149,7 +149,7 @@ function SelectScrollUpButton({
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
)
}
function SelectScrollDownButton({
@ -167,7 +167,7 @@ function SelectScrollDownButton({
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
)
}
export {
@ -181,4 +181,4 @@ export {
SelectSeparator,
SelectTrigger,
SelectValue,
};
}

View file

@ -1,8 +1,8 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import type * as React from "react"
function Separator({
className,
@ -21,7 +21,7 @@ function Separator({
orientation={orientation}
{...props}
/>
);
)
}
export { Separator };
export { Separator }

View file

@ -1,48 +1,48 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import { Button } from "@ui/components/button";
import { UploadIcon } from "lucide-react";
import type { ReactNode } from "react";
import { createContext, useContext } from "react";
import type { DropEvent, DropzoneOptions, FileRejection } from "react-dropzone";
import { useDropzone } from "react-dropzone";
import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"
import { UploadIcon } from "lucide-react"
import type { ReactNode } from "react"
import { createContext, useContext } from "react"
import type { DropEvent, DropzoneOptions, FileRejection } from "react-dropzone"
import { useDropzone } from "react-dropzone"
type DropzoneContextType = {
src?: File[];
accept?: DropzoneOptions["accept"];
maxSize?: DropzoneOptions["maxSize"];
minSize?: DropzoneOptions["minSize"];
maxFiles?: DropzoneOptions["maxFiles"];
};
src?: File[]
accept?: DropzoneOptions["accept"]
maxSize?: DropzoneOptions["maxSize"]
minSize?: DropzoneOptions["minSize"]
maxFiles?: DropzoneOptions["maxFiles"]
}
const renderBytes = (bytes: number) => {
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
let size = bytes;
let unitIndex = 0;
const units = ["B", "KB", "MB", "GB", "TB", "PB"]
let size = bytes
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
size /= 1024
unitIndex++
}
return `${size.toFixed(2)}${units[unitIndex]}`;
};
return `${size.toFixed(2)}${units[unitIndex]}`
}
const DropzoneContext = createContext<DropzoneContextType | undefined>(
undefined,
);
)
export type DropzoneProps = Omit<DropzoneOptions, "onDrop"> & {
src?: File[];
className?: string;
src?: File[]
className?: string
onDrop?: (
acceptedFiles: File[],
fileRejections: FileRejection[],
event: DropEvent,
) => void;
children?: ReactNode;
};
) => void
children?: ReactNode
}
export const Dropzone = ({
accept,
@ -66,15 +66,15 @@ export const Dropzone = ({
disabled,
onDrop: (acceptedFiles, fileRejections, event) => {
if (fileRejections.length > 0) {
const message = fileRejections.at(0)?.errors.at(0)?.message;
onError?.(new Error(message));
return;
const message = fileRejections.at(0)?.errors.at(0)?.message
onError?.(new Error(message))
return
}
onDrop?.(acceptedFiles, fileRejections, event);
onDrop?.(acceptedFiles, fileRejections, event)
},
...props,
});
})
return (
<DropzoneContext.Provider
@ -96,38 +96,38 @@ export const Dropzone = ({
{children}
</Button>
</DropzoneContext.Provider>
);
};
)
}
const useDropzoneContext = () => {
const context = useContext(DropzoneContext);
const context = useContext(DropzoneContext)
if (!context) {
throw new Error("useDropzoneContext must be used within a Dropzone");
throw new Error("useDropzoneContext must be used within a Dropzone")
}
return context;
};
return context
}
export type DropzoneContentProps = {
children?: ReactNode;
className?: string;
};
children?: ReactNode
className?: string
}
const maxLabelItems = 1;
const maxLabelItems = 1
export const DropzoneContent = ({
children,
className,
}: DropzoneContentProps) => {
const { src } = useDropzoneContext();
const { src } = useDropzoneContext()
if (!src) {
return null;
return null
}
if (children) {
return children;
return children
}
return (
@ -146,41 +146,41 @@ export const DropzoneContent = ({
Drag and drop or click to replace
</p>
</div>
);
};
)
}
export type DropzoneEmptyStateProps = {
children?: ReactNode;
className?: string;
};
children?: ReactNode
className?: string
}
export const DropzoneEmptyState = ({
children,
className,
}: DropzoneEmptyStateProps) => {
const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext();
const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext()
if (src) {
return null;
return null
}
if (children) {
return children;
return children
}
let caption = "";
let caption = ""
if (accept) {
caption += "Accepts ";
caption += new Intl.ListFormat("en").format(Object.keys(accept));
caption += "Accepts "
caption += new Intl.ListFormat("en").format(Object.keys(accept))
}
if (minSize && maxSize) {
caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}`;
caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}`
} else if (minSize) {
caption += ` at least ${renderBytes(minSize)}`;
caption += ` at least ${renderBytes(minSize)}`
} else if (maxSize) {
caption += ` less than ${renderBytes(maxSize)}`;
caption += ` less than ${renderBytes(maxSize)}`
}
return (
@ -198,5 +198,5 @@ export const DropzoneEmptyState = ({
<p className="text-wrap text-muted-foreground text-xs">{caption}.</p>
)}
</div>
);
};
)
}

View file

@ -1,30 +1,30 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import type * as React from "react"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
function SheetOverlay({
@ -40,7 +40,7 @@ function SheetOverlay({
data-slot="sheet-overlay"
{...props}
/>
);
)
}
function SheetContent({
@ -49,7 +49,7 @@ function SheetContent({
side = "right",
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left";
side?: "top" | "right" | "bottom" | "left"
}) {
return (
<SheetPortal>
@ -77,7 +77,7 @@ function SheetContent({
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
);
)
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
@ -87,7 +87,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sheet-header"
{...props}
/>
);
)
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
@ -97,7 +97,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sheet-footer"
{...props}
/>
);
)
}
function SheetTitle({
@ -110,7 +110,7 @@ function SheetTitle({
data-slot="sheet-title"
{...props}
/>
);
)
}
function SheetDescription({
@ -123,7 +123,7 @@ function SheetDescription({
data-slot="sheet-description"
{...props}
/>
);
)
}
export {
@ -135,4 +135,4 @@ export {
SheetFooter,
SheetTitle,
SheetDescription,
};
}

View file

@ -1,55 +1,55 @@
"use client";
"use client"
import { useIsMobile } from "@hooks/use-mobile";
import { cn } from "@lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { Button } from "@ui/components/button";
import { Input } from "@ui/components/input";
import { Separator } from "@ui/components/separator";
import { useIsMobile } from "@hooks/use-mobile"
import { cn } from "@lib/utils"
import { Slot } from "@radix-ui/react-slot"
import { Button } from "@ui/components/button"
import { Input } from "@ui/components/input"
import { Separator } from "@ui/components/separator"
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@ui/components/sheet";
import { Skeleton } from "@ui/components/skeleton";
} from "@ui/components/sheet"
import { Skeleton } from "@ui/components/skeleton"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@ui/components/tooltip";
import { cva, type VariantProps } from "class-variance-authority";
import { PanelLeftIcon } from "lucide-react";
import * as React from "react";
} from "@ui/components/tooltip"
import { cva, type VariantProps } from "class-variance-authority"
import { PanelLeftIcon } from "lucide-react"
import * as React from "react"
const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
const SIDEBAR_COOKIE_NAME = "sidebar_state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"
const SIDEBAR_WIDTH_ICON = "3rem"
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
type SidebarContextProps = {
state: "expanded" | "collapsed";
open: boolean;
setOpen: (open: boolean) => void;
openMobile: boolean;
setOpenMobile: (open: boolean) => void;
isMobile: boolean;
toggleSidebar: () => void;
};
state: "expanded" | "collapsed"
open: boolean
setOpen: (open: boolean) => void
openMobile: boolean
setOpenMobile: (open: boolean) => void
isMobile: boolean
toggleSidebar: () => void
}
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
const SidebarContext = React.createContext<SidebarContextProps | null>(null)
function useSidebar() {
const context = React.useContext(SidebarContext);
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.");
throw new Error("useSidebar must be used within a SidebarProvider.")
}
return context;
return context
}
function SidebarProvider({
@ -61,36 +61,36 @@ function SidebarProvider({
children,
...props
}: React.ComponentProps<"div"> & {
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}) {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen);
const open = openProp ?? _open;
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value;
const openState = typeof value === "function" ? value(open) : value
if (setOpenProp) {
setOpenProp(openState);
setOpenProp(openState)
} else {
_setOpen(openState);
_setOpen(openState)
}
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open],
);
)
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen]);
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
}, [isMobile, setOpen])
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
@ -99,18 +99,18 @@ function SidebarProvider({
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault();
toggleSidebar();
event.preventDefault()
toggleSidebar()
}
};
}
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [toggleSidebar]);
window.addEventListener("keydown", handleKeyDown)
return () => window.removeEventListener("keydown", handleKeyDown)
}, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed";
const state = open ? "expanded" : "collapsed"
const contextValue = React.useMemo<SidebarContextProps>(
() => ({
@ -123,7 +123,7 @@ function SidebarProvider({
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
);
)
return (
<SidebarContext.Provider value={contextValue}>
@ -147,7 +147,7 @@ function SidebarProvider({
</div>
</TooltipProvider>
</SidebarContext.Provider>
);
)
}
function Sidebar({
@ -158,11 +158,11 @@ function Sidebar({
children,
...props
}: React.ComponentProps<"div"> & {
side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none";
side?: "left" | "right"
variant?: "sidebar" | "floating" | "inset"
collapsible?: "offcanvas" | "icon" | "none"
}) {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
if (collapsible === "none") {
return (
@ -176,7 +176,7 @@ function Sidebar({
>
{children}
</div>
);
)
}
if (isMobile) {
@ -201,7 +201,7 @@ function Sidebar({
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
);
)
}
return (
@ -249,7 +249,7 @@ function Sidebar({
</div>
</div>
</div>
);
)
}
function SidebarTrigger({
@ -257,7 +257,7 @@ function SidebarTrigger({
onClick,
...props
}: React.ComponentProps<typeof Button>) {
const { toggleSidebar } = useSidebar();
const { toggleSidebar } = useSidebar()
return (
<Button
@ -265,8 +265,8 @@ function SidebarTrigger({
data-sidebar="trigger"
data-slot="sidebar-trigger"
onClick={(event) => {
onClick?.(event);
toggleSidebar();
onClick?.(event)
toggleSidebar()
}}
size="icon"
variant="ghost"
@ -275,11 +275,11 @@ function SidebarTrigger({
<PanelLeftIcon />
<span className="sr-only">Toggle Sidebar</span>
</Button>
);
)
}
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
const { toggleSidebar } = useSidebar();
const { toggleSidebar } = useSidebar()
return (
<button
@ -300,7 +300,7 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
title="Toggle Sidebar"
{...props}
/>
);
)
}
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
@ -314,7 +314,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
data-slot="sidebar-inset"
{...props}
/>
);
)
}
function SidebarInput({
@ -328,7 +328,7 @@ function SidebarInput({
data-slot="sidebar-input"
{...props}
/>
);
)
}
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
@ -339,7 +339,7 @@ function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sidebar-header"
{...props}
/>
);
)
}
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
@ -350,7 +350,7 @@ function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sidebar-footer"
{...props}
/>
);
)
}
function SidebarSeparator({
@ -364,7 +364,7 @@ function SidebarSeparator({
data-slot="sidebar-separator"
{...props}
/>
);
)
}
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
@ -378,7 +378,7 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sidebar-content"
{...props}
/>
);
)
}
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
@ -389,7 +389,7 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sidebar-group"
{...props}
/>
);
)
}
function SidebarGroupLabel({
@ -401,7 +401,7 @@ function SidebarGroupLabel({
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
);
)
if (asChild) {
return (
@ -411,7 +411,7 @@ function SidebarGroupLabel({
data-slot="sidebar-group-label"
{...(props as any)}
/>
);
)
}
return (
@ -421,7 +421,7 @@ function SidebarGroupLabel({
data-slot="sidebar-group-label"
{...props}
/>
);
)
}
function SidebarGroupAction({
@ -434,7 +434,7 @@ function SidebarGroupAction({
"after:absolute after:-inset-2 md:after:hidden",
"group-data-[collapsible=icon]:hidden",
className,
);
)
if (asChild) {
return (
@ -444,7 +444,7 @@ function SidebarGroupAction({
data-slot="sidebar-group-action"
{...(props as any)}
/>
);
)
}
return (
@ -454,7 +454,7 @@ function SidebarGroupAction({
data-slot="sidebar-group-action"
{...props}
/>
);
)
}
function SidebarGroupContent({
@ -468,7 +468,7 @@ function SidebarGroupContent({
data-slot="sidebar-group-content"
{...props}
/>
);
)
}
function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
@ -479,7 +479,7 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
data-slot="sidebar-menu"
{...props}
/>
);
)
}
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
@ -490,7 +490,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
data-slot="sidebar-menu-item"
{...props}
/>
);
)
}
const sidebarMenuButtonVariants = cva(
@ -513,7 +513,7 @@ const sidebarMenuButtonVariants = cva(
size: "default",
},
},
);
)
function SidebarMenuButton({
asChild = false,
@ -524,11 +524,11 @@ function SidebarMenuButton({
className,
...props
}: React.ComponentProps<"button"> & {
asChild?: boolean;
isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
asChild?: boolean
isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent>
} & VariantProps<typeof sidebarMenuButtonVariants>) {
const { isMobile, state } = useSidebar();
const { isMobile, state } = useSidebar()
const buttonProps = {
className: cn(sidebarMenuButtonVariants({ variant, size }), className),
@ -537,22 +537,22 @@ function SidebarMenuButton({
"data-size": size,
"data-slot": "sidebar-menu-button",
...props,
};
}
const button = asChild ? (
<Slot {...(buttonProps as any)} />
) : (
<button {...buttonProps} />
);
)
if (!tooltip) {
return button;
return button
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
};
}
}
return (
@ -565,7 +565,7 @@ function SidebarMenuButton({
{...tooltip}
/>
</Tooltip>
);
)
}
function SidebarMenuAction({
@ -574,8 +574,8 @@ function SidebarMenuAction({
showOnHover = false,
...props
}: React.ComponentProps<"button"> & {
asChild?: boolean;
showOnHover?: boolean;
asChild?: boolean
showOnHover?: boolean
}) {
const classes = cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
@ -587,7 +587,7 @@ function SidebarMenuAction({
showOnHover &&
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
className,
);
)
if (asChild) {
return (
@ -597,7 +597,7 @@ function SidebarMenuAction({
data-slot="sidebar-menu-action"
{...(props as any)}
/>
);
)
}
return (
@ -607,7 +607,7 @@ function SidebarMenuAction({
data-slot="sidebar-menu-action"
{...props}
/>
);
)
}
function SidebarMenuBadge({
@ -629,7 +629,7 @@ function SidebarMenuBadge({
data-slot="sidebar-menu-badge"
{...props}
/>
);
)
}
function SidebarMenuSkeleton({
@ -637,12 +637,12 @@ function SidebarMenuSkeleton({
showIcon = false,
...props
}: React.ComponentProps<"div"> & {
showIcon?: boolean;
showIcon?: boolean
}) {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`;
}, []);
return `${Math.floor(Math.random() * 40) + 50}%`
}, [])
return (
<div
@ -667,7 +667,7 @@ function SidebarMenuSkeleton({
}
/>
</div>
);
)
}
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
@ -682,7 +682,7 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
data-slot="sidebar-menu-sub"
{...props}
/>
);
)
}
function SidebarMenuSubItem({
@ -696,7 +696,7 @@ function SidebarMenuSubItem({
data-slot="sidebar-menu-sub-item"
{...props}
/>
);
)
}
function SidebarMenuSubButton({
@ -706,9 +706,9 @@ function SidebarMenuSubButton({
className,
...props
}: React.ComponentProps<"a"> & {
asChild?: boolean;
size?: "sm" | "md";
isActive?: boolean;
asChild?: boolean
size?: "sm" | "md"
isActive?: boolean
}) {
const classes = cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
@ -717,7 +717,7 @@ function SidebarMenuSubButton({
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className,
);
)
if (asChild) {
return (
@ -729,7 +729,7 @@ function SidebarMenuSubButton({
data-slot="sidebar-menu-sub-button"
{...(props as any)}
/>
);
)
}
return (
@ -741,7 +741,7 @@ function SidebarMenuSubButton({
data-slot="sidebar-menu-sub-button"
{...props}
/>
);
)
}
export {
@ -769,4 +769,4 @@ export {
SidebarSeparator,
SidebarTrigger,
useSidebar,
};
}

View file

@ -1,4 +1,4 @@
import { cn } from "@lib/utils";
import { cn } from "@lib/utils"
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
@ -7,7 +7,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
data-slot="skeleton"
{...props}
/>
);
)
}
export { Skeleton };
export { Skeleton }

View file

@ -1,10 +1,10 @@
"use client";
"use client"
import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
const { theme = "system" } = useTheme()
return (
<Sonner
@ -26,7 +26,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}}
{...props}
/>
);
};
)
}
export { Toaster };
export { Toaster }

View file

@ -1,7 +1,7 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import type * as React from "react";
import { cn } from "@lib/utils"
import type * as React from "react"
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
@ -15,7 +15,7 @@ function Table({ className, ...props }: React.ComponentProps<"table">) {
{...props}
/>
</div>
);
)
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
@ -25,7 +25,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
data-slot="table-header"
{...props}
/>
);
)
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
@ -35,7 +35,7 @@ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
data-slot="table-body"
{...props}
/>
);
)
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
@ -48,7 +48,7 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
data-slot="table-footer"
{...props}
/>
);
)
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
@ -61,7 +61,7 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
data-slot="table-row"
{...props}
/>
);
)
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
@ -74,7 +74,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
data-slot="table-head"
{...props}
/>
);
)
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
@ -87,7 +87,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
data-slot="table-cell"
{...props}
/>
);
)
}
function TableCaption({
@ -100,7 +100,7 @@ function TableCaption({
data-slot="table-caption"
{...props}
/>
);
)
}
export {
@ -112,4 +112,4 @@ export {
TableRow,
TableCell,
TableCaption,
};
}

View file

@ -1,8 +1,8 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import type * as React from "react"
function Tabs({
className,
@ -14,7 +14,7 @@ function Tabs({
data-slot="tabs"
{...props}
/>
);
)
}
function TabsList({
@ -30,7 +30,7 @@ function TabsList({
data-slot="tabs-list"
{...props}
/>
);
)
}
function TabsTrigger({
@ -46,7 +46,7 @@ function TabsTrigger({
data-slot="tabs-trigger"
{...props}
/>
);
)
}
function TabsContent({
@ -59,7 +59,7 @@ function TabsContent({
data-slot="tabs-content"
{...props}
/>
);
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent };
export { Tabs, TabsList, TabsTrigger, TabsContent }

View file

@ -1,7 +1,7 @@
import { cn } from "@lib/utils";
import { cn } from "@lib/utils"
interface TextSeparatorProps extends React.ComponentProps<"div"> {
text: string;
text: string
}
export function TextSeparator({
@ -18,5 +18,5 @@ export function TextSeparator({
{text}
</span>
</div>
);
)
}

View file

@ -1,5 +1,5 @@
import { cn } from "@lib/utils";
import type * as React from "react";
import { cn } from "@lib/utils"
import type * as React from "react"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
@ -11,7 +11,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
data-slot="textarea"
{...props}
/>
);
)
}
export { Textarea };
export { Textarea }

View file

@ -1,17 +1,17 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
import { toggleVariants } from "@ui/components/toggle";
import type { VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@lib/utils"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { toggleVariants } from "@ui/components/toggle"
import type { VariantProps } from "class-variance-authority"
import * as React from "react"
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
});
})
function ToggleGroup({
className,
@ -36,7 +36,7 @@ function ToggleGroup({
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
);
)
}
function ToggleGroupItem({
@ -47,7 +47,7 @@ function ToggleGroupItem({
...props
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>) {
const context = React.useContext(ToggleGroupContext);
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
@ -66,7 +66,7 @@ function ToggleGroupItem({
>
{children}
</ToggleGroupPrimitive.Item>
);
)
}
export { ToggleGroup, ToggleGroupItem };
export { ToggleGroup, ToggleGroupItem }

View file

@ -1,9 +1,9 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as TogglePrimitive from "@radix-ui/react-toggle";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority"
import type * as React from "react"
const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-2 outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
@ -25,7 +25,7 @@ const toggleVariants = cva(
size: "default",
},
},
);
)
function Toggle({
className,
@ -40,7 +40,7 @@ function Toggle({
data-slot="toggle"
{...props}
/>
);
)
}
export { Toggle, toggleVariants };
export { Toggle, toggleVariants }

View file

@ -1,8 +1,8 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import type * as React from "react";
import { cn } from "@lib/utils"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import type * as React from "react"
function TooltipProvider({
delayDuration = 0,
@ -14,7 +14,7 @@ function TooltipProvider({
delayDuration={delayDuration}
{...props}
/>
);
)
}
function Tooltip({
@ -24,13 +24,13 @@ function Tooltip({
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
);
)
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}
function TooltipContent({
@ -54,7 +54,7 @@ function TooltipContent({
<TooltipPrimitive.Arrow className="bg-secondary fill-secondary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-sm" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
);
)
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

View file

@ -1,17 +1,17 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import { Button, type buttonVariants } from "@ui/components/button";
import type { VariantProps } from "class-variance-authority";
import { CheckIcon, ClipboardIcon } from "lucide-react";
import * as React from "react";
import { useEffect } from "react";
import { cn } from "@lib/utils"
import { Button, type buttonVariants } from "@ui/components/button"
import type { VariantProps } from "class-variance-authority"
import { CheckIcon, ClipboardIcon } from "lucide-react"
import * as React from "react"
import { useEffect } from "react"
interface CopyButtonProps
extends React.ComponentProps<"button">,
VariantProps<typeof buttonVariants> {
value: string;
src?: string;
value: string
src?: string
}
export function CopyButton({
@ -21,13 +21,13 @@ export function CopyButton({
variant = "ghost",
...props
}: CopyButtonProps) {
const [hasCopied, setHasCopied] = React.useState(false);
const [hasCopied, setHasCopied] = React.useState(false)
useEffect(() => {
setTimeout(() => {
setHasCopied(false);
}, 2000);
}, []);
setHasCopied(false)
}, 2000)
}, [])
return (
<Button
@ -36,8 +36,8 @@ export function CopyButton({
className,
)}
onClick={() => {
navigator.clipboard.writeText(value);
setHasCopied(true);
navigator.clipboard.writeText(value)
setHasCopied(true)
}}
size="icon"
variant={variant}
@ -46,5 +46,5 @@ export function CopyButton({
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
);
)
}

View file

@ -1,13 +1,13 @@
"use client";
"use client"
import { cn } from "@lib/utils";
import { Label1Regular } from "@ui/text/label/label-1-regular";
import { AnimatePresence, motion } from "motion/react";
import * as React from "react";
import { cn } from "@lib/utils"
import { Label1Regular } from "@ui/text/label/label-1-regular"
import { AnimatePresence, motion } from "motion/react"
import * as React from "react"
interface CopyableCellProps extends React.HTMLAttributes<HTMLDivElement> {
value: string;
displayValue?: React.ReactNode;
value: string
displayValue?: React.ReactNode
}
export function CopyableCell({
@ -17,26 +17,26 @@ export function CopyableCell({
children,
...props
}: CopyableCellProps) {
const [hasCopied, setHasCopied] = React.useState(false);
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
if (hasCopied) {
const timeout = setTimeout(() => {
setHasCopied(false);
}, 2000);
return () => clearTimeout(timeout);
setHasCopied(false)
}, 2000)
return () => clearTimeout(timeout)
}
}, [hasCopied]);
}, [hasCopied])
const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation();
e.stopPropagation()
try {
await navigator.clipboard.writeText(value);
setHasCopied(true);
await navigator.clipboard.writeText(value)
setHasCopied(true)
} catch (err) {
console.error("Failed to copy:", err);
console.error("Failed to copy:", err)
}
};
}
return (
// biome-ignore lint/a11y/noStaticElementInteractions: shadcn
@ -80,5 +80,5 @@ export function CopyableCell({
)}
</AnimatePresence>
</div>
);
)
}

View file

@ -1,13 +1,13 @@
import { cn } from "@lib/utils";
import { Input } from "@ui/components/input";
import { Label1Regular } from "@ui/text/label/label-1-regular";
import { cn } from "@lib/utils"
import { Input } from "@ui/components/input"
import { Label1Regular } from "@ui/text/label/label-1-regular"
interface LabeledInputProps extends React.ComponentProps<"div"> {
label?: string;
inputType: string;
inputPlaceholder: string;
error?: string | null;
inputProps?: React.ComponentProps<typeof Input>;
label?: string
inputType: string
inputPlaceholder: string
error?: string | null
inputProps?: React.ComponentProps<typeof Input>
}
export function LabeledInput({
@ -47,5 +47,5 @@ export function LabeledInput({
</p>
)}
</div>
);
)
}

View file

@ -1,53 +1,53 @@
"use client";
"use client"
import { authClient } from "@lib/auth";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { authClient } from "@lib/auth"
import { useRouter } from "next/navigation"
import { useEffect } from "react"
export const AnonymousAuth = ({
dashboardPath = "/dashboard",
loginPath = "/login",
}) => {
const router = useRouter();
const router = useRouter()
useEffect(() => {
const createAnonymousSession = async () => {
const session = await authClient.getSession();
const session = await authClient.getSession()
if (!session?.session) {
console.debug(
"[ANONYMOUS_AUTH] No session found, creating anonymous session...",
);
)
try {
// Create anonymous session
console.debug("[ANONYMOUS_AUTH] Calling signIn.anonymous()...");
const res = await authClient.signIn.anonymous();
console.debug("[ANONYMOUS_AUTH] Calling signIn.anonymous()...")
const res = await authClient.signIn.anonymous()
if (!res.token) {
throw new Error("Failed to get anonymous token");
throw new Error("Failed to get anonymous token")
}
// Get the new session
console.debug(
"[ANONYMOUS_AUTH] Getting new session with anonymous token...",
);
const newSession = await authClient.getSession();
)
const newSession = await authClient.getSession()
console.debug("[ANONYMOUS_AUTH] New session retrieved:", newSession);
console.debug("[ANONYMOUS_AUTH] New session retrieved:", newSession)
if (!newSession?.session || !newSession?.user) {
console.error(
"[ANONYMOUS_AUTH] Failed to create anonymous session - missing session or user",
);
throw new Error("Failed to create anonymous session");
)
throw new Error("Failed to create anonymous session")
}
// Get the user's organization
console.debug(
"[ANONYMOUS_AUTH] Fetching organizations for anonymous user...",
);
const orgs = await authClient.organization.list();
)
const orgs = await authClient.organization.list()
console.debug("[ANONYMOUS_AUTH] Organizations retrieved:", {
count: orgs?.length || 0,
@ -56,43 +56,43 @@ export const AnonymousAuth = ({
name: o.name,
slug: o.slug,
})),
});
})
const org = orgs?.[0];
const org = orgs?.[0]
if (!org) {
console.error(
"[ANONYMOUS_AUTH] No organization found for anonymous user",
);
throw new Error("Failed to get organization for anonymous user");
)
throw new Error("Failed to get organization for anonymous user")
}
// Redirect to the organization dashboard
console.debug(
`[ANONYMOUS_AUTH] Redirecting anonymous user to /${org.slug}${dashboardPath}`,
);
router.push(dashboardPath);
)
router.push(dashboardPath)
} catch (error) {
console.error(
"[ANONYMOUS_AUTH] Anonymous session creation error:",
error,
);
)
console.error("[ANONYMOUS_AUTH] Error details:", {
message: error instanceof Error ? error.message : "Unknown error",
stack: error instanceof Error ? error.stack : undefined,
});
router.push(loginPath);
})
router.push(loginPath)
}
} else if (session.session) {
// Session exists, handle organization routing
console.debug(
"[ANONYMOUS_AUTH] Session exists, checking organization...",
);
)
if (!session.session.activeOrganizationId) {
console.debug(
"[ANONYMOUS_AUTH] No active organization ID, fetching organizations...",
);
const orgs = await authClient.organization.list();
)
const orgs = await authClient.organization.list()
console.debug("[ANONYMOUS_AUTH] Organizations for existing user:", {
count: orgs?.length || 0,
@ -101,50 +101,50 @@ export const AnonymousAuth = ({
name: o.name,
slug: o.slug,
})),
});
})
if (orgs?.[0]) {
console.debug(
`[ANONYMOUS_AUTH] Setting active organization to ${orgs[0].id}`,
);
)
await authClient.organization.setActive({
organizationId: orgs[0].id,
});
})
console.debug(
`[ANONYMOUS_AUTH] Redirecting to /${orgs[0].slug}${dashboardPath}`,
);
router.push(dashboardPath);
)
router.push(dashboardPath)
}
} else {
console.debug(
`[ANONYMOUS_AUTH] Active organization ID: ${session.session.activeOrganizationId}`,
);
)
console.debug(
"[ANONYMOUS_AUTH] Fetching full organization details...",
);
)
const org = await authClient.organization.getFullOrganization({
query: {
organizationId: session.session.activeOrganizationId,
},
});
})
console.debug("[ANONYMOUS_AUTH] Full organization retrieved:", {
id: org.id,
name: org.name,
slug: org.slug,
});
})
console.debug(
`[ANONYMOUS_AUTH] Redirecting to /${org.slug}${dashboardPath}`,
);
router.push(dashboardPath);
)
router.push(dashboardPath)
}
}
};
}
createAnonymousSession();
}, [router.push]);
createAnonymousSession()
}, [router.push])
// Return null as this component only handles the redirect logic
return null;
};
return null
}

View file

@ -1,6 +1,6 @@
interface GlassMenuEffectProps {
rounded?: string;
className?: string;
rounded?: string
className?: string
}
export function GlassMenuEffect({
@ -14,5 +14,5 @@ export function GlassMenuEffect({
className={`absolute inset-0 backdrop-blur-md bg-white/5 border border-white/10 ${rounded}`}
/>
</div>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function HeadingH1Bold({
className,
asChild,
...props
}: React.ComponentProps<"h1"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h1";
const Comp = asChild ? Root : "h1"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function HeadingH1Bold({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function HeadingH1Medium({
className,
asChild,
...props
}: React.ComponentProps<"h1"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h1";
const Comp = asChild ? Root : "h1"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function HeadingH1Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function HeadingH2Bold({
className,
asChild,
...props
}: React.ComponentProps<"h2"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h2";
const Comp = asChild ? Root : "h2"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function HeadingH2Bold({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function HeadingH2Medium({
className,
asChild,
...props
}: React.ComponentProps<"h2"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h2";
const Comp = asChild ? Root : "h2"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function HeadingH2Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function HeadingH3Bold({
className,
asChild,
...props
}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h3";
const Comp = asChild ? Root : "h3"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function HeadingH3Bold({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function HeadingH3Medium({
className,
asChild,
...props
}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h3";
const Comp = asChild ? Root : "h3"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function HeadingH3Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function HeadingH4Bold({
className,
asChild,
...props
}: React.ComponentProps<"h4"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h4";
const Comp = asChild ? Root : "h4"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function HeadingH4Bold({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function HeadingH4Medium({
className,
asChild,
...props
}: React.ComponentProps<"h4"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h4";
const Comp = asChild ? Root : "h4"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function HeadingH4Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Label1Medium({
className,
asChild,
...props
}: React.ComponentProps<"p"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "p";
const Comp = asChild ? Root : "p"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Label1Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Label1Regular({
className,
asChild,
...props
}: React.ComponentProps<"p"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "p";
const Comp = asChild ? Root : "p"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Label1Regular({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Label2Medium({
className,
asChild,
...props
}: React.ComponentProps<"p"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "p";
const Comp = asChild ? Root : "p"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Label2Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Label2Regular({
className,
asChild,
...props
}: React.ComponentProps<"p"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "p";
const Comp = asChild ? Root : "p"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Label2Regular({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Label3Medium({
className,
asChild,
...props
}: React.ComponentProps<"p"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "p";
const Comp = asChild ? Root : "p"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Label3Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Label3Regular({
className,
asChild,
...props
}: React.ComponentProps<"p"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "p";
const Comp = asChild ? Root : "p"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Label3Regular({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Title1Bold({
className,
asChild,
...props
}: React.ComponentProps<"h1"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h1";
const Comp = asChild ? Root : "h1"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Title1Bold({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Title1Medium({
className,
asChild,
...props
}: React.ComponentProps<"h1"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h1";
const Comp = asChild ? Root : "h1"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Title1Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Title2Bold({
className,
asChild,
...props
}: React.ComponentProps<"h2"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h2";
const Comp = asChild ? Root : "h2"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Title2Bold({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Title2Medium({
className,
asChild,
...props
}: React.ComponentProps<"h2"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h2";
const Comp = asChild ? Root : "h2"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Title2Medium({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Title3Bold({
className,
asChild,
...props
}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h3";
const Comp = asChild ? Root : "h3"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Title3Bold({
)}
{...props}
/>
);
)
}

View file

@ -1,12 +1,12 @@
import { cn } from "@lib/utils";
import { Root } from "@radix-ui/react-slot";
import { cn } from "@lib/utils"
import { Root } from "@radix-ui/react-slot"
export function Title3Medium({
className,
asChild,
...props
}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
const Comp = asChild ? Root : "h3";
const Comp = asChild ? Root : "h3"
return (
<Comp
className={cn(
@ -15,5 +15,5 @@ export function Title3Medium({
)}
{...props}
/>
);
)
}