mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-24 07:19:23 +01:00
chore: adjust list details ui (#128)
* chore: detail * chore: list detail ui * chore: adjust list details ui
This commit is contained in:
@@ -37,7 +37,7 @@ const ChatSwitch: React.FC<ChatSwitchProps> = ({ isChatMode, onChange }) => {
|
|||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={isChatMode}
|
aria-checked={isChatMode}
|
||||||
className={`relative flex items-center justify-between w-10 h-[18px] rounded-full cursor-pointer transition-colors duration-300 ${
|
className={`relative flex items-center justify-between w-10 h-[18px] rounded-full cursor-pointer transition-colors duration-300 ${
|
||||||
isChatMode ? "bg-[#0072ff]" : "bg-[#950599]"
|
isChatMode ? "bg-[#0072ff]" : "bg-[var(--coco-primary-color)]"
|
||||||
}`}
|
}`}
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import {formatter} from "@/utils/index"
|
import { formatter } from "@/utils/index";
|
||||||
import TypeIcon from "@/components/Common/Icons/TypeIcon";
|
import TypeIcon from "@/components/Common/Icons/TypeIcon";
|
||||||
|
|
||||||
interface DocumentDetailProps {
|
interface DocumentDetailProps {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ interface DocumentListProps {
|
|||||||
input: string;
|
input: string;
|
||||||
isChatMode: boolean;
|
isChatMode: boolean;
|
||||||
selectedId?: string;
|
selectedId?: string;
|
||||||
|
viewMode: "detail" | "list";
|
||||||
|
setViewMode: (mode: "detail" | "list") => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = 20;
|
const PAGE_SIZE = 20;
|
||||||
@@ -23,6 +25,8 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||||||
input,
|
input,
|
||||||
getDocDetail,
|
getDocDetail,
|
||||||
isChatMode,
|
isChatMode,
|
||||||
|
viewMode,
|
||||||
|
setViewMode,
|
||||||
}) => {
|
}) => {
|
||||||
const sourceData = useSearchStore((state) => state.sourceData);
|
const sourceData = useSearchStore((state) => state.sourceData);
|
||||||
|
|
||||||
@@ -196,9 +200,17 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||||||
}, [selectedItem]);
|
}, [selectedItem]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[50%] border-r border-gray-200 dark:border-gray-700 flex flex-col h-full">
|
<div
|
||||||
|
className={`border-r border-gray-200 dark:border-gray-700 flex flex-col h-full ${
|
||||||
|
viewMode === "list" ? "w-[100%]" : "w-[50%]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className="px-2 flex-shrink-0">
|
<div className="px-2 flex-shrink-0">
|
||||||
<SearchHeader total={total} />
|
<SearchHeader
|
||||||
|
total={total}
|
||||||
|
viewMode={viewMode}
|
||||||
|
setViewMode={setViewMode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -220,7 +232,7 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||||||
}}
|
}}
|
||||||
className={`w-full px-2 py-2.5 text-sm flex items-center gap-3 rounded-lg transition-colors cursor-pointer ${
|
className={`w-full px-2 py-2.5 text-sm flex items-center gap-3 rounded-lg transition-colors cursor-pointer ${
|
||||||
isSelected
|
isSelected
|
||||||
? "text-white bg-[#950599] hover:bg-[#950599]"
|
? "text-white bg-[var(--coco-primary-color)] hover:bg-[var(--coco-primary-color)]"
|
||||||
: "text-[#333] dark:text-[#d8d8d8]"
|
: "text-[#333] dark:text-[#d8d8d8]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ function DropdownList({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`w-full px-2 py-2.5 text-sm flex gap-7 items-center justify-between rounded-lg transition-colors cursor-pointer ${isSelected
|
className={`w-full px-2 py-2.5 text-sm flex gap-7 items-center justify-between rounded-lg transition-colors cursor-pointer ${isSelected
|
||||||
? "text-white bg-[#950599] hover:bg-[#950599]"
|
? "text-white bg-[var(--coco-primary-color)] hover:bg-[var(--coco-primary-color)]"
|
||||||
: "text-[#333] dark:text-[#d8d8d8]"
|
: "text-[#333] dark:text-[#d8d8d8]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,70 +1,17 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { Menu, MenuButton, MenuItems, MenuItem } from "@headlessui/react";
|
import { AlignLeft, Columns2 } from "lucide-react";
|
||||||
import { ChevronDown } from "lucide-react";
|
|
||||||
|
|
||||||
interface FilterOption {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FilterDropdownProps {
|
|
||||||
label: string;
|
|
||||||
options: FilterOption[];
|
|
||||||
value?: string;
|
|
||||||
onChange: (value: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FilterDropdown: React.FC<FilterDropdownProps> = ({
|
|
||||||
label,
|
|
||||||
options,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Menu as="div" className="relative">
|
|
||||||
<MenuButton className="inline-flex items-center px-2.5 py-1 text-xs bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-700 font-medium">
|
|
||||||
{label}
|
|
||||||
<ChevronDown className="w-3.5 h-3.5 ml-1 text-gray-500 dark:text-gray-400" />
|
|
||||||
</MenuButton>
|
|
||||||
|
|
||||||
<MenuItems className="absolute right-0 mt-1 w-44 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-10 focus:outline-none">
|
|
||||||
{options.map((option) => (
|
|
||||||
<MenuItem key={option.id}>
|
|
||||||
{({ active }) => (
|
|
||||||
<button
|
|
||||||
onClick={() => onChange(option.id)}
|
|
||||||
className={`w-full text-left px-3 py-1.5 text-xs ${
|
|
||||||
active ? "bg-gray-50 dark:bg-gray-700" : ""
|
|
||||||
} ${
|
|
||||||
value === option.id
|
|
||||||
? "text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/50"
|
|
||||||
: "text-gray-700 dark:text-gray-300"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</MenuItems>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeOptions: FilterOption[] = [
|
|
||||||
{ id: "all", label: "All" },
|
|
||||||
{ id: "doc", label: "Doc" },
|
|
||||||
{ id: "image", label: "Image" },
|
|
||||||
{ id: "code", label: "Code" },
|
|
||||||
];
|
|
||||||
|
|
||||||
interface SearchHeaderProps {
|
interface SearchHeaderProps {
|
||||||
total: number;
|
total: number;
|
||||||
|
viewMode: "detail" | "list";
|
||||||
|
setViewMode: (mode: "detail" | "list") => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchHeader: React.FC<SearchHeaderProps> = ({ total }) => {
|
export const SearchHeader: React.FC<SearchHeaderProps> = ({
|
||||||
const [typeFilter, setTypeFilter] = useState("all");
|
total,
|
||||||
|
viewMode,
|
||||||
|
setViewMode,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between py-1">
|
<div className="flex items-center justify-between py-1">
|
||||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
@@ -75,12 +22,28 @@ export const SearchHeader: React.FC<SearchHeaderProps> = ({ total }) => {
|
|||||||
results
|
results
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<FilterDropdown
|
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5">
|
||||||
label="Type"
|
<button
|
||||||
options={typeOptions}
|
onClick={() => setViewMode("list")}
|
||||||
value={typeFilter}
|
className={`p-1 rounded ${
|
||||||
onChange={setTypeFilter}
|
viewMode === "list"
|
||||||
/>
|
? "bg-white dark:bg-gray-700 shadow-sm text-[var(--coco-primary-color)]"
|
||||||
|
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<AlignLeft className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode("detail")}
|
||||||
|
className={`p-1 rounded ${
|
||||||
|
viewMode === "detail"
|
||||||
|
? "bg-white dark:bg-gray-700 shadow-sm text-[var(--coco-primary-color)]"
|
||||||
|
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Columns2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ export function SearchResults({ input, isChatMode }: SearchResultsProps) {
|
|||||||
const [selectedDocumentId, setSelectedDocumentId] = useState("1");
|
const [selectedDocumentId, setSelectedDocumentId] = useState("1");
|
||||||
|
|
||||||
const [detailData, setDetailData] = useState<any>({});
|
const [detailData, setDetailData] = useState<any>({});
|
||||||
|
const [viewMode, setViewMode] = useState<"detail" | "list">("detail");
|
||||||
|
|
||||||
function getDocDetail(detail: any) {
|
function getDocDetail(detail: any) {
|
||||||
setDetailData(detail)
|
setDetailData(detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -27,12 +28,16 @@ export function SearchResults({ input, isChatMode }: SearchResultsProps) {
|
|||||||
input={input}
|
input={input}
|
||||||
getDocDetail={getDocDetail}
|
getDocDetail={getDocDetail}
|
||||||
isChatMode={isChatMode}
|
isChatMode={isChatMode}
|
||||||
|
viewMode={viewMode}
|
||||||
|
setViewMode={setViewMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Right Panel */}
|
{/* Right Panel */}
|
||||||
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
{viewMode === "detail" && (
|
||||||
<DocumentDetail document={detailData} />
|
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
||||||
</div>
|
<DocumentDetail document={detailData} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user