mirror of
https://github.com/james-m-jordan/morphik-core.git
synced 2025-05-09 19:32:38 +00:00
Enhance alert system: add warning type and improve event handling; add Checkbox component and update dependencies
This commit is contained in:
parent
e6ae047aad
commit
05d5fab228
@ -56,10 +56,10 @@ class ColpaliEmbeddingModel(BaseEmbeddingModel):
|
|||||||
else:
|
else:
|
||||||
contents.append(chunk.content)
|
contents.append(chunk.content)
|
||||||
|
|
||||||
return [self.generate_embeddings(content) for content in contents]
|
return [await self.generate_embeddings(content) for content in contents]
|
||||||
|
|
||||||
async def embed_for_query(self, text: str) -> torch.Tensor:
|
async def embed_for_query(self, text: str) -> torch.Tensor:
|
||||||
return self.generate_embeddings(text)
|
return await self.generate_embeddings(text)
|
||||||
|
|
||||||
async def generate_embeddings(self, content: str | Image) -> np.ndarray:
|
async def generate_embeddings(self, content: str | Image) -> np.ndarray:
|
||||||
if isinstance(content, Image):
|
if isinstance(content, Image):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect, ChangeEvent } from 'react';
|
import React, { useState, useEffect, ChangeEvent } from 'react';
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@ -59,15 +60,17 @@ interface QueryOptions extends SearchOptions {
|
|||||||
graph_name?: string;
|
graph_name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BatchUploadError {
|
// Commented out as currently unused
|
||||||
filename: string;
|
// interface BatchUploadError {
|
||||||
error: string;
|
// filename: string;
|
||||||
}
|
// error: string;
|
||||||
|
// }
|
||||||
|
|
||||||
const MorphikUI = () => {
|
const MorphikUI = () => {
|
||||||
const [activeSection, setActiveSection] = useState('documents');
|
const [activeSection, setActiveSection] = useState('documents');
|
||||||
const [documents, setDocuments] = useState<Document[]>([]);
|
const [documents, setDocuments] = useState<Document[]>([]);
|
||||||
const [selectedDocument, setSelectedDocument] = useState<Document | null>(null);
|
const [selectedDocument, setSelectedDocument] = useState<Document | null>(null);
|
||||||
|
const [selectedDocuments, setSelectedDocuments] = useState<string[]>([]);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
||||||
const [chatQuery, setChatQuery] = useState('');
|
const [chatQuery, setChatQuery] = useState('');
|
||||||
@ -249,6 +252,108 @@ const MorphikUI = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle multiple document deletion
|
||||||
|
const handleDeleteMultipleDocuments = async () => {
|
||||||
|
if (selectedDocuments.length === 0) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Show initial alert for deletion progress
|
||||||
|
const alertId = 'delete-multiple-progress';
|
||||||
|
showAlert(`Deleting ${selectedDocuments.length} documents...`, {
|
||||||
|
type: 'info',
|
||||||
|
dismissible: false,
|
||||||
|
id: alertId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Perform deletions sequentially
|
||||||
|
const results = await Promise.all(
|
||||||
|
selectedDocuments.map(docId =>
|
||||||
|
fetch(`${API_BASE_URL}/documents/${docId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if any deletion failed
|
||||||
|
const failedCount = results.filter(res => !res.ok).length;
|
||||||
|
|
||||||
|
// Clear selected document if it was among deleted ones
|
||||||
|
if (selectedDocument && selectedDocuments.includes(selectedDocument.external_id)) {
|
||||||
|
setSelectedDocument(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear selection
|
||||||
|
setSelectedDocuments([]);
|
||||||
|
|
||||||
|
// Refresh documents list
|
||||||
|
await fetchDocuments();
|
||||||
|
|
||||||
|
// Remove progress alert
|
||||||
|
removeAlert(alertId);
|
||||||
|
|
||||||
|
// Show final result alert
|
||||||
|
if (failedCount > 0) {
|
||||||
|
showAlert(`Deleted ${selectedDocuments.length - failedCount} documents. ${failedCount} deletions failed.`, {
|
||||||
|
type: "warning",
|
||||||
|
duration: 4000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showAlert(`Successfully deleted ${selectedDocuments.length} documents`, {
|
||||||
|
type: "success",
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'An unknown error occurred';
|
||||||
|
showAlert(errorMsg, {
|
||||||
|
type: 'error',
|
||||||
|
title: 'Delete Failed',
|
||||||
|
duration: 5000
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle document selection - currently handled by handleCheckboxChange
|
||||||
|
// Keeping implementation in comments for reference
|
||||||
|
/*
|
||||||
|
const toggleDocumentSelection = (e: React.MouseEvent, docId: string) => {
|
||||||
|
e.stopPropagation(); // Prevent document selection/details view
|
||||||
|
|
||||||
|
setSelectedDocuments(prev => {
|
||||||
|
if (prev.includes(docId)) {
|
||||||
|
return prev.filter(id => id !== docId);
|
||||||
|
} else {
|
||||||
|
return [...prev, docId];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Handle checkbox change (wrapper function for use with shadcn checkbox)
|
||||||
|
const handleCheckboxChange = (checked: boolean | "indeterminate", docId: string) => {
|
||||||
|
setSelectedDocuments(prev => {
|
||||||
|
if (checked === true && !prev.includes(docId)) {
|
||||||
|
return [...prev, docId];
|
||||||
|
} else if (checked === false && prev.includes(docId)) {
|
||||||
|
return prev.filter(id => id !== docId);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get "indeterminate" state for select all checkbox
|
||||||
|
const getSelectAllState = () => {
|
||||||
|
if (selectedDocuments.length === 0) return false;
|
||||||
|
if (selectedDocuments.length === documents.length) return true;
|
||||||
|
return "indeterminate";
|
||||||
|
};
|
||||||
|
|
||||||
// Handle file upload
|
// Handle file upload
|
||||||
const handleFileUpload = async () => {
|
const handleFileUpload = async () => {
|
||||||
@ -780,9 +885,21 @@ const MorphikUI = () => {
|
|||||||
{activeSection === 'documents' && (
|
{activeSection === 'documents' && (
|
||||||
<div className="flex-1 flex flex-col h-full">
|
<div className="flex-1 flex flex-col h-full">
|
||||||
<div className="flex justify-between items-center bg-white py-3 mb-4">
|
<div className="flex justify-between items-center bg-white py-3 mb-4">
|
||||||
<div>
|
<div className="flex items-center gap-4">
|
||||||
<h2 className="text-2xl font-bold leading-tight">Your Documents</h2>
|
<div>
|
||||||
<p className="text-muted-foreground">Manage your uploaded documents and view their metadata.</p>
|
<h2 className="text-2xl font-bold leading-tight">Your Documents</h2>
|
||||||
|
<p className="text-muted-foreground">Manage your uploaded documents and view their metadata.</p>
|
||||||
|
</div>
|
||||||
|
{selectedDocuments.length > 0 && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleDeleteMultipleDocuments}
|
||||||
|
disabled={loading}
|
||||||
|
className="border-red-500 text-red-500 hover:bg-red-50 ml-4"
|
||||||
|
>
|
||||||
|
Delete {selectedDocuments.length} selected
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Dialog
|
<Dialog
|
||||||
open={showUploadDialog}
|
open={showUploadDialog}
|
||||||
@ -939,7 +1056,21 @@ const MorphikUI = () => {
|
|||||||
<div className="border rounded-md">
|
<div className="border rounded-md">
|
||||||
<div className="bg-gray-100 border-b p-3 font-medium sticky top-0">
|
<div className="bg-gray-100 border-b p-3 font-medium sticky top-0">
|
||||||
<div className="grid grid-cols-12">
|
<div className="grid grid-cols-12">
|
||||||
<div className="col-span-5">Filename</div>
|
<div className="col-span-1 flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
id="select-all-documents"
|
||||||
|
checked={getSelectAllState()}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (checked) {
|
||||||
|
setSelectedDocuments(documents.map(doc => doc.external_id));
|
||||||
|
} else {
|
||||||
|
setSelectedDocuments([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-label="Select all documents"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-4">Filename</div>
|
||||||
<div className="col-span-3">Type</div>
|
<div className="col-span-3">Type</div>
|
||||||
<div className="col-span-4">ID</div>
|
<div className="col-span-4">ID</div>
|
||||||
</div>
|
</div>
|
||||||
@ -952,7 +1083,16 @@ const MorphikUI = () => {
|
|||||||
onClick={() => handleDocumentClick(doc)}
|
onClick={() => handleDocumentClick(doc)}
|
||||||
className="grid grid-cols-12 p-3 cursor-pointer hover:bg-gray-50 border-b"
|
className="grid grid-cols-12 p-3 cursor-pointer hover:bg-gray-50 border-b"
|
||||||
>
|
>
|
||||||
<div className="col-span-5 flex items-center">
|
<div className="col-span-1 flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
id={`doc-${doc.external_id}`}
|
||||||
|
checked={selectedDocuments.includes(doc.external_id)}
|
||||||
|
onCheckedChange={(checked) => handleCheckboxChange(checked, doc.external_id)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
aria-label={`Select ${doc.filename || 'document'}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-4 flex items-center">
|
||||||
{doc.filename || 'N/A'}
|
{doc.filename || 'N/A'}
|
||||||
{doc.external_id === selectedDocument?.external_id && (
|
{doc.external_id === selectedDocument?.external_id && (
|
||||||
<Badge variant="outline" className="ml-2">Selected</Badge>
|
<Badge variant="outline" className="ml-2">Selected</Badge>
|
||||||
@ -1083,14 +1223,14 @@ const MorphikUI = () => {
|
|||||||
|
|
||||||
{/* Search Section */}
|
{/* Search Section */}
|
||||||
{activeSection === 'search' && (
|
{activeSection === 'search' && (
|
||||||
<Card>
|
<Card className="flex-1 flex flex-col h-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Search Documents</CardTitle>
|
<CardTitle>Search Documents</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Search across your documents to find relevant information.
|
Search across your documents to find relevant information.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="flex-1 flex flex-col">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
@ -1108,19 +1248,22 @@ const MorphikUI = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button
|
<Dialog open={showSearchAdvanced} onOpenChange={setShowSearchAdvanced}>
|
||||||
type="button"
|
<DialogTrigger asChild>
|
||||||
className="flex items-center text-sm text-gray-600 hover:text-gray-900"
|
<Button variant="outline" size="sm" className="flex items-center">
|
||||||
onClick={() => setShowSearchAdvanced(!showSearchAdvanced)}
|
<Settings className="mr-2 h-4 w-4" />
|
||||||
>
|
Advanced Options
|
||||||
<Settings className="mr-1 h-4 w-4" />
|
</Button>
|
||||||
Advanced Options
|
</DialogTrigger>
|
||||||
{showSearchAdvanced ? <ChevronUp className="ml-1 h-4 w-4" /> : <ChevronDown className="ml-1 h-4 w-4" />}
|
<DialogContent className="sm:max-w-md">
|
||||||
</button>
|
<DialogHeader>
|
||||||
|
<DialogTitle>Search Options</DialogTitle>
|
||||||
{showSearchAdvanced && (
|
<DialogDescription>
|
||||||
<div className="mt-3 p-4 border rounded-md bg-gray-50">
|
Configure advanced search parameters
|
||||||
<div className="space-y-4">
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="grid gap-4 py-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="search-filters" className="block mb-2">Filters (JSON)</Label>
|
<Label htmlFor="search-filters" className="block mb-2">Filters (JSON)</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@ -1178,49 +1321,57 @@ const MorphikUI = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
<DialogFooter>
|
||||||
|
<Button onClick={() => setShowSearchAdvanced(false)}>Apply</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6 flex-1 overflow-hidden">
|
||||||
{searchResults.length > 0 ? (
|
{searchResults.length > 0 ? (
|
||||||
<div className="space-y-6">
|
<div>
|
||||||
<h3 className="text-lg font-medium">Results ({searchResults.length})</h3>
|
<h3 className="text-lg font-medium mb-4">Results ({searchResults.length})</h3>
|
||||||
|
|
||||||
{searchResults.map((result) => (
|
<ScrollArea className="h-[calc(100vh-320px)]">
|
||||||
<Card key={`${result.document_id}-${result.chunk_number}`}>
|
<div className="space-y-6 pr-4">
|
||||||
<CardHeader className="pb-2">
|
{searchResults.map((result) => (
|
||||||
<div className="flex justify-between items-start">
|
<Card key={`${result.document_id}-${result.chunk_number}`}>
|
||||||
<div>
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-base">
|
<div className="flex justify-between items-start">
|
||||||
{result.filename || `Document ${result.document_id.substring(0, 8)}...`}
|
<div>
|
||||||
</CardTitle>
|
<CardTitle className="text-base">
|
||||||
<CardDescription>
|
{result.filename || `Document ${result.document_id.substring(0, 8)}...`}
|
||||||
Chunk {result.chunk_number} • Score: {result.score.toFixed(2)}
|
</CardTitle>
|
||||||
</CardDescription>
|
<CardDescription>
|
||||||
</div>
|
Chunk {result.chunk_number} • Score: {result.score.toFixed(2)}
|
||||||
<Badge variant="outline">
|
</CardDescription>
|
||||||
{result.content_type}
|
</div>
|
||||||
</Badge>
|
<Badge variant="outline">
|
||||||
</div>
|
{result.content_type}
|
||||||
</CardHeader>
|
</Badge>
|
||||||
<CardContent>
|
</div>
|
||||||
{renderContent(result.content, result.content_type)}
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
<Accordion type="single" collapsible className="mt-4">
|
{renderContent(result.content, result.content_type)}
|
||||||
<AccordionItem value="metadata">
|
|
||||||
<AccordionTrigger className="text-sm">Metadata</AccordionTrigger>
|
<Accordion type="single" collapsible className="mt-4">
|
||||||
<AccordionContent>
|
<AccordionItem value="metadata">
|
||||||
<pre className="bg-gray-50 p-2 rounded text-xs overflow-x-auto">
|
<AccordionTrigger className="text-sm">Metadata</AccordionTrigger>
|
||||||
{JSON.stringify(result.metadata, null, 2)}
|
<AccordionContent>
|
||||||
</pre>
|
<pre className="bg-gray-50 p-2 rounded text-xs overflow-x-auto">
|
||||||
</AccordionContent>
|
{JSON.stringify(result.metadata, null, 2)}
|
||||||
</AccordionItem>
|
</pre>
|
||||||
</Accordion>
|
</AccordionContent>
|
||||||
</CardContent>
|
</AccordionItem>
|
||||||
</Card>
|
</Accordion>
|
||||||
))}
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-16 border border-dashed rounded-lg">
|
<div className="text-center py-16 border border-dashed rounded-lg">
|
||||||
|
@ -6,7 +6,7 @@ import { cn } from '@/lib/utils';
|
|||||||
|
|
||||||
interface AlertInstanceProps {
|
interface AlertInstanceProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: 'error' | 'success' | 'info' | 'upload';
|
type: 'error' | 'success' | 'info' | 'upload' | 'warning';
|
||||||
title?: string;
|
title?: string;
|
||||||
message: string;
|
message: string;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
@ -29,7 +29,8 @@ const AlertInstance = ({
|
|||||||
type === 'error' && "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
type === 'error' && "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
type === 'upload' && "bg-blue-50 text-blue-700 border-blue-200",
|
type === 'upload' && "bg-blue-50 text-blue-700 border-blue-200",
|
||||||
type === 'success' && "bg-green-50 text-green-700 border-green-200",
|
type === 'success' && "bg-green-50 text-green-700 border-green-200",
|
||||||
type === 'info' && "bg-gray-50 text-gray-700 border-gray-200"
|
type === 'info' && "bg-gray-50 text-gray-700 border-gray-200",
|
||||||
|
type === 'warning' && "bg-amber-50 text-amber-700 border-amber-200"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{dismissible && (
|
{dismissible && (
|
||||||
@ -57,13 +58,22 @@ export function AlertSystem({ position = 'bottom-right' }: AlertSystemProps) {
|
|||||||
|
|
||||||
// Custom event handlers for adding and removing alerts
|
// Custom event handlers for adding and removing alerts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleAddAlert = (event: CustomEvent) => {
|
const handleAddAlert = (event: Event) => {
|
||||||
const alert = event.detail;
|
const customEvent = event as CustomEvent<{
|
||||||
|
id?: string;
|
||||||
|
type: 'error' | 'success' | 'info' | 'upload' | 'warning';
|
||||||
|
title?: string;
|
||||||
|
message: string;
|
||||||
|
duration?: number;
|
||||||
|
dismissible?: boolean;
|
||||||
|
}>;
|
||||||
|
const alert = customEvent.detail;
|
||||||
if (alert) {
|
if (alert) {
|
||||||
const newAlert = {
|
const newAlert: AlertInstanceProps = {
|
||||||
...alert,
|
...alert,
|
||||||
id: alert.id || Date.now().toString(),
|
id: alert.id || Date.now().toString(),
|
||||||
dismissible: alert.dismissible !== false,
|
dismissible: alert.dismissible !== false,
|
||||||
|
onDismiss: removeAlert,
|
||||||
};
|
};
|
||||||
|
|
||||||
setAlerts(prev => [...prev, newAlert]);
|
setAlerts(prev => [...prev, newAlert]);
|
||||||
@ -77,20 +87,20 @@ export function AlertSystem({ position = 'bottom-right' }: AlertSystemProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveAlert = (event: CustomEvent) => {
|
const handleRemoveAlert = (event: Event) => {
|
||||||
const { id } = event.detail;
|
const customEvent = event as CustomEvent<{id: string}>;
|
||||||
|
const { id } = customEvent.detail;
|
||||||
if (id) {
|
if (id) {
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cast to any to handle CustomEvent
|
window.addEventListener('morphik:alert', handleAddAlert as EventListener);
|
||||||
window.addEventListener('morphik:alert' as any, handleAddAlert);
|
window.addEventListener('morphik:alert:remove', handleRemoveAlert as EventListener);
|
||||||
window.addEventListener('morphik:alert:remove' as any, handleRemoveAlert);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('morphik:alert' as any, handleAddAlert);
|
window.removeEventListener('morphik:alert', handleAddAlert as EventListener);
|
||||||
window.removeEventListener('morphik:alert:remove' as any, handleRemoveAlert);
|
window.removeEventListener('morphik:alert:remove', handleRemoveAlert as EventListener);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -125,7 +135,7 @@ export function AlertSystem({ position = 'bottom-right' }: AlertSystemProps) {
|
|||||||
export const showAlert = (
|
export const showAlert = (
|
||||||
message: string,
|
message: string,
|
||||||
options?: {
|
options?: {
|
||||||
type?: 'error' | 'success' | 'info' | 'upload';
|
type?: 'error' | 'success' | 'info' | 'upload' | 'warning';
|
||||||
title?: string;
|
title?: string;
|
||||||
duration?: number; // in milliseconds, none means it stays until dismissed
|
duration?: number; // in milliseconds, none means it stays until dismissed
|
||||||
dismissible?: boolean;
|
dismissible?: boolean;
|
||||||
|
30
ui-component/components/ui/checkbox.tsx
Normal file
30
ui-component/components/ui/checkbox.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { Check } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
@ -17,6 +17,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-accordion": "^1.2.3",
|
"@radix-ui/react-accordion": "^1.2.3",
|
||||||
|
"@radix-ui/react-checkbox": "^1.1.5",
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-label": "^2.1.2",
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"force-graph": "^1.49.4",
|
"force-graph": "^1.49.4",
|
||||||
|
"formdata-node": "^6.0.3",
|
||||||
"label": "^0.2.2",
|
"label": "^0.2.2",
|
||||||
"lucide-react": "^0.469.0",
|
"lucide-react": "^0.469.0",
|
||||||
"next": "^14.2.24",
|
"next": "^14.2.24",
|
||||||
@ -40,7 +42,8 @@
|
|||||||
"shadcn-ui": "^0.9.4",
|
"shadcn-ui": "^0.9.4",
|
||||||
"sheet": "^0.2.0",
|
"sheet": "^0.2.0",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"web-streams-polyfill": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@shadcn/ui": "^0.0.4",
|
"@shadcn/ui": "^0.0.4",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user