feat(ui): optimize prompt card layout and enhance prompt select dialog for improved usability
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Check, Search } from "lucide-react";
|
||||
import { Search } from "lucide-react";
|
||||
import { type UIEvent, useEffect, useState } from "react";
|
||||
import { App, Empty, Input, Modal, Spin, Tag } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -34,54 +34,50 @@ export function PromptSelectDialog({ open, onOpenChange, onSelect }: { open: boo
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title={t("prompts.library")} open={open} onCancel={() => onOpenChange(false)} footer={null} width={1040} centered>
|
||||
<div data-canvas-no-zoom onWheelCapture={(event) => event.stopPropagation()}>
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<Input size="large" prefix={<Search className="size-4 text-stone-400" />} value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder={t("prompts.searchTitle")} />
|
||||
</div>
|
||||
<div className="mt-5 grid gap-3">
|
||||
<div className="grid gap-2 sm:grid-cols-[56px_minmax(0,1fr)] sm:items-start">
|
||||
<div className="pt-2 text-xs font-medium text-stone-500 dark:text-stone-400">{t("prompts.category")}</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{promptCategories.map((category) => (
|
||||
<Tag.CheckableTag key={category} checked={selectedCategory === category} className={cn("prompt-filter-tag", selectedCategory === category && "is-active")} onChange={() => setSelectedCategory(category)}>
|
||||
{category === ALL_PROMPTS_OPTION ? t("common.all") : category}
|
||||
</Tag.CheckableTag>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2 sm:grid-cols-[56px_minmax(0,1fr)] sm:items-start">
|
||||
<div className="pt-2 text-xs font-medium text-stone-500 dark:text-stone-400">{t("prompts.tags")}</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{promptTags.map((tag) => {
|
||||
const active = tag === ALL_PROMPTS_OPTION ? selectedTags.length === 0 : selectedTags.includes(tag);
|
||||
return (
|
||||
<Tag.CheckableTag key={tag} checked={active} className={cn("prompt-filter-tag", active && "is-active")} onChange={() => toggleTag(tag)}>
|
||||
{tag === ALL_PROMPTS_OPTION ? t("common.all") : tag}
|
||||
</Tag.CheckableTag>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="thin-scrollbar mt-6 max-h-[520px] overflow-y-auto pr-2" data-canvas-no-zoom onScroll={handleListScroll} onWheelCapture={(event) => event.stopPropagation()}>
|
||||
{query.isLoading ? (
|
||||
<div className="flex h-40 items-center justify-center">
|
||||
<Spin />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{items.map((item) => (
|
||||
<PromptCard key={item.id} item={item} onOpen={() => selectPrompt(item.prompt)} onCopy={() => selectPrompt(item.prompt)} actionLabel={t("prompts.use")} actionIcon={<Check className="size-3.5" />} actionType="primary" />
|
||||
<Modal title={t("prompts.library")} open={open} onCancel={() => onOpenChange(false)} footer={null} width={880} centered>
|
||||
<div className="grid h-[62dvh] min-h-0 gap-5 sm:grid-cols-[200px_minmax(0,1fr)]" data-canvas-no-zoom onWheelCapture={(event) => event.stopPropagation()}>
|
||||
<aside className="thin-scrollbar min-h-0 overflow-y-auto border-r border-stone-200 pr-4 dark:border-stone-800">
|
||||
<div className="mb-2 text-xs font-semibold uppercase tracking-widest text-stone-400 dark:text-stone-500">{t("prompts.category")}</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{promptCategories.map((category) => (
|
||||
<Tag.CheckableTag key={category} checked={selectedCategory === category} className={cn("prompt-filter-tag", selectedCategory === category && "is-active")} onChange={() => setSelectedCategory(category)}>
|
||||
{category === ALL_PROMPTS_OPTION ? t("common.all") : category}
|
||||
</Tag.CheckableTag>
|
||||
))}
|
||||
</div>
|
||||
{!query.isLoading && items.length === 0 ? <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("prompts.empty")} className="py-8" /> : null}
|
||||
{query.isFetchingNextPage ? (
|
||||
<div className="py-4 text-center">
|
||||
<Spin size="small" />
|
||||
<div className="mb-2 mt-5 text-xs font-semibold uppercase tracking-widest text-stone-400 dark:text-stone-500">{t("prompts.tags")}</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{promptTags.map((tag) => {
|
||||
const active = tag === ALL_PROMPTS_OPTION ? selectedTags.length === 0 : selectedTags.includes(tag);
|
||||
return (
|
||||
<Tag.CheckableTag key={tag} checked={active} className={cn("prompt-filter-tag", active && "is-active")} onChange={() => toggleTag(tag)}>
|
||||
{tag === ALL_PROMPTS_OPTION ? t("common.all") : tag}
|
||||
</Tag.CheckableTag>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</aside>
|
||||
<section className="flex min-h-0 min-w-0 flex-col">
|
||||
<Input size="large" prefix={<Search className="size-4 text-stone-400" />} value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder={t("prompts.searchTitle")} />
|
||||
<div className="thin-scrollbar mt-4 min-h-0 flex-1 overflow-y-auto pr-2" data-canvas-no-zoom onScroll={handleListScroll} onWheelCapture={(event) => event.stopPropagation()}>
|
||||
{query.isLoading ? (
|
||||
<div className="flex h-40 items-center justify-center">
|
||||
<Spin />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
|
||||
{items.map((item) => (
|
||||
<PromptCard key={item.id} item={item} onOpen={() => selectPrompt(item.prompt)} onCopy={() => selectPrompt(item.prompt)} compact />
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{!query.isLoading && items.length === 0 ? <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("prompts.empty")} className="py-8" /> : null}
|
||||
{query.isFetchingNextPage ? (
|
||||
<div className="py-4 text-center">
|
||||
<Spin size="small" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user