mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-04 02:20:19 +01:00
V2 results flow (#4196)
Better tool flow for reusability Pinning Styling of tool flow consumption of files after tooling --------- Co-authored-by: Connor Yoh <connor@stirlingpdf.com> Co-authored-by: James Brunton <jbrunton96@gmail.com>
This commit is contained in:
65
frontend/src/components/shared/filePreview/DocumentStack.tsx
Normal file
65
frontend/src/components/shared/filePreview/DocumentStack.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mantine/core';
|
||||
|
||||
export interface DocumentStackProps {
|
||||
totalFiles: number;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const DocumentStack: React.FC<DocumentStackProps> = ({
|
||||
totalFiles,
|
||||
children
|
||||
}) => {
|
||||
const stackDocumentBaseStyle = {
|
||||
position: 'absolute' as const,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
};
|
||||
|
||||
const stackDocumentShadows = {
|
||||
back: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
middle: '0 3px 10px rgba(0, 0, 0, 0.12)'
|
||||
};
|
||||
|
||||
return (
|
||||
<Box style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||
{/* Background documents (stack effect) */}
|
||||
{totalFiles >= 3 && (
|
||||
<Box
|
||||
style={{
|
||||
...stackDocumentBaseStyle,
|
||||
backgroundColor: 'var(--mantine-color-gray-3)',
|
||||
boxShadow: stackDocumentShadows.back,
|
||||
transform: 'translate(0.75rem, 0.75rem) rotate(2deg)',
|
||||
zIndex: 1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{totalFiles >= 2 && (
|
||||
<Box
|
||||
style={{
|
||||
...stackDocumentBaseStyle,
|
||||
backgroundColor: 'var(--mantine-color-gray-2)',
|
||||
boxShadow: stackDocumentShadows.middle,
|
||||
transform: 'translate(0.375rem, 0.375rem) rotate(1deg)',
|
||||
zIndex: 2
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Main document container */}
|
||||
<Box style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 3,
|
||||
boxShadow: '0 6px 16px rgba(0, 0, 0, 0.2)'
|
||||
}}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentStack;
|
||||
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { Box, Center, Image } from '@mantine/core';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import { FileWithUrl } from '../../../types/file';
|
||||
|
||||
export interface DocumentThumbnailProps {
|
||||
file: File | FileWithUrl | null;
|
||||
thumbnail?: string | null;
|
||||
style?: React.CSSProperties;
|
||||
onClick?: () => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const DocumentThumbnail: React.FC<DocumentThumbnailProps> = ({
|
||||
file,
|
||||
thumbnail,
|
||||
style = {},
|
||||
onClick,
|
||||
children
|
||||
}) => {
|
||||
if (!file) return null;
|
||||
|
||||
const containerStyle = {
|
||||
position: 'relative' as const,
|
||||
cursor: onClick ? 'pointer' : 'default',
|
||||
transition: 'opacity 0.2s ease',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
...style
|
||||
};
|
||||
|
||||
if (thumbnail) {
|
||||
return (
|
||||
<Box style={containerStyle} onClick={onClick}>
|
||||
<Image
|
||||
src={thumbnail}
|
||||
alt={`Preview of ${file.name}`}
|
||||
fit="contain"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||
/>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box style={containerStyle} onClick={onClick}>
|
||||
<Center style={{ width: '100%', height: '100%', backgroundColor: 'var(--mantine-color-gray-1)', borderRadius: '0.25rem' }}>
|
||||
<PictureAsPdfIcon
|
||||
style={{
|
||||
fontSize: '2rem',
|
||||
color: 'var(--mantine-color-gray-6)'
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentThumbnail;
|
||||
63
frontend/src/components/shared/filePreview/HoverOverlay.tsx
Normal file
63
frontend/src/components/shared/filePreview/HoverOverlay.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mantine/core';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
|
||||
export interface HoverOverlayProps {
|
||||
onMouseEnter?: (e: React.MouseEvent) => void;
|
||||
onMouseLeave?: (e: React.MouseEvent) => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const HoverOverlay: React.FC<HoverOverlayProps> = ({
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
children
|
||||
}) => {
|
||||
const defaultMouseEnter = (e: React.MouseEvent) => {
|
||||
const overlay = e.currentTarget.querySelector('.hover-overlay') as HTMLElement;
|
||||
if (overlay) overlay.style.opacity = '1';
|
||||
};
|
||||
|
||||
const defaultMouseLeave = (e: React.MouseEvent) => {
|
||||
const overlay = e.currentTarget.querySelector('.hover-overlay') as HTMLElement;
|
||||
if (overlay) overlay.style.opacity = '0';
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
onMouseEnter={onMouseEnter || defaultMouseEnter}
|
||||
onMouseLeave={onMouseLeave || defaultMouseLeave}
|
||||
>
|
||||
{children}
|
||||
|
||||
{/* Hover overlay */}
|
||||
<Box
|
||||
className="hover-overlay"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
borderRadius: '0.25rem',
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.2s ease',
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
>
|
||||
<VisibilityIcon style={{ color: 'white', fontSize: '1.5rem' }} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default HoverOverlay;
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { Box, ActionIcon } from '@mantine/core';
|
||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
|
||||
export interface NavigationArrowsProps {
|
||||
onPrevious: () => void;
|
||||
onNext: () => void;
|
||||
disabled?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const NavigationArrows: React.FC<NavigationArrowsProps> = ({
|
||||
onPrevious,
|
||||
onNext,
|
||||
disabled = false,
|
||||
children
|
||||
}) => {
|
||||
const navigationArrowStyle = {
|
||||
position: 'absolute' as const,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
zIndex: 10
|
||||
};
|
||||
|
||||
return (
|
||||
<Box style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||
{/* Left Navigation Arrow */}
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
onClick={onPrevious}
|
||||
color="blue"
|
||||
disabled={disabled}
|
||||
style={{
|
||||
...navigationArrowStyle,
|
||||
left: '0'
|
||||
}}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</ActionIcon>
|
||||
|
||||
{/* Content */}
|
||||
<Box style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
{/* Right Navigation Arrow */}
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
onClick={onNext}
|
||||
color="blue"
|
||||
disabled={disabled}
|
||||
style={{
|
||||
...navigationArrowStyle,
|
||||
right: '0'
|
||||
}}
|
||||
>
|
||||
<ChevronRightIcon />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavigationArrows;
|
||||
Reference in New Issue
Block a user