-
Notifications
You must be signed in to change notification settings - Fork 87
chore(DATAGO-120962): replacing custom components with standard ones in project (and prompt) UI #740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(DATAGO-120962): replacing custom components with standard ones in project (and prompt) UI #740
Changes from 1 commit
c12cffb
e2157ff
90cd7e3
5bdad03
08c6fdc
0dc5443
333f052
48a0e1e
971373c
2e86e31
0cfc180
f8c8f07
3a6ac79
a4abc10
dfec097
a8ad161
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,11 @@ | ||
| import { useState, useCallback, useEffect } from "react"; | ||
| import { Search, X } from "lucide-react"; | ||
| import { Input } from "@/lib/components/ui/input"; | ||
| import { Button } from "@/lib/components/ui/button"; | ||
| import { Badge } from "@/lib/components/ui/badge"; | ||
| import { useDebounce } from "@/lib/hooks/useDebounce"; | ||
| import type { Session } from "@/lib/types"; | ||
|
|
||
| import { api } from "@/lib/api"; | ||
| import { ProjectBadge } from "@/lib/components/chat"; | ||
| import { Button, Input } from "@/lib/components/ui"; | ||
| import { useDebounce } from "@/lib/hooks"; | ||
| import type { Session } from "@/lib/types"; | ||
|
|
||
| interface SessionSearchProps { | ||
| onSessionSelect: (sessionId: string) => void; | ||
|
|
@@ -98,11 +98,7 @@ export const SessionSearch = ({ onSessionSelect, projectId }: SessionSearchProps | |
| <button key={session.id} onClick={() => handleSessionClick(session.id)} className="hover:bg-accent hover:text-accent-foreground w-full rounded-sm px-3 py-2 text-left text-sm"> | ||
| <div className="mb-1 flex items-center justify-between gap-2"> | ||
| <div className="flex-1 truncate font-medium">{session.name || "Untitled Session"}</div> | ||
| {session.projectName && ( | ||
| <Badge variant="outline" className="bg-primary/10 border-primary/30 text-primary flex-shrink-0 px-2 py-0.5 text-xs font-semibold shadow-sm"> | ||
| {session.projectName} | ||
| </Badge> | ||
| )} | ||
| {session.projectName && <ProjectBadge text={session.projectName} />} | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use extracted badge. |
||
| </div> | ||
| <div className="text-muted-foreground text-xs">{new Date(session.updatedTime).toLocaleDateString()}</div> | ||
| </button> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| import React, { useState, useEffect } from "react"; | ||
| import { Download, ChevronDown, Trash, Info, ChevronUp, CircleAlert } from "lucide-react"; | ||
| import { Download, ChevronDown, Trash, Info, ChevronUp, CircleAlert, Pencil } from "lucide-react"; | ||
|
|
||
| import { Button, Spinner, Badge } from "@/lib/components/ui"; | ||
| import { FileIcon } from "../file/FileIcon"; | ||
| import { Button, Spinner } from "@/lib/components/ui"; | ||
| import { FileIcon, ProjectBadge } from "../file"; | ||
| import { cn } from "@/lib/utils"; | ||
|
|
||
| const ErrorState: React.FC<{ message: string }> = ({ message }) => ( | ||
|
|
@@ -26,6 +26,7 @@ export interface ArtifactBarProps { | |
| onDelete?: () => void; | ||
| onInfo?: () => void; | ||
| onExpand?: () => void; | ||
| onEdit?: () => void; | ||
| }; | ||
| // For creation progress | ||
| bytesTransferred?: number; | ||
|
|
@@ -211,11 +212,7 @@ export const ArtifactBar: React.FC<ArtifactBarProps> = ({ | |
| {hasDescription ? displayDescription : filename.length > 50 ? `${filename.substring(0, 47)}...` : filename} | ||
| </div> | ||
| {/* Project badge */} | ||
| {source === "project" && ( | ||
| <Badge variant="outline" className="bg-primary/10 border-primary/30 text-primary px-2 py-0.5 text-xs font-semibold shadow-sm"> | ||
| Project | ||
| </Badge> | ||
| )} | ||
| {source === "project" && <ProjectBadge />} | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use extracted badge |
||
| </div> | ||
|
|
||
| {/* Secondary line: Filename (if description shown) or status */} | ||
|
|
@@ -298,6 +295,24 @@ export const ArtifactBar: React.FC<ArtifactBarProps> = ({ | |
| </Button> | ||
| )} | ||
|
|
||
| {status === "completed" && actions?.onEdit && !isDeleted && ( | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| onClick={e => { | ||
| e.stopPropagation(); | ||
| try { | ||
| actions.onEdit?.(); | ||
| } catch (error) { | ||
| console.error("Edit failed:", error); | ||
| } | ||
| }} | ||
| tooltip="Edit Description" | ||
| > | ||
| <Pencil className="h-4 w-4" /> | ||
| </Button> | ||
| )} | ||
|
|
||
| {status === "completed" && actions?.onDelete && !isDeleted && ( | ||
| <Button | ||
| variant="ghost" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { Badge, Tooltip, TooltipContent, TooltipTrigger } from "@/lib"; | ||
|
|
||
| export const ProjectBadge = ({ text = "Project" }: { text?: string }) => { | ||
| return ( | ||
| <Tooltip> | ||
| <TooltipTrigger asChild> | ||
| <Badge variant="outline" className="max-w-[120px]"> | ||
| <span className="block truncate">{text}</span> | ||
| </Badge> | ||
| </TooltipTrigger> | ||
| <TooltipContent>{text}</TooltipContent> | ||
| </Tooltip> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| export { AudioRecorder } from "./AudioRecorder"; | ||
| export { ChatInputArea } from "./ChatInputArea"; | ||
| export { ChatMessage } from "./ChatMessage"; | ||
| export { ChatSessionDeleteDialog } from "./ChatSessionDeleteDialog"; | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add missing exports. |
||
| export { ChatSessionDialog } from "./ChatSessionDialog"; | ||
| export { ChatSessions } from "./ChatSessions"; | ||
| export { ChatSidePanel } from "./ChatSidePanel"; | ||
| export { LoadingMessageRow } from "./LoadingMessageRow"; | ||
|
|
@@ -9,4 +11,5 @@ export { MoveSessionDialog } from "./MoveSessionDialog"; | |
| export { VariableDialog } from "./VariableDialog"; | ||
| export { SessionSearch } from "./SessionSearch"; | ||
| export { MessageHoverButtons } from "./MessageHoverButtons"; | ||
| export * from "./file"; | ||
| export * from "./selection"; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| import { useState, useRef, type DragEvent, type ChangeEvent } from "react"; | ||
| import { X } from "lucide-react"; | ||
|
|
||
| import { Button } from "@/lib/components"; | ||
| import { MessageBanner } from "@/lib/components/common"; | ||
|
|
||
| /** | ||
| * Removes a file at the specified index from a FileList. | ||
| * @param prevFiles the FileList | ||
| * @param indexToRemove the index of the file to remove | ||
| * @returns new FileList with the file removed, or null if no files remain | ||
| */ | ||
| const removeAtIndex = (prevFiles: FileList | null, indexToRemove: number): FileList | null => { | ||
| if (!prevFiles) return null; | ||
| const filesArray = Array.from(prevFiles); | ||
| filesArray.splice(indexToRemove, 1); | ||
| if (filesArray.length === 0) { | ||
| return null; | ||
| } | ||
| const dataTransfer = new DataTransfer(); | ||
| filesArray.forEach(file => dataTransfer.items.add(file)); | ||
| return dataTransfer.files; | ||
| }; | ||
|
|
||
| export interface FileUploadProps { | ||
| name: string; | ||
| accept: string; | ||
| multiple?: boolean; | ||
| disabled?: boolean; | ||
| testid?: string; | ||
| value?: FileList | null; | ||
| onChange: (file: FileList | null) => void; | ||
| onValidate?: (files: FileList) => { valid: boolean; error?: string }; | ||
| } | ||
|
|
||
| function FileUpload({ name, accept, multiple = false, disabled = false, testid = "", value = null, onChange, onValidate }: FileUploadProps) { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Developed in enterprise, porting back to community for sharing with a couple of tweaks minor fixes and adding optional file validation. |
||
| const [uploadedFiles, setUploadedFiles] = useState<FileList | null>(value); | ||
| const [isDragging, setIsDragging] = useState(false); | ||
| const [validationError, setValidationError] = useState<string | null>(null); | ||
| const fileInputRef = useRef<HTMLInputElement>(null); | ||
|
|
||
| const setSelectedFiles = (files: FileList | null) => { | ||
| if (files && files.length > 0) { | ||
| // Validate files if validation function is provided | ||
| if (onValidate) { | ||
| const validation = onValidate(files); | ||
| if (!validation.valid) { | ||
| setValidationError(validation.error || "File validation failed."); | ||
| if (fileInputRef.current) { | ||
| fileInputRef.current.value = ""; | ||
| } | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| setValidationError(null); | ||
| setUploadedFiles(files); | ||
| onChange(files); | ||
| } else { | ||
| setValidationError(null); | ||
| setUploadedFiles(null); | ||
| onChange(null); | ||
| fileInputRef.current!.value = ""; | ||
| } | ||
| }; | ||
|
|
||
| const handleDragEnter = (e: DragEvent<HTMLDivElement>) => { | ||
| e.preventDefault(); | ||
| setIsDragging(true); | ||
| }; | ||
|
|
||
| const handleDragLeave = (e: DragEvent<HTMLDivElement>) => { | ||
| e.preventDefault(); | ||
| setIsDragging(false); | ||
| }; | ||
|
|
||
| const handleDragOver = (e: DragEvent<HTMLDivElement>) => { | ||
| e.preventDefault(); | ||
| if (disabled) { | ||
| e.currentTarget.style.cursor = "not-allowed"; | ||
| } else { | ||
| e.currentTarget.style.cursor = "default"; | ||
| } | ||
| }; | ||
|
|
||
| const handleDrop = (e: DragEvent<HTMLDivElement>) => { | ||
| e.preventDefault(); | ||
| setIsDragging(false); | ||
|
|
||
| if (!disabled) { | ||
| let files = e.dataTransfer.files; | ||
|
|
||
| // If multiple is false and more than one file is dropped, only take the first file | ||
| if (!multiple && files.length > 1) { | ||
| const dataTransfer = new DataTransfer(); | ||
| dataTransfer.items.add(files[0]); | ||
| files = dataTransfer.files; | ||
| } | ||
|
|
||
| setSelectedFiles(files); | ||
| } | ||
| }; | ||
|
|
||
| const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { | ||
| const files = e.target.files; | ||
| setSelectedFiles(files); | ||
| }; | ||
|
|
||
| const handleDropZoneClick = (e: React.MouseEvent<HTMLButtonElement>) => { | ||
| e.preventDefault(); | ||
| fileInputRef.current?.click(); | ||
| }; | ||
|
|
||
| const handleClearValidationError = () => { | ||
| setValidationError(null); | ||
| }; | ||
|
|
||
| const handleRemoveFile = (index: number) => { | ||
| const newFiles = removeAtIndex(uploadedFiles, index); | ||
| setUploadedFiles(newFiles); | ||
| onChange(newFiles); | ||
| }; | ||
|
|
||
| return ( | ||
| <div> | ||
| {validationError && ( | ||
| <div className="mb-3"> | ||
| <MessageBanner variant="error" message={validationError} dismissible onDismiss={handleClearValidationError} /> | ||
| </div> | ||
| )} | ||
| <input ref={fileInputRef} name={name} type="file" multiple={multiple} disabled={disabled} onChange={handleFileChange} className="hidden" accept={accept} data-testid={testid} /> | ||
| {uploadedFiles ? ( | ||
| Array.from(uploadedFiles).map((file, index) => ( | ||
| <div key={file.name} className="var(--tw-border-style) flex h-[48px] flex-row items-center rounded-sm border-1 pr-2 pl-4 text-[var(--color-secondary-text-wMain)]"> | ||
| <div className="flex-1 font-semibold">{file.name}</div> | ||
| <Button variant="ghost" size="sm" onClick={() => handleRemoveFile(index)} aria-label={`Remove file ${file.name}`}> | ||
| <X /> | ||
| </Button> | ||
| </div> | ||
| )) | ||
| ) : ( | ||
| <div | ||
| className={`flex h-[140px] flex-col justify-center rounded-sm border-1 border-dashed transition-colors ${isDragging ? "border-[var(--color-brand-wMain)] hover:border-solid" : "border-[var(--color-secondary-w40)]"}`} | ||
| onDragEnter={handleDragEnter} | ||
| onDragLeave={handleDragLeave} | ||
| onDragOver={handleDragOver} | ||
| onDrop={handleDrop} | ||
| role="dropzone" | ||
| > | ||
| {isDragging && !disabled ? ( | ||
| <div className="pointer-events-none text-center text-[var(--color-primary-text-wMain)]">Drop file here</div> | ||
| ) : ( | ||
| <div className="pointer-events-none text-center text-[var(--color-secondary-text-wMain)]"> | ||
| <div>Drag and drop file here</div> | ||
| <div className="mt-2 mb-2 flex flex-row items-center justify-center"> | ||
| <div className="mr-1 h-[1px] w-[125px] bg-[var(--color-secondary-w40)]"></div> | ||
| <div>OR</div> | ||
| <div className="ml-1 h-[1px] w-[125px] bg-[var(--color-secondary-w40)]"></div> | ||
| </div> | ||
| <div> | ||
| <Button className="pointer-events-auto" variant="ghost" disabled={disabled} onClick={handleDropZoneClick}> | ||
| Upload File | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export { FileUpload }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,7 +47,7 @@ export const NavigationButton: React.FC<NavigationItemProps> = ({ item, isActive | |
| <Icon className={cn("mb-1 h-6 w-6", isActive && "text-(--color-brand-wMain)")} /> | ||
| <span className="text-center text-[13px] leading-tight">{label}</span> | ||
| {badge && ( | ||
| <Badge variant="outline" className="mt-1 border-gray-400 bg-(--color-secondary-w80) px-1 py-0.5 text-[8px] leading-tight text-(--color-secondary-text-w10) uppercase"> | ||
| <Badge variant="outline" className="mt-1 h-4 bg-(--color-secondary-w80) pt-1 text-[8px] leading-none text-(--color-secondary-text-w10) uppercase"> | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| {badge} | ||
| </Badge> | ||
| )} | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This badge was used in 4 places so extracted it to a component.