fix divider css

This commit is contained in:
2026-03-18 10:42:25 +08:00
parent 5a470c146a
commit d03d1e75c2
3 changed files with 75 additions and 65 deletions

View File

@@ -19,13 +19,9 @@ interface FileItemProps {
onView?: () => void; onView?: () => void;
onDownload?: () => void; onDownload?: () => void;
isDownloading?: boolean; isDownloading?: boolean;
showDivider?: boolean;
} }
const useStyles = createStyles(({ token, css }) => ({ const useStyles = createStyles(({ token, css }) => ({
wrapper: css`
width: 100%;
`,
item: css` item: css`
display: flex; display: flex;
align-items: center; align-items: center;
@@ -34,16 +30,12 @@ const useStyles = createStyles(({ token, css }) => ({
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
transition: background-color 0.15s ease; transition: background-color 0.15s ease;
position: relative;
&:hover { &:hover {
background-color: ${token.colorBgTextHover}; background-color: ${token.colorBgTextHover};
} }
`, `,
divider: css`
height: 1px;
background-color: ${token.colorBorder};
margin: 0 ${token.paddingMD}px;
`,
fileInfo: css` fileInfo: css`
display: flex; display: flex;
align-items: center; align-items: center;
@@ -75,7 +67,7 @@ const formatFileSize = (size: number | undefined): string => {
return `${(size / (1024 * 1024)).toFixed(1)} MB`; return `${(size / (1024 * 1024)).toFixed(1)} MB`;
}; };
const FileItem: React.FC<FileItemProps> = ({ entry, onDelete, onRestore, onView, onDownload, isDownloading, showDivider = true }) => { const FileItem: React.FC<FileItemProps> = ({ entry, onDelete, onRestore, onView, onDownload, isDownloading }) => {
const { t } = useTranslation(["app", "common"]); const { t } = useTranslation(["app", "common"]);
const { styles, cx } = useStyles(); const { styles, cx } = useStyles();
const token = useTheme(); const token = useTheme();
@@ -103,52 +95,47 @@ const FileItem: React.FC<FileItemProps> = ({ entry, onDelete, onRestore, onView,
}; };
return ( return (
<div className={styles.wrapper}> <div className={styles.item} onClick={handleItemClick} data-file-item="true">
<div className={styles.item} onClick={handleItemClick}> <div className={styles.fileInfo}>
<div className={styles.fileInfo}> <div onClick={handleDragHandleClick}>
<div onClick={handleDragHandleClick}> <SortableList.DragHandle />
<SortableList.DragHandle />
</div>
<MaterialFileTypeIcon type="file" filename={`file.${entry.fileType}`} size={16} />
<span
className={cx(styles.fileName, entry.status === "deleted" && styles.fileNameDeleted)}
style={entry.status !== "unchanged" ? { color: statusColor[entry.status] } : undefined}
>
{entry.fileName}
</span>
{entry.status === "added" && (
<Badge color={token.colorSuccess} text={t("statusAdded")} style={{ fontSize: token.fontSizeSM, whiteSpace: "nowrap" }} />
)}
{entry.status === "deleted" && (
<Badge color={token.colorError} text={t("statusDeleted")} style={{ fontSize: token.fontSizeSM, whiteSpace: "nowrap" }} />
)}
{entry.status === "reordered" && (
<Badge color={token.colorWarning} text={t("statusReordered")} style={{ fontSize: token.fontSizeSM, whiteSpace: "nowrap" }} />
)}
</div> </div>
<MaterialFileTypeIcon type="file" filename={`file.${entry.fileType}`} size={16} />
<Space> <span
{entry.size && <span className={styles.fileSize}>{formatFileSize(entry.size)}</span>} className={cx(styles.fileName, entry.status === "deleted" && styles.fileNameDeleted)}
style={entry.status !== "unchanged" ? { color: statusColor[entry.status] } : undefined}
{entry.status === "deleted" ? ( >
<Button type="text" size="small" icon={<Undo2 size={16} />} onClick={(e) => handleButtonClick(e, onRestore)}> {entry.fileName}
{t("restore")} </span>
</Button> {entry.status === "added" && <Badge color={token.colorSuccess} text={t("statusAdded")} style={{ fontSize: token.fontSizeSM, whiteSpace: "nowrap" }} />}
) : ( {entry.status === "deleted" && (
<> <Badge color={token.colorError} text={t("statusDeleted")} style={{ fontSize: token.fontSizeSM, whiteSpace: "nowrap" }} />
{(entry.status === "unchanged" || entry.status === "reordered") && onDownload && entry.fileKey && ( )}
<Tooltip title={t("downloadFile", { ns: "common" })}> {entry.status === "reordered" && (
<Button type="text" size="small" icon={<Download size={16} />} loading={isDownloading} onClick={(e) => handleButtonClick(e, onDownload!)} /> <Badge color={token.colorWarning} text={t("statusReordered")} style={{ fontSize: token.fontSizeSM, whiteSpace: "nowrap" }} />
</Tooltip> )}
)}
<Tooltip title={t("deleteFile", { ns: "common" })}>
<Button type="text" size="small" danger icon={<Trash2 size={16} />} onClick={(e) => handleButtonClick(e, onDelete)} />
</Tooltip>
</>
)}
</Space>
</div> </div>
{showDivider && <div className={styles.divider} />}
<Space>
{entry.size && <span className={styles.fileSize}>{formatFileSize(entry.size)}</span>}
{entry.status === "deleted" ? (
<Button type="text" size="small" icon={<Undo2 size={16} />} onClick={(e) => handleButtonClick(e, onRestore)}>
{t("restore")}
</Button>
) : (
<>
{(entry.status === "unchanged" || entry.status === "reordered") && onDownload && entry.fileKey && (
<Tooltip title={t("downloadFile", { ns: "common" })}>
<Button type="text" size="small" icon={<Download size={16} />} loading={isDownloading} onClick={(e) => handleButtonClick(e, onDownload!)} />
</Tooltip>
)}
<Tooltip title={t("deleteFile", { ns: "common" })}>
<Button type="text" size="small" danger icon={<Trash2 size={16} />} onClick={(e) => handleButtonClick(e, onDelete)} />
</Tooltip>
</>
)}
</Space>
</div> </div>
); );
}; };

View File

@@ -287,10 +287,9 @@ const FileSection: React.FC<FileSectionProps> = ({
// ── Render item ──────────────────────────────────────────────────────────── // ── Render item ────────────────────────────────────────────────────────────
const renderItem = useCallback( const renderItem = useCallback(
(entry: FileEntry, index: number, totalCount: number) => { (entry: FileEntry) => {
// Determine if file is viewable (has fileKey for Kintone files OR storagePath for local files) // Determine if file is viewable (has fileKey for Kintone files OR storagePath for local files)
const isViewable = entry.fileKey || entry.storagePath; const isViewable = entry.fileKey || entry.storagePath;
const isLast = index === totalCount - 1;
return ( return (
<FileItem <FileItem
@@ -300,7 +299,6 @@ const FileSection: React.FC<FileSectionProps> = ({
onView={isViewable ? () => onView(entry.fileKey, entry.fileName, entry.storagePath) : undefined} onView={isViewable ? () => onView(entry.fileKey, entry.fileName, entry.storagePath) : undefined}
onDownload={entry.fileKey ? () => onDownload(entry.fileKey!, entry.fileName) : undefined} onDownload={entry.fileKey ? () => onDownload(entry.fileKey!, entry.fileName) : undefined}
isDownloading={downloadingKey === entry.fileKey} isDownloading={downloadingKey === entry.fileKey}
showDivider={!isLast}
/> />
); );
}, },
@@ -367,7 +365,7 @@ const FileSection: React.FC<FileSectionProps> = ({
{files.length === 0 ? ( {files.length === 0 ? (
<div className={styles.emptySection}>{t("noConfig")}</div> <div className={styles.emptySection}>{t("noConfig")}</div>
) : ( ) : (
<SortableFileList items={files} onReorder={handleReorder} renderItem={renderItem} /> <SortableFileList items={files} onReorder={handleReorder} renderItem={renderItem} dividerColor={token.colorBorder} />
)} )}
{/* Click-to-add strip */} {/* Click-to-add strip */}

View File

@@ -10,16 +10,39 @@ import { SortableList } from "@lobehub/ui";
import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from "@dnd-kit/core"; import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy, arrayMove, sortableKeyboardCoordinates } from "@dnd-kit/sortable"; import { SortableContext, verticalListSortingStrategy, arrayMove, sortableKeyboardCoordinates } from "@dnd-kit/sortable";
import { restrictToVerticalAxis, restrictToWindowEdges } from "@dnd-kit/modifiers"; import { restrictToVerticalAxis, restrictToWindowEdges } from "@dnd-kit/modifiers";
import { createStyles } from "antd-style";
import type { FileEntry } from "@renderer/stores"; import type { FileEntry } from "@renderer/stores";
const useStyles = createStyles(({ css }) => ({
fileList: css`
display: flex;
flex-direction: column;
width: 100%;
/* 分割线:非最后一个子元素的 .fileItem 显示 */
& > *:not(:last-child) [data-file-item="true"]::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background-color: var(--divider-color, #d9d9d9);
}
`,
}));
// ── SortableFileList Component ──────────────────────────────────────────────── // ── SortableFileList Component ────────────────────────────────────────────────
interface SortableFileListProps { interface SortableFileListProps {
items: FileEntry[]; items: FileEntry[];
onReorder: (newOrder: string[], draggedItemId: string) => void; onReorder: (newOrder: string[], draggedItemId: string) => void;
renderItem: (entry: FileEntry, index: number, totalCount: number) => React.ReactNode; renderItem: (entry: FileEntry) => React.ReactNode;
dividerColor?: string;
} }
const SortableFileList: React.FC<SortableFileListProps> = ({ items, onReorder, renderItem }) => { const SortableFileList: React.FC<SortableFileListProps> = ({ items, onReorder, renderItem, dividerColor }) => {
const { styles } = useStyles();
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor), useSensor(PointerSensor),
useSensor(KeyboardSensor, { useSensor(KeyboardSensor, {
@@ -45,11 +68,13 @@ const SortableFileList: React.FC<SortableFileListProps> = ({ items, onReorder, r
return ( return (
<DndContext sensors={sensors} collisionDetection={undefined} onDragEnd={handleDragEnd} modifiers={[restrictToVerticalAxis, restrictToWindowEdges]}> <DndContext sensors={sensors} collisionDetection={undefined} onDragEnd={handleDragEnd} modifiers={[restrictToVerticalAxis, restrictToWindowEdges]}>
<SortableContext items={items} strategy={verticalListSortingStrategy}> <SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map((entry, index) => ( <div className={styles.fileList} style={dividerColor ? ({ "--divider-color": dividerColor } as React.CSSProperties) : undefined}>
<SortableList.Item key={entry.id} id={entry.id}> {items.map((entry) => (
{renderItem(entry, index, items.length)} <SortableList.Item key={entry.id} id={entry.id}>
</SortableList.Item> {renderItem(entry)}
))} </SortableList.Item>
))}
</div>
</SortableContext> </SortableContext>
</DndContext> </DndContext>
); );