mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-08 22:21:07 +00:00
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:
parent
36ecf47110
commit
1706752668
81 changed files with 1321 additions and 1127 deletions
|
|
@ -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<{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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())}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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={{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
? [
|
||||
|
|
|
|||
|
|
@ -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")}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export function SyncStatusBadge({
|
|||
"font-medium text-[13px] tracking-[-0.13px] text-[#EF4444]",
|
||||
)}
|
||||
>
|
||||
Disconnected
|
||||
Needs reauth
|
||||
</span>
|
||||
)}
|
||||
{status === "idle" && (
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
29
apps/web/hooks/use-connection-health.ts
Normal file
29
apps/web/hooks/use-connection-health.ts
Normal 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 }
|
||||
}
|
||||
|
|
@ -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")) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue