mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-07-27 07:35:22 +00:00
Css fixes, Tailwind forward
This commit is contained in:
parent
b1584151ba
commit
1e0949ff2b
@ -4,9 +4,7 @@ import { mantineTheme } from './theme/mantineTheme';
|
|||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
|
|
||||||
// Import global styles
|
// Import global styles
|
||||||
import './styles/theme.css';
|
|
||||||
import './styles/tailwind.css';
|
import './styles/tailwind.css';
|
||||||
import './styles/components.css';
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
@ -12,7 +12,7 @@ import { Group } from "@mantine/core";
|
|||||||
const VIEW_OPTIONS = [
|
const VIEW_OPTIONS = [
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<Group gap={4}>
|
<Group gap={5}>
|
||||||
<VisibilityIcon fontSize="small" />
|
<VisibilityIcon fontSize="small" />
|
||||||
</Group>
|
</Group>
|
||||||
),
|
),
|
||||||
@ -48,8 +48,8 @@ const TopControls: React.FC<TopControlsProps> = ({
|
|||||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute left-0 w-full top-0 z-sticky pointer-events-none">
|
<div className="absolute left-0 w-full top-0 z-[9999] pointer-events-none">
|
||||||
<div className="absolute left-app-md top-1/2 -translate-y-1/2 pointer-events-auto flex gap-app-sm items-center">
|
<div className="absolute left-4 top-1/2 -translate-y-1/2 pointer-events-auto flex gap-2 items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={toggleColorScheme}
|
onClick={toggleColorScheme}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
@ -61,7 +61,6 @@ const TopControls: React.FC<TopControlsProps> = ({
|
|||||||
<LanguageSelector />
|
<LanguageSelector />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center items-center h-full pointer-events-auto">
|
<div className="flex justify-center items-center h-full pointer-events-auto">
|
||||||
<div className="bg-bg-overlay backdrop-blur-sm rounded-app-xl p-app-sm">
|
|
||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
data={VIEW_OPTIONS}
|
data={VIEW_OPTIONS}
|
||||||
value={currentView}
|
value={currentView}
|
||||||
@ -71,10 +70,9 @@ const TopControls: React.FC<TopControlsProps> = ({
|
|||||||
size="md"
|
size="md"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TopControls;
|
export default TopControls;
|
||||||
|
@ -25,8 +25,8 @@ interface LazyPageImageProps {
|
|||||||
setPageRef: (index: number, ref: HTMLImageElement | null) => void;
|
setPageRef: (index: number, ref: HTMLImageElement | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LazyPageImage: React.FC<LazyPageImageProps> = ({
|
const LazyPageImage: React.FC<LazyPageImageProps> = ({
|
||||||
pageIndex, zoom, theme, isFirst, renderPage, pageImages, setPageRef
|
pageIndex, zoom, theme, isFirst, renderPage, pageImages, setPageRef
|
||||||
}) => {
|
}) => {
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const [imageUrl, setImageUrl] = useState<string | null>(pageImages[pageIndex]);
|
const [imageUrl, setImageUrl] = useState<string | null>(pageImages[pageIndex]);
|
||||||
@ -41,9 +41,9 @@ const LazyPageImage: React.FC<LazyPageImageProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rootMargin: '200px', // Start loading 200px before visible
|
rootMargin: '200px', // Start loading 200px before visible
|
||||||
threshold: 0.1
|
threshold: 0.1
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -104,10 +104,10 @@ const LazyPageImage: React.FC<LazyPageImageProps> = ({
|
|||||||
>
|
>
|
||||||
{isVisible ? (
|
{isVisible ? (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
border: '2px solid #ddd',
|
border: '2px solid #ddd',
|
||||||
borderTop: '2px solid #666',
|
borderTop: '2px solid #666',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
animation: 'spin 1s linear infinite',
|
animation: 'spin 1s linear infinite',
|
||||||
@ -163,7 +163,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderingPagesRef.current.add(pageIndex);
|
renderingPagesRef.current.add(pageIndex);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const page = await pdfDocRef.current.getPage(pageNum);
|
const page = await pdfDocRef.current.getPage(pageNum);
|
||||||
const viewport = page.getViewport({ scale: 1.2 });
|
const viewport = page.getViewport({ scale: 1.2 });
|
||||||
@ -171,25 +171,25 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
canvas.width = viewport.width;
|
canvas.width = viewport.width;
|
||||||
canvas.height = viewport.height;
|
canvas.height = viewport.height;
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||||
const dataUrl = canvas.toDataURL();
|
const dataUrl = canvas.toDataURL();
|
||||||
|
|
||||||
// Update the pageImages array
|
// Update the pageImages array
|
||||||
setPageImages(prev => {
|
setPageImages(prev => {
|
||||||
const newImages = [...prev];
|
const newImages = [...prev];
|
||||||
newImages[pageIndex] = dataUrl;
|
newImages[pageIndex] = dataUrl;
|
||||||
return newImages;
|
return newImages;
|
||||||
});
|
});
|
||||||
|
|
||||||
renderingPagesRef.current.delete(pageIndex);
|
renderingPagesRef.current.delete(pageIndex);
|
||||||
return dataUrl;
|
return dataUrl;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to render page ${pageNum}:`, error);
|
console.error(`Failed to render page ${pageNum}:`, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderingPagesRef.current.delete(pageIndex);
|
renderingPagesRef.current.delete(pageIndex);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@ -284,21 +284,21 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
let pdfUrl = pdfFile.url;
|
let pdfUrl = pdfFile.url;
|
||||||
|
|
||||||
// Handle special IndexedDB URLs for large files
|
// Handle special IndexedDB URLs for large files
|
||||||
if (pdfFile.url.startsWith('indexeddb:')) {
|
if (pdfFile.url.startsWith('indexeddb:')) {
|
||||||
const fileId = pdfFile.url.replace('indexeddb:', '');
|
const fileId = pdfFile.url.replace('indexeddb:', '');
|
||||||
console.log('Loading large file from IndexedDB:', fileId);
|
console.log('Loading large file from IndexedDB:', fileId);
|
||||||
|
|
||||||
// Get data directly from IndexedDB
|
// Get data directly from IndexedDB
|
||||||
const arrayBuffer = await fileStorage.getFileData(fileId);
|
const arrayBuffer = await fileStorage.getFileData(fileId);
|
||||||
if (!arrayBuffer) {
|
if (!arrayBuffer) {
|
||||||
throw new Error('File not found in IndexedDB - may have been purged by browser');
|
throw new Error('File not found in IndexedDB - may have been purged by browser');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store reference for cleanup
|
// Store reference for cleanup
|
||||||
currentArrayBufferRef.current = arrayBuffer;
|
currentArrayBufferRef.current = arrayBuffer;
|
||||||
|
|
||||||
// Use ArrayBuffer directly instead of creating blob URL
|
// Use ArrayBuffer directly instead of creating blob URL
|
||||||
const pdf = await getDocument({ data: arrayBuffer }).promise;
|
const pdf = await getDocument({ data: arrayBuffer }).promise;
|
||||||
pdfDocRef.current = pdf;
|
pdfDocRef.current = pdf;
|
||||||
@ -321,8 +321,8 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
if (!cancelled) setLoading(false);
|
if (!cancelled) setLoading(false);
|
||||||
}
|
}
|
||||||
loadPdfInfo();
|
loadPdfInfo();
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
// Cleanup ArrayBuffer reference to help garbage collection
|
// Cleanup ArrayBuffer reference to help garbage collection
|
||||||
currentArrayBufferRef.current = null;
|
currentArrayBufferRef.current = null;
|
||||||
};
|
};
|
||||||
@ -339,16 +339,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
}, [pageImages]);
|
}, [pageImages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<>
|
||||||
shadow="xs"
|
|
||||||
radius="md"
|
|
||||||
style={{
|
|
||||||
height: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
position: "relative",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!pdfFile ? (
|
{!pdfFile ? (
|
||||||
<Center style={{ flex: 1 }}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Stack align="center">
|
<Stack align="center">
|
||||||
@ -586,7 +577,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</Paper>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { ColorSchemeScript, MantineProvider, mantineHtmlProps } from '@mantine/core';
|
import { ColorSchemeScript, MantineProvider, mantineHtmlProps } from '@mantine/core';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import './index.css';
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './i18n'; // Initialize i18next
|
import './i18n'; // Initialize i18next
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ import PageEditor from "../components/PageEditor";
|
|||||||
import Viewer from "../components/Viewer";
|
import Viewer from "../components/Viewer";
|
||||||
import TopControls from "../components/TopControls";
|
import TopControls from "../components/TopControls";
|
||||||
import ToolRenderer from "../components/ToolRenderer";
|
import ToolRenderer from "../components/ToolRenderer";
|
||||||
import styles from "../styles/HomePage.module.css";
|
|
||||||
|
|
||||||
type ToolRegistryEntry = {
|
type ToolRegistryEntry = {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
@ -55,7 +54,6 @@ export default function HomePage() {
|
|||||||
// URL parameter management
|
// URL parameter management
|
||||||
const { toolParams, updateParams } = useToolParams(selectedToolKey, currentView);
|
const { toolParams, updateParams } = useToolParams(selectedToolKey, currentView);
|
||||||
|
|
||||||
// Create translated tool registry
|
|
||||||
const toolRegistry: ToolRegistry = {
|
const toolRegistry: ToolRegistry = {
|
||||||
split: { ...baseToolRegistry.split, name: t("home.split.title", "Split PDF") },
|
split: { ...baseToolRegistry.split, name: t("home.split.title", "Split PDF") },
|
||||||
compress: { ...baseToolRegistry.compress, name: t("home.compressPdfs.title", "Compress PDF") },
|
compress: { ...baseToolRegistry.compress, name: t("home.compressPdfs.title", "Compress PDF") },
|
||||||
@ -82,32 +80,27 @@ export default function HomePage() {
|
|||||||
>
|
>
|
||||||
{/* Left: Tool Picker */}
|
{/* Left: Tool Picker */}
|
||||||
{sidebarsVisible && (
|
{sidebarsVisible && (
|
||||||
<Box
|
<div
|
||||||
className={`${styles.leftSidebar} h-screen z-sticky flex flex-col bg-bg-surface border-r border-border-subtle`}
|
className="h-screen z-sticky flex flex-col bg-surface border-r border-border min-w-[180px] max-w-[240px] w-[16vw]"
|
||||||
|
style={{ padding: '1rem' }}
|
||||||
>
|
>
|
||||||
<ToolPicker
|
<ToolPicker
|
||||||
selectedToolKey={selectedToolKey}
|
selectedToolKey={selectedToolKey}
|
||||||
onSelect={handleToolSelect}
|
onSelect={handleToolSelect}
|
||||||
toolRegistry={toolRegistry}
|
toolRegistry={toolRegistry}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Middle: Main View */}
|
{/* Middle: Main View */}
|
||||||
<Box className="flex-1 h-screen min-w-80 relative flex flex-col transition-all duration-300 bg-bg-app">
|
<Box className="flex-1 h-screen min-w-80 relative flex flex-col transition-all duration-300 bg-background">
|
||||||
{/* Top Controls */}
|
{/* Top Controls */}
|
||||||
<TopControls
|
<TopControls
|
||||||
currentView={currentView}
|
currentView={currentView}
|
||||||
setCurrentView={setCurrentView}
|
setCurrentView={setCurrentView}
|
||||||
/>
|
/>
|
||||||
{/* Main content area */}
|
{/* Main content area */}
|
||||||
<Paper
|
<Box className="flex-1 min-h-0 margin-top-200 relative z-10">
|
||||||
radius="0 0 xl xl"
|
|
||||||
shadow="sm"
|
|
||||||
p={0}
|
|
||||||
className="flex-1 min-h-0 mt-0 box-border overflow-hidden flex flex-col"
|
|
||||||
>
|
|
||||||
<Box className="flex-1 min-h-0">
|
|
||||||
{(currentView === "viewer" || currentView === "pageEditor") && !pdfFile ? (
|
{(currentView === "viewer" || currentView === "pageEditor") && !pdfFile ? (
|
||||||
<FileManager
|
<FileManager
|
||||||
files={files}
|
files={files}
|
||||||
@ -138,13 +131,13 @@ export default function HomePage() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Right: Tool Interaction */}
|
{/* Right: Tool Interaction */}
|
||||||
{sidebarsVisible && (
|
{sidebarsVisible && (
|
||||||
<Box
|
<div
|
||||||
className={`${styles.rightSidebar} h-screen bg-bg-surface border-l border-border-subtle p-app-lg gap-app-md z-sticky flex flex-col`}
|
className="h-screen bg-surface border-l border-border gap-6 z-sticky flex flex-col min-w-[260px] max-w-[400px] w-[22vw]"
|
||||||
|
style={{ padding: '1.5rem' }}
|
||||||
>
|
>
|
||||||
<ToolRenderer
|
<ToolRenderer
|
||||||
selectedToolKey={selectedToolKey}
|
selectedToolKey={selectedToolKey}
|
||||||
@ -156,19 +149,8 @@ export default function HomePage() {
|
|||||||
toolParams={toolParams}
|
toolParams={toolParams}
|
||||||
updateParams={updateParams}
|
updateParams={updateParams}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Sidebar toggle button */}
|
|
||||||
<Button
|
|
||||||
variant="light"
|
|
||||||
color="blue"
|
|
||||||
size="xs"
|
|
||||||
className="fixed top-app-md right-app-md z-fixed"
|
|
||||||
onClick={() => setSidebarsVisible((v) => !v)}
|
|
||||||
>
|
|
||||||
{t("sidebar.toggle", sidebarsVisible ? "Hide Sidebars" : "Show Sidebars")}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
/* Use Tailwind for most styles, keep only complex layouts in CSS modules */
|
|
||||||
|
|
||||||
.leftSidebar {
|
|
||||||
min-width: var(--sidebar-width-min);
|
|
||||||
max-width: var(--sidebar-width-max);
|
|
||||||
width: var(--sidebar-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightSidebar {
|
|
||||||
min-width: 260px;
|
|
||||||
max-width: 400px;
|
|
||||||
width: 22vw;
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
/* Custom component styles that don't fit Tailwind patterns */
|
|
||||||
|
|
||||||
/* Mantine component overrides */
|
|
||||||
.mantine-override {
|
|
||||||
/* Custom overrides for Mantine components when needed */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* PDF-specific custom components */
|
|
||||||
.pdf-page-thumbnail {
|
|
||||||
aspect-ratio: 3/4;
|
|
||||||
background: var(--bg-surface);
|
|
||||||
border: 1px solid var(--border-subtle);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-page-thumbnail:hover {
|
|
||||||
border-color: var(--color-primary-500);
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-page-thumbnail img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading spinner for PDF operations */
|
|
||||||
.pdf-loading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 200px;
|
|
||||||
background: var(--bg-surface);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom scrollbar for PDF viewer */
|
|
||||||
.pdf-viewer-container {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: var(--border-strong) var(--bg-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-viewer-container::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-viewer-container::-webkit-scrollbar-track {
|
|
||||||
background: var(--bg-muted);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-viewer-container::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--border-strong);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-viewer-container::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--color-primary-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File upload drag and drop animations */
|
|
||||||
.file-drop-zone.drag-over {
|
|
||||||
animation: pulse 1s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.02);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Progress bar for operations */
|
|
||||||
.progress-bar {
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
background: var(--bg-muted);
|
|
||||||
border-radius: var(--radius-full);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: var(--color-primary-500);
|
|
||||||
border-radius: var(--radius-full);
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tool-specific layouts that need custom CSS */
|
|
||||||
.split-preview-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: var(--space-md);
|
|
||||||
padding: var(--space-md);
|
|
||||||
background: var(--bg-surface);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
border: 1px solid var(--border-subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Error states */
|
|
||||||
.error-state {
|
|
||||||
padding: var(--space-lg);
|
|
||||||
text-align: center;
|
|
||||||
background: var(--bg-surface);
|
|
||||||
border: 1px solid var(--color-error);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
color: var(--color-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Success states */
|
|
||||||
.success-state {
|
|
||||||
padding: var(--space-lg);
|
|
||||||
text-align: center;
|
|
||||||
background: var(--bg-surface);
|
|
||||||
border: 1px solid var(--color-success);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
color: var(--color-success);
|
|
||||||
}
|
|
@ -1,66 +1,7 @@
|
|||||||
|
|
||||||
|
/* Import minimal theme variables */
|
||||||
|
@import './theme.css';
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
/* Custom component classes using Tailwind (with app- prefix to avoid Mantine conflicts) */
|
|
||||||
@layer components {
|
|
||||||
.app-card {
|
|
||||||
@apply bg-bg-surface border border-border-subtle rounded-app-lg p-app-lg shadow-app-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-surface {
|
|
||||||
@apply bg-bg-surface border border-border-subtle rounded-app-md shadow-app-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-raised {
|
|
||||||
@apply bg-bg-raised shadow-app-sm rounded-app-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-interactive {
|
|
||||||
@apply cursor-pointer transition-all duration-200 hover:bg-hover-bg active:bg-active-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-file-drop-zone {
|
|
||||||
@apply border-2 border-dashed border-file-drop rounded-app-lg bg-bg-surface transition-all duration-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-file-drop-zone:hover,
|
|
||||||
.app-file-drop-zone.drag-over {
|
|
||||||
@apply border-app-primary-500 bg-file-drop-hover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* PDF-specific component classes */
|
|
||||||
.app-pdf-viewer-bg {
|
|
||||||
@apply bg-pdf-viewer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-pdf-toolbar {
|
|
||||||
@apply bg-pdf-toolbar border-b border-border-subtle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button variants */
|
|
||||||
.app-btn-pdf-tool {
|
|
||||||
@apply bg-bg-surface border border-border-default text-text-primary px-app-md py-app-sm rounded-app-md font-medium transition-all duration-200 hover:bg-hover-bg hover:border-app-primary-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Focus styles */
|
|
||||||
.app-focus-ring {
|
|
||||||
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom utilities */
|
|
||||||
@layer utilities {
|
|
||||||
.text-balance {
|
|
||||||
text-wrap: balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar-hide {
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar-hide::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,24 @@
|
|||||||
/* Design System CSS Variables - Single Source of Truth */
|
/* CSS variables for Tailwind + Mantine integration */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Color System */
|
/* Standard gray scale */
|
||||||
|
--gray-50: 249 250 251;
|
||||||
|
--gray-100: 243 244 246;
|
||||||
|
--gray-200: 229 231 235;
|
||||||
|
--gray-300: 209 213 219;
|
||||||
|
--gray-400: 156 163 175;
|
||||||
|
--gray-500: 107 114 128;
|
||||||
|
--gray-600: 75 85 99;
|
||||||
|
--gray-700: 55 65 81;
|
||||||
|
--gray-800: 31 41 55;
|
||||||
|
--gray-900: 17 24 39;
|
||||||
|
|
||||||
|
/* Semantic colors for Tailwind */
|
||||||
|
--surface: 255 255 255;
|
||||||
|
--background: 249 250 251;
|
||||||
|
--border: 229 231 235;
|
||||||
|
|
||||||
|
/* Colors for Mantine integration */
|
||||||
--color-primary-50: #eff6ff;
|
--color-primary-50: #eff6ff;
|
||||||
--color-primary-100: #dbeafe;
|
--color-primary-100: #dbeafe;
|
||||||
--color-primary-200: #bfdbfe;
|
--color-primary-200: #bfdbfe;
|
||||||
@ -24,123 +41,91 @@
|
|||||||
--color-gray-800: #1f2937;
|
--color-gray-800: #1f2937;
|
||||||
--color-gray-900: #111827;
|
--color-gray-900: #111827;
|
||||||
|
|
||||||
/* Semantic Colors */
|
/* Spacing system */
|
||||||
--color-success: #10b981;
|
|
||||||
--color-warning: #f59e0b;
|
|
||||||
--color-error: #ef4444;
|
|
||||||
--color-info: #3b82f6;
|
|
||||||
|
|
||||||
/* Spacing System */
|
|
||||||
--space-xs: 4px;
|
--space-xs: 4px;
|
||||||
--space-sm: 8px;
|
--space-sm: 8px;
|
||||||
--space-md: 16px;
|
--space-md: 16px;
|
||||||
--space-lg: 24px;
|
--space-lg: 24px;
|
||||||
--space-xl: 32px;
|
--space-xl: 32px;
|
||||||
--space-2xl: 48px;
|
|
||||||
|
|
||||||
/* Radius System */
|
/* Radius system */
|
||||||
--radius-xs: 2px;
|
--radius-xs: 2px;
|
||||||
--radius-sm: 4px;
|
--radius-sm: 4px;
|
||||||
--radius-md: 8px;
|
--radius-md: 8px;
|
||||||
--radius-lg: 12px;
|
--radius-lg: 12px;
|
||||||
--radius-xl: 16px;
|
--radius-xl: 16px;
|
||||||
--radius-2xl: 24px;
|
|
||||||
--radius-full: 9999px;
|
|
||||||
|
|
||||||
/* Shadow System */
|
/* Shadow system */
|
||||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
|
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
|
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
/* Layout */
|
/* Font weights */
|
||||||
--sidebar-width-min: 180px;
|
|
||||||
--sidebar-width-max: 240px;
|
|
||||||
--sidebar-width: 16vw;
|
|
||||||
--header-height: 60px;
|
|
||||||
--border-width: 1px;
|
|
||||||
|
|
||||||
/* Typography */
|
|
||||||
--font-weight-normal: 400;
|
--font-weight-normal: 400;
|
||||||
--font-weight-medium: 500;
|
--font-weight-medium: 500;
|
||||||
--font-weight-semibold: 600;
|
--font-weight-semibold: 600;
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
|
|
||||||
/* Z-Index Scale */
|
/* Light theme semantic colors */
|
||||||
--z-dropdown: 1000;
|
|
||||||
--z-sticky: 1020;
|
|
||||||
--z-fixed: 1030;
|
|
||||||
--z-modal-backdrop: 1040;
|
|
||||||
--z-modal: 1050;
|
|
||||||
--z-popover: 1060;
|
|
||||||
--z-tooltip: 1070;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Light Theme */
|
|
||||||
:root, [data-mantine-color-scheme="light"] {
|
|
||||||
/* Background Colors */
|
|
||||||
--bg-app: var(--color-gray-50);
|
|
||||||
--bg-surface: #ffffff;
|
--bg-surface: #ffffff;
|
||||||
--bg-raised: var(--color-gray-50);
|
--bg-raised: #f9fafb;
|
||||||
--bg-overlay: rgba(255, 255, 255, 0.8);
|
--bg-muted: #f3f4f6;
|
||||||
--bg-muted: var(--color-gray-100);
|
--text-primary: #111827;
|
||||||
|
--text-secondary: #4b5563;
|
||||||
/* Text Colors */
|
--text-muted: #6b7280;
|
||||||
--text-primary: var(--color-gray-900);
|
--border-subtle: #e5e7eb;
|
||||||
--text-secondary: var(--color-gray-600);
|
--border-default: #d1d5db;
|
||||||
--text-muted: var(--color-gray-500);
|
--border-strong: #9ca3af;
|
||||||
--text-inverse: #ffffff;
|
--hover-bg: #f9fafb;
|
||||||
|
--active-bg: #f3f4f6;
|
||||||
/* Border Colors */
|
|
||||||
--border-subtle: var(--color-gray-200);
|
|
||||||
--border-default: var(--color-gray-300);
|
|
||||||
--border-strong: var(--color-gray-400);
|
|
||||||
|
|
||||||
/* Interactive States */
|
|
||||||
--hover-bg: var(--color-gray-50);
|
|
||||||
--active-bg: var(--color-gray-100);
|
|
||||||
--focus-ring: var(--color-primary-500);
|
|
||||||
|
|
||||||
/* PDF Tool Specific */
|
|
||||||
--pdf-viewer-bg: #f8fafc;
|
|
||||||
--pdf-toolbar-bg: var(--bg-surface);
|
|
||||||
--file-drop-border: var(--color-gray-300);
|
|
||||||
--file-drop-hover: var(--color-primary-100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark Theme */
|
|
||||||
[data-mantine-color-scheme="dark"] {
|
[data-mantine-color-scheme="dark"] {
|
||||||
/* Background Colors */
|
/* Dark theme gray scale (inverted) */
|
||||||
--bg-app: var(--color-gray-900);
|
--gray-50: 17 24 39;
|
||||||
--bg-surface: var(--color-gray-800);
|
--gray-100: 31 41 55;
|
||||||
--bg-raised: var(--color-gray-700);
|
--gray-200: 55 65 81;
|
||||||
--bg-overlay: rgba(17, 24, 39, 0.8);
|
--gray-300: 75 85 99;
|
||||||
--bg-muted: var(--color-gray-700);
|
--gray-400: 107 114 128;
|
||||||
|
--gray-500: 156 163 175;
|
||||||
|
--gray-600: 209 213 219;
|
||||||
|
--gray-700: 229 231 235;
|
||||||
|
--gray-800: 243 244 246;
|
||||||
|
--gray-900: 249 250 251;
|
||||||
|
|
||||||
/* Text Colors */
|
/* Dark semantic colors for Tailwind */
|
||||||
--text-primary: var(--color-gray-50);
|
--surface: 31 41 55;
|
||||||
--text-secondary: var(--color-gray-300);
|
--background: 17 24 39;
|
||||||
--text-muted: var(--color-gray-400);
|
--border: 75 85 99;
|
||||||
--text-inverse: var(--color-gray-900);
|
|
||||||
|
|
||||||
/* Border Colors */
|
/* Dark theme Mantine colors */
|
||||||
--border-subtle: var(--color-gray-700);
|
--color-gray-50: #111827;
|
||||||
--border-default: var(--color-gray-600);
|
--color-gray-100: #1f2937;
|
||||||
--border-strong: var(--color-gray-500);
|
--color-gray-200: #374151;
|
||||||
|
--color-gray-300: #4b5563;
|
||||||
|
--color-gray-400: #6b7280;
|
||||||
|
--color-gray-500: #9ca3af;
|
||||||
|
--color-gray-600: #d1d5db;
|
||||||
|
--color-gray-700: #e5e7eb;
|
||||||
|
--color-gray-800: #f3f4f6;
|
||||||
|
--color-gray-900: #f9fafb;
|
||||||
|
|
||||||
/* Interactive States */
|
/* Dark theme semantic colors */
|
||||||
--hover-bg: var(--color-gray-700);
|
--bg-surface: #1f2937;
|
||||||
--active-bg: var(--color-gray-600);
|
--bg-raised: #374151;
|
||||||
--focus-ring: var(--color-primary-400);
|
--bg-muted: #374151;
|
||||||
|
--text-primary: #f9fafb;
|
||||||
|
--text-secondary: #d1d5db;
|
||||||
|
--text-muted: #9ca3af;
|
||||||
|
--border-subtle: #374151;
|
||||||
|
--border-default: #4b5563;
|
||||||
|
--border-strong: #6b7280;
|
||||||
|
--hover-bg: #374151;
|
||||||
|
--active-bg: #4b5563;
|
||||||
|
|
||||||
/* PDF Tool Specific */
|
/* Adjust shadows for dark mode */
|
||||||
--pdf-viewer-bg: var(--color-gray-800);
|
|
||||||
--pdf-toolbar-bg: var(--bg-surface);
|
|
||||||
--file-drop-border: var(--color-gray-600);
|
|
||||||
--file-drop-hover: var(--color-primary-900);
|
|
||||||
|
|
||||||
/* Shadow adjustments for dark mode */
|
|
||||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
|
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
|
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
|
||||||
@ -151,10 +136,4 @@
|
|||||||
/* Smooth transitions for theme switching */
|
/* Smooth transitions for theme switching */
|
||||||
* {
|
* {
|
||||||
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
||||||
}
|
|
||||||
|
|
||||||
/* Focus styles */
|
|
||||||
*:focus-visible {
|
|
||||||
outline: 2px solid var(--focus-ring);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
}
|
@ -4,141 +4,45 @@ module.exports = {
|
|||||||
"./index.html",
|
"./index.html",
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
"./src/**/*.{js,ts,jsx,tsx}",
|
||||||
],
|
],
|
||||||
|
darkMode: ['class', '[data-mantine-color-scheme="dark"]'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
// Colors using CSS variables (namespaced to avoid Mantine conflicts)
|
// Use standard Tailwind color system with CSS variables for theme switching
|
||||||
colors: {
|
colors: {
|
||||||
// Custom palette (avoid 'primary' and 'gray' which Mantine uses)
|
// Override gray to work with both themes
|
||||||
'app-primary': {
|
gray: {
|
||||||
50: 'var(--color-primary-50)',
|
50: 'rgb(var(--gray-50) / <alpha-value>)',
|
||||||
100: 'var(--color-primary-100)',
|
100: 'rgb(var(--gray-100) / <alpha-value>)',
|
||||||
200: 'var(--color-primary-200)',
|
200: 'rgb(var(--gray-200) / <alpha-value>)',
|
||||||
300: 'var(--color-primary-300)',
|
300: 'rgb(var(--gray-300) / <alpha-value>)',
|
||||||
400: 'var(--color-primary-400)',
|
400: 'rgb(var(--gray-400) / <alpha-value>)',
|
||||||
500: 'var(--color-primary-500)',
|
500: 'rgb(var(--gray-500) / <alpha-value>)',
|
||||||
600: 'var(--color-primary-600)',
|
600: 'rgb(var(--gray-600) / <alpha-value>)',
|
||||||
700: 'var(--color-primary-700)',
|
700: 'rgb(var(--gray-700) / <alpha-value>)',
|
||||||
800: 'var(--color-primary-800)',
|
800: 'rgb(var(--gray-800) / <alpha-value>)',
|
||||||
900: 'var(--color-primary-900)',
|
900: 'rgb(var(--gray-900) / <alpha-value>)',
|
||||||
},
|
},
|
||||||
// Custom gray palette
|
// Custom semantic colors for app-specific usage
|
||||||
'app-gray': {
|
surface: 'rgb(var(--surface) / <alpha-value>)',
|
||||||
50: 'var(--color-gray-50)',
|
background: 'rgb(var(--background) / <alpha-value>)',
|
||||||
100: 'var(--color-gray-100)',
|
border: 'rgb(var(--border) / <alpha-value>)',
|
||||||
200: 'var(--color-gray-200)',
|
|
||||||
300: 'var(--color-gray-300)',
|
|
||||||
400: 'var(--color-gray-400)',
|
|
||||||
500: 'var(--color-gray-500)',
|
|
||||||
600: 'var(--color-gray-600)',
|
|
||||||
700: 'var(--color-gray-700)',
|
|
||||||
800: 'var(--color-gray-800)',
|
|
||||||
900: 'var(--color-gray-900)',
|
|
||||||
},
|
|
||||||
// Semantic colors
|
|
||||||
success: 'var(--color-success)',
|
|
||||||
warning: 'var(--color-warning)',
|
|
||||||
error: 'var(--color-error)',
|
|
||||||
info: 'var(--color-info)',
|
|
||||||
|
|
||||||
// Background colors
|
|
||||||
'bg-app': 'var(--bg-app)',
|
|
||||||
'bg-surface': 'var(--bg-surface)',
|
|
||||||
'bg-raised': 'var(--bg-raised)',
|
|
||||||
'bg-muted': 'var(--bg-muted)',
|
|
||||||
'bg-overlay': 'var(--bg-overlay)',
|
|
||||||
|
|
||||||
// Text colors
|
|
||||||
'text-primary': 'var(--text-primary)',
|
|
||||||
'text-secondary': 'var(--text-secondary)',
|
|
||||||
'text-muted': 'var(--text-muted)',
|
|
||||||
'text-inverse': 'var(--text-inverse)',
|
|
||||||
|
|
||||||
// Border colors
|
|
||||||
'border-subtle': 'var(--border-subtle)',
|
|
||||||
'border-default': 'var(--border-default)',
|
|
||||||
'border-strong': 'var(--border-strong)',
|
|
||||||
|
|
||||||
// Interactive states
|
|
||||||
'hover-bg': 'var(--hover-bg)',
|
|
||||||
'active-bg': 'var(--active-bg)',
|
|
||||||
'focus-ring': 'var(--focus-ring)',
|
|
||||||
|
|
||||||
// PDF-specific colors
|
|
||||||
'pdf-viewer': 'var(--pdf-viewer-bg)',
|
|
||||||
'pdf-toolbar': 'var(--pdf-toolbar-bg)',
|
|
||||||
'file-drop': 'var(--file-drop-border)',
|
|
||||||
'file-drop-hover': 'var(--file-drop-hover)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Spacing using CSS variables (namespaced to avoid Mantine conflicts)
|
|
||||||
spacing: {
|
|
||||||
'app-xs': 'var(--space-xs)',
|
|
||||||
'app-sm': 'var(--space-sm)',
|
|
||||||
'app-md': 'var(--space-md)',
|
|
||||||
'app-lg': 'var(--space-lg)',
|
|
||||||
'app-xl': 'var(--space-xl)',
|
|
||||||
'app-2xl': 'var(--space-2xl)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Border radius using CSS variables (namespaced)
|
|
||||||
borderRadius: {
|
|
||||||
'app-xs': 'var(--radius-xs)',
|
|
||||||
'app-sm': 'var(--radius-sm)',
|
|
||||||
'app-md': 'var(--radius-md)',
|
|
||||||
'app-lg': 'var(--radius-lg)',
|
|
||||||
'app-xl': 'var(--radius-xl)',
|
|
||||||
'app-2xl': 'var(--radius-2xl)',
|
|
||||||
'app-full': 'var(--radius-full)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Box shadows using CSS variables (namespaced)
|
|
||||||
boxShadow: {
|
|
||||||
'app-xs': 'var(--shadow-xs)',
|
|
||||||
'app-sm': 'var(--shadow-sm)',
|
|
||||||
'app-md': 'var(--shadow-md)',
|
|
||||||
'app-lg': 'var(--shadow-lg)',
|
|
||||||
'app-xl': 'var(--shadow-xl)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Font weights using CSS variables
|
|
||||||
fontWeight: {
|
|
||||||
'normal': 'var(--font-weight-normal)',
|
|
||||||
'medium': 'var(--font-weight-medium)',
|
|
||||||
'semibold': 'var(--font-weight-semibold)',
|
|
||||||
'bold': 'var(--font-weight-bold)',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Z-index scale
|
// Z-index scale
|
||||||
zIndex: {
|
zIndex: {
|
||||||
'dropdown': 'var(--z-dropdown)',
|
'dropdown': '1000',
|
||||||
'sticky': 'var(--z-sticky)',
|
'sticky': '1020',
|
||||||
'fixed': 'var(--z-fixed)',
|
'fixed': '1030',
|
||||||
'modal-backdrop': 'var(--z-modal-backdrop)',
|
'modal-backdrop': '1040',
|
||||||
'modal': 'var(--z-modal)',
|
'modal': '1050',
|
||||||
'popover': 'var(--z-popover)',
|
'popover': '1060',
|
||||||
'tooltip': 'var(--z-tooltip)',
|
'tooltip': '1070',
|
||||||
},
|
|
||||||
|
|
||||||
// Layout variables
|
|
||||||
width: {
|
|
||||||
'sidebar': 'var(--sidebar-width)',
|
|
||||||
'sidebar-min': 'var(--sidebar-width-min)',
|
|
||||||
'sidebar-max': 'var(--sidebar-width-max)',
|
|
||||||
},
|
|
||||||
|
|
||||||
height: {
|
|
||||||
'header': 'var(--header-height)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Border width
|
|
||||||
borderWidth: {
|
|
||||||
DEFAULT: 'var(--border-width)',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
// Prevent conflicts with Mantine classes
|
// Enable preflight for standard Tailwind functionality
|
||||||
corePlugins: {
|
corePlugins: {
|
||||||
preflight: false,
|
preflight: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user