📃 WIP bottom sheet

This commit is contained in:
2026-04-03 10:55:10 -04:00
parent 3e045f4c10
commit 0949ebc637
9 changed files with 1137 additions and 456 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, ReactElement, useCallback } from "react";
import React, { useState, ReactElement, useCallback, useMemo } from "react";
import { useParams } from "react-router-dom";
import Card from "../shared/Carda";
import { RawFileDetails } from "./RawFileDetails";
@@ -141,11 +141,9 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
});
// Query for airdc++
const airDCPPQuery = {
issue: {
name: issueName,
},
};
const airDCPPQuery = useMemo(() => ({
issue: { name: issueName },
}), [issueName]);
// Create tab configuration
const openReconcilePanel = useCallback(() => {
@@ -153,7 +151,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
setVisible(true);
}, []);
const tabGroup = createTabConfig({
const tabGroup = useMemo(() => createTabConfig({
data: data.data,
hasAnyMetadata,
areRawFileDetailsAvailable,
@@ -163,9 +161,9 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
issueName,
acquisition,
onReconcileMetadata: openReconcilePanel,
});
}), [data.data, hasAnyMetadata, areRawFileDetailsAvailable, airDCPPQuery, _id, userSettings, issueName, acquisition, openReconcilePanel]);
const filteredTabs = tabGroup.filter((tab) => tab.shouldShow);
const filteredTabs = useMemo(() => tabGroup.filter((tab) => tab.shouldShow), [tabGroup]);
// Sliding panel content mapping
const renderSlidingPanelContent = () => {

View File

@@ -47,10 +47,12 @@ export const TabControls = (props): ReactElement => {
</nav>
</div>
</div>
<Suspense>
{filteredTabs.map(({ id, content }) => {
return currentActive === id ? content : null;
})}
<Suspense fallback={null}>
{filteredTabs.map(({ id, content }) => (
<React.Fragment key={id}>
{currentActive === id ? content : null}
</React.Fragment>
))}
</Suspense>
</>
);

View File

@@ -131,10 +131,12 @@ export const ArchiveOperations = (props: { data: any }): ReactElement => {
enabled: false,
});
if (isSuccess && shouldRefetchComicBookData) {
queryClient.invalidateQueries({ queryKey: ["comicBookMetadata"] });
setShouldRefetchComicBookData(false);
}
useEffect(() => {
if (isSuccess && shouldRefetchComicBookData) {
queryClient.invalidateQueries({ queryKey: ["comicBookMetadata"] });
setShouldRefetchComicBookData(false);
}
}, [isSuccess, shouldRefetchComicBookData, queryClient]);
// sliding panel init
const contentForSlidingPanel: Record<string, { content: () => JSX.Element }> = {

View File

@@ -1,6 +1,6 @@
import React, { ReactElement, useMemo, useState } from "react";
import { isEmpty, isNil } from "lodash";
import { Sheet } from "react-modal-sheet";
import { Drawer } from "vaul";
import ComicVineDetails from "../ComicVineDetails";
interface ComicVineMetadata {
@@ -60,10 +60,8 @@ const SOURCE_ICONS: Record<string, string> = {
const MetadataSourceChips = ({
sources,
onReconcile,
}: {
sources: string[];
onReconcile: () => void;
}): ReactElement => {
const [isSheetOpen, setSheetOpen] = useState(false);
@@ -98,17 +96,18 @@ const MetadataSourceChips = ({
Reconcile sources
</button>
<Sheet isOpen={isSheetOpen} onClose={() => setSheetOpen(false)}>
<Sheet.Container>
<Sheet.Header />
<Sheet.Content>
<Drawer.Root open={isSheetOpen} onOpenChange={setSheetOpen}>
<Drawer.Portal>
<Drawer.Overlay className="fixed inset-0 bg-black/40" />
<Drawer.Content aria-describedby={undefined} className="fixed bottom-0 left-0 right-0 rounded-t-2xl bg-white dark:bg-slate-800 p-4 outline-none">
<Drawer.Title className="sr-only">Reconcile metadata sources</Drawer.Title>
<div className="mx-auto mb-4 h-1.5 w-12 rounded-full bg-slate-300 dark:bg-slate-600" />
<div className="p-4">
{/* Reconciliation UI goes here */}
</div>
</Sheet.Content>
</Sheet.Container>
<Sheet.Backdrop />
</Sheet>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
</>
);
};
@@ -127,7 +126,7 @@ const MetadataSourceChips = ({
export const VolumeInformation = (
props: VolumeInformationProps,
): ReactElement => {
const { data, onReconcile } = props;
const { data } = props;
const presentSources = useMemo(() => {
const sources = SOURCED_METADATA_KEYS.filter((key) => {
@@ -152,10 +151,7 @@ export const VolumeInformation = (
return (
<div key={1}>
{presentSources.length > 1 && (
<MetadataSourceChips
sources={presentSources}
onReconcile={onReconcile ?? (() => {})}
/>
<MetadataSourceChips sources={presentSources} />
)}
{presentSources.length === 1 &&
data.sourcedMetadata?.comicvine?.volumeInformation && (

View File

@@ -46,7 +46,7 @@ export const createTabConfig = ({
<i className="h-5 w-5 icon-[solar--book-2-bold] text-slate-500 dark:text-slate-300"></i>
),
content: hasAnyMetadata ? (
<VolumeInformation data={data} onReconcile={onReconcileMetadata} key={1} />
<VolumeInformation data={data} onReconcile={onReconcileMetadata} />
) : null,
shouldShow: hasAnyMetadata,
},
@@ -56,7 +56,7 @@ export const createTabConfig = ({
<i className="h-5 w-5 icon-[solar--winrar-bold-duotone] text-slate-500 dark:text-slate-300" />
),
name: "Archive Operations",
content: <ArchiveOperations data={data} key={3} />,
content: <ArchiveOperations data={data} />,
shouldShow: areRawFileDetailsAvailable,
},
{
@@ -71,7 +71,6 @@ export const createTabConfig = ({
comicObjectId={comicObjectId}
comicObject={data}
settings={userSettings}
key={4}
/>
),
shouldShow: true,
@@ -98,7 +97,7 @@ export const createTabConfig = ({
),
content:
!isNil(data) && !isEmpty(data) ? (
<DownloadsPanel key={5} />
<DownloadsPanel />
) : (
<div className="column is-three-fifths">
<article className="message is-info">