mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-02 18:45:21 +00:00
V2: Design Navbar (#4017)
# Description of Changes - Changed the navbar styling to include all the icons that were on our figma design - Added a new link to our index.html to include the MUI symbols. - I chose to keep the automate and read icons the same as they were already because I feel as though they make more sense ``` <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,700,0,0" /> ``` --- figma vs app dark <img width="422" height="1038" alt="Screenshot 2025-07-21 at 5 44 19 PM" src="https://github.com/user-attachments/assets/15d5583f-ce3c-418f-81c6-6e6022f5f4d0" /> figma vs app light <img width="244" height="926" alt="Screenshot 2025-07-21 at 5 57 27 PM" src="https://github.com/user-attachments/assets/c855d02b-20ee-4ccf-af1f-a3c1a4c2a154" /> ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [x] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
parent
5f7e578ff8
commit
a9a41b3877
7
frontend/package-lock.json
generated
7
frontend/package-lock.json
generated
@ -27,6 +27,7 @@
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"material-symbols": "^0.33.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"react": "^19.1.0",
|
||||
@ -4175,6 +4176,12 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/material-symbols": {
|
||||
"version": "0.33.0",
|
||||
"resolved": "https://registry.npmjs.org/material-symbols/-/material-symbols-0.33.0.tgz",
|
||||
"integrity": "sha512-t9/Gz+14fClRgN7oVOt5CBuwsjFLxSNP9BRDyMrI5el3IZNvoD94IDGJha0YYivyAow24rCS0WOkAv4Dp+YjNg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"material-symbols": "^0.33.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"react": "^19.1.0",
|
||||
|
179
frontend/src/components/shared/QuickAccessBar.css
Normal file
179
frontend/src/components/shared/QuickAccessBar.css
Normal file
@ -0,0 +1,179 @@
|
||||
.activeIconScale {
|
||||
transform: scale(1.3);
|
||||
transition: transform 0.2s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.iconContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
/* Action icon styles */
|
||||
.action-icon-style {
|
||||
background-color: var(--icon-user-bg);
|
||||
color: var(--icon-user-color);
|
||||
border-radius: 50%;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
/* Main container styles */
|
||||
.quick-access-bar-main {
|
||||
background-color: var(--bg-muted);
|
||||
width: 5rem;
|
||||
min-width: 5rem;
|
||||
max-width: 5rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Rainbow mode container */
|
||||
.quick-access-bar-main.rainbow-mode {
|
||||
background-color: var(--bg-muted);
|
||||
width: 5rem;
|
||||
min-width: 5rem;
|
||||
max-width: 5rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Header padding */
|
||||
.quick-access-header {
|
||||
padding: 1rem 0.5rem 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.nav-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Nav header divider */
|
||||
.nav-header-divider {
|
||||
width: 3.75rem;
|
||||
border-color: var(--color-gray-300);
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* All tools text styles */
|
||||
.all-tools-text {
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-synthesis: none;
|
||||
}
|
||||
|
||||
.all-tools-text.active {
|
||||
color: var(--text-primary);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.all-tools-text.inactive {
|
||||
color: var(--color-gray-700);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Overflow divider */
|
||||
.overflow-divider {
|
||||
width: 3.75rem;
|
||||
border-color: var(--color-gray-300);
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
/* Scrollable content area */
|
||||
.quick-access-bar {
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 0 0.5rem 1rem 0.5rem;
|
||||
}
|
||||
|
||||
/* Scrollable content container */
|
||||
.scrollable-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* Button text styles */
|
||||
.button-text {
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-synthesis: none;
|
||||
}
|
||||
|
||||
.button-text.active {
|
||||
color: var(--text-primary);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.button-text.inactive {
|
||||
color: var(--color-gray-700);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Content divider */
|
||||
.content-divider {
|
||||
width: 3.75rem;
|
||||
border-color: var(--color-gray-300);
|
||||
}
|
||||
|
||||
/* Spacer */
|
||||
.spacer {
|
||||
flex: 1;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* Config button text */
|
||||
.config-button-text {
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-gray-700);
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-synthesis: none;
|
||||
}
|
||||
|
||||
/* Font size utility */
|
||||
.font-size-20 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* Hide scrollbar by default, show on scroll (Webkit browsers - Chrome, Safari, Edge) */
|
||||
.quick-access-bar::-webkit-scrollbar {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.quick-access-bar:hover::-webkit-scrollbar,
|
||||
.quick-access-bar:active::-webkit-scrollbar,
|
||||
.quick-access-bar:focus::-webkit-scrollbar {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.quick-access-bar::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.quick-access-bar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Firefox scrollbar styling */
|
||||
.quick-access-bar {
|
||||
scrollbar-width: auto;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||
}
|
@ -1,11 +1,17 @@
|
||||
import React, { useState } from "react";
|
||||
import { ActionIcon, Stack, Tooltip } from "@mantine/core";
|
||||
import MenuBookIcon from "@mui/icons-material/MenuBook";
|
||||
import AppsIcon from "@mui/icons-material/Apps";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import React, { useState, useRef } from "react";
|
||||
import { ActionIcon, Stack, Tooltip, Divider } from "@mantine/core";
|
||||
import MenuBookIcon from "@mui/icons-material/MenuBookRounded";
|
||||
import AppsIcon from "@mui/icons-material/AppsRounded";
|
||||
import SettingsIcon from "@mui/icons-material/SettingsRounded";
|
||||
import AutoAwesomeIcon from "@mui/icons-material/AutoAwesomeRounded";
|
||||
import FolderIcon from "@mui/icons-material/FolderRounded";
|
||||
import PersonIcon from "@mui/icons-material/PersonRounded";
|
||||
import NotificationsIcon from "@mui/icons-material/NotificationsRounded";
|
||||
import { useRainbowThemeContext } from "./RainbowThemeProvider";
|
||||
import rainbowStyles from '../../styles/rainbow.module.css';
|
||||
import AppConfigModal from './AppConfigModal';
|
||||
import { useIsOverflowing } from '../../hooks/useIsOverflowing';
|
||||
import './QuickAccessBar.css';
|
||||
|
||||
interface QuickAccessBarProps {
|
||||
onToolsClick: () => void;
|
||||
@ -16,6 +22,86 @@ interface QuickAccessBarProps {
|
||||
readerMode: boolean;
|
||||
}
|
||||
|
||||
interface ButtonConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: React.ReactNode;
|
||||
tooltip: string;
|
||||
isRound?: boolean;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
function NavHeader({
|
||||
activeButton,
|
||||
setActiveButton,
|
||||
onReaderToggle,
|
||||
onToolsClick
|
||||
}: {
|
||||
activeButton: string;
|
||||
setActiveButton: (id: string) => void;
|
||||
onReaderToggle: () => void;
|
||||
onToolsClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className="nav-header">
|
||||
<Tooltip label="User Profile" position="right">
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
className="action-icon-style"
|
||||
>
|
||||
<PersonIcon sx={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Notifications" position="right">
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
className="action-icon-style"
|
||||
>
|
||||
<NotificationsIcon sx={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/* Divider after top icons */}
|
||||
<Divider
|
||||
size="xs"
|
||||
className="nav-header-divider"
|
||||
/>
|
||||
{/* All Tools button below divider */}
|
||||
<Tooltip label="View all available tools" position="right">
|
||||
<div className="flex flex-col items-center gap-1 mt-4 mb-2">
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
setActiveButton('tools');
|
||||
onReaderToggle();
|
||||
onToolsClick();
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: activeButton === 'tools' ? 'var(--icon-tools-bg)' : 'var(--icon-inactive-bg)',
|
||||
color: activeButton === 'tools' ? 'var(--icon-tools-color)' : 'var(--icon-inactive-color)',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
className={activeButton === 'tools' ? 'activeIconScale' : ''}
|
||||
>
|
||||
<span className="iconContainer">
|
||||
<AppsIcon sx={{ fontSize: "1.75rem" }} />
|
||||
</span>
|
||||
</ActionIcon>
|
||||
<span className={`all-tools-text ${activeButton === 'tools' ? 'active' : 'inactive'}`}>
|
||||
All Tools
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const QuickAccessBar = ({
|
||||
onToolsClick,
|
||||
onReaderToggle,
|
||||
@ -26,55 +112,201 @@ const QuickAccessBar = ({
|
||||
}: QuickAccessBarProps) => {
|
||||
const { isRainbowMode } = useRainbowThemeContext();
|
||||
const [configModalOpen, setConfigModalOpen] = useState(false);
|
||||
const [activeButton, setActiveButton] = useState<string>('tools');
|
||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||
const isOverflow = useIsOverflowing(scrollableRef);
|
||||
|
||||
const buttonConfigs: ButtonConfig[] = [
|
||||
{
|
||||
id: 'read',
|
||||
name: 'Read',
|
||||
icon: <MenuBookIcon sx={{ fontSize: "1.5rem" }} />,
|
||||
tooltip: 'Read documents',
|
||||
size: 'lg',
|
||||
isRound: false,
|
||||
onClick: () => {
|
||||
setActiveButton('read');
|
||||
onReaderToggle();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'sign',
|
||||
name: 'Sign',
|
||||
icon:
|
||||
<span className="material-symbols-rounded font-size-20">
|
||||
signature
|
||||
</span>,
|
||||
tooltip: 'Sign your document',
|
||||
size: 'lg',
|
||||
isRound: false,
|
||||
onClick: () => setActiveButton('sign')
|
||||
},
|
||||
{
|
||||
id: 'automate',
|
||||
name: 'Automate',
|
||||
icon: <AutoAwesomeIcon sx={{ fontSize: "1.5rem" }} />,
|
||||
tooltip: 'Automate workflows',
|
||||
size: 'lg',
|
||||
isRound: false,
|
||||
onClick: () => setActiveButton('automate')
|
||||
},
|
||||
{
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
icon: <FolderIcon sx={{ fontSize: "1.5rem" }} />,
|
||||
tooltip: 'Manage files',
|
||||
isRound: true,
|
||||
size: 'lg',
|
||||
onClick: () => setActiveButton('files')
|
||||
},
|
||||
{
|
||||
id: 'activity',
|
||||
name: 'Activity',
|
||||
icon:
|
||||
<span className="material-symbols-rounded font-size-20">
|
||||
vital_signs
|
||||
</span>,
|
||||
tooltip: 'View activity and analytics',
|
||||
isRound: true,
|
||||
size: 'lg',
|
||||
onClick: () => setActiveButton('activity')
|
||||
},
|
||||
{
|
||||
id: 'config',
|
||||
name: 'Config',
|
||||
icon: <SettingsIcon sx={{ fontSize: "1rem" }} />,
|
||||
tooltip: 'Configure settings',
|
||||
size: 'lg',
|
||||
onClick: () => {
|
||||
setConfigModalOpen(true);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const CIRCULAR_BORDER_RADIUS = '50%';
|
||||
const ROUND_BORDER_RADIUS = '8px';
|
||||
|
||||
const getBorderRadius = (config: ButtonConfig): string => {
|
||||
return config.isRound ? CIRCULAR_BORDER_RADIUS : ROUND_BORDER_RADIUS;
|
||||
};
|
||||
|
||||
const getButtonStyle = (config: ButtonConfig) => {
|
||||
const isActive = activeButton === config.id;
|
||||
|
||||
if (isActive) {
|
||||
return {
|
||||
backgroundColor: `var(--icon-${config.id}-bg)`,
|
||||
color: `var(--icon-${config.id}-color)`,
|
||||
border: 'none',
|
||||
borderRadius: getBorderRadius(config),
|
||||
};
|
||||
}
|
||||
|
||||
// Inactive state - use consistent inactive colors
|
||||
return {
|
||||
backgroundColor: 'var(--icon-inactive-bg)',
|
||||
color: 'var(--icon-inactive-color)',
|
||||
border: 'none',
|
||||
borderRadius: getBorderRadius(config),
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`h-screen flex flex-col w-20 ${isRainbowMode ? rainbowStyles.rainbowPaper : ''}`}
|
||||
style={{
|
||||
padding: '1rem 0.5rem',
|
||||
backgroundColor: 'var(--bg-muted)'
|
||||
}}
|
||||
className={`h-screen flex flex-col w-20 quick-access-bar-main ${isRainbowMode ? 'rainbow-mode' : ''}`}
|
||||
>
|
||||
<Stack gap="lg" align="center" className="flex-1">
|
||||
{/* All Tools Button */}
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<ActionIcon
|
||||
size="xl"
|
||||
variant={leftPanelView === 'toolPicker' && !readerMode ? "filled" : "subtle"}
|
||||
onClick={onToolsClick}
|
||||
>
|
||||
<AppsIcon sx={{ fontSize: 28 }} />
|
||||
</ActionIcon>
|
||||
<span className="text-xs text-center leading-tight" style={{ color: 'var(--text-secondary)' }}>Tools</span>
|
||||
</div>
|
||||
{/* Fixed header outside scrollable area */}
|
||||
<div className="quick-access-header">
|
||||
<NavHeader
|
||||
activeButton={activeButton}
|
||||
setActiveButton={setActiveButton}
|
||||
onReaderToggle={onReaderToggle}
|
||||
onToolsClick={onToolsClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Reader Mode Button */}
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<ActionIcon
|
||||
size="xl"
|
||||
variant={readerMode ? "filled" : "subtle"}
|
||||
onClick={onReaderToggle}
|
||||
>
|
||||
<MenuBookIcon sx={{ fontSize: 28 }} />
|
||||
</ActionIcon>
|
||||
<span className="text-xs text-center leading-tight" style={{ color: 'var(--text-secondary)' }}>Read</span>
|
||||
</div>
|
||||
{/* Conditional divider when overflowing */}
|
||||
{isOverflow && (
|
||||
<Divider
|
||||
size="xs"
|
||||
className="overflow-divider"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Config Modal Button (for testing) */}
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
variant="subtle"
|
||||
onClick={() => setConfigModalOpen(true)}
|
||||
>
|
||||
<SettingsIcon sx={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
<span className="text-xs text-center leading-tight" style={{ color: 'var(--text-secondary)' }}>Config</span>
|
||||
{/* Scrollable content area */}
|
||||
<div
|
||||
ref={scrollableRef}
|
||||
className="quick-access-bar flex-1"
|
||||
onWheel={(e) => {
|
||||
// Prevent the wheel event from bubbling up to parent containers
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="scrollable-content">
|
||||
{/* Top section with main buttons */}
|
||||
<Stack gap="lg" align="center">
|
||||
{buttonConfigs.slice(0, -1).map((config, index) => (
|
||||
<React.Fragment key={config.id}>
|
||||
<Tooltip label={config.tooltip} position="right">
|
||||
<div className="flex flex-col items-center gap-1" style={{ marginTop: index === 0 ? '0.5rem' : "0rem" }}>
|
||||
<ActionIcon
|
||||
size={config.size || 'xl'}
|
||||
variant="subtle"
|
||||
onClick={config.onClick}
|
||||
style={getButtonStyle(config)}
|
||||
className={activeButton === config.id ? 'activeIconScale' : ''}
|
||||
>
|
||||
<span className="iconContainer">
|
||||
{config.icon}
|
||||
</span>
|
||||
</ActionIcon>
|
||||
<span className={`button-text ${activeButton === config.id ? 'active' : 'inactive'}`}>
|
||||
{config.name}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* Add divider after Automate button (index 2) */}
|
||||
{index === 2 && (
|
||||
<Divider
|
||||
size="xs"
|
||||
className="content-divider"
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
{/* Spacer to push Config button to bottom */}
|
||||
<div className="spacer" />
|
||||
|
||||
{/* Config button at the bottom */}
|
||||
<Tooltip label="Configure settings" position="right">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
setConfigModalOpen(true);
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: 'var(--icon-inactive-bg)',
|
||||
color: 'var(--icon-inactive-color)',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
<span className="iconContainer">
|
||||
<SettingsIcon sx={{ fontSize: "1rem" }} />
|
||||
</span>
|
||||
</ActionIcon>
|
||||
<span className="config-button-text">
|
||||
Config
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
<AppConfigModal
|
||||
opened={configModalOpen}
|
||||
|
1
frontend/src/global.d.ts
vendored
1
frontend/src/global.d.ts
vendored
@ -4,3 +4,4 @@ declare module "../tools/Merge";
|
||||
declare module "../components/PageEditor";
|
||||
declare module "../components/Viewer";
|
||||
declare module "*.js";
|
||||
declare module '*.module.css';
|
73
frontend/src/hooks/useIsOverflowing.ts
Normal file
73
frontend/src/hooks/useIsOverflowing.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import * as React from 'react';
|
||||
|
||||
|
||||
/**
|
||||
Hook to detect if an element's content overflows its container
|
||||
|
||||
|
||||
Parameters:
|
||||
- ref: React ref to the element to monitor
|
||||
- callback: Optional callback function called when overflow state changes
|
||||
|
||||
Returns: boolean | undefined - true if overflowing, false if not, undefined before first check
|
||||
|
||||
Usage example:
|
||||
|
||||
useEffect(() => {
|
||||
if (isOverflow) {
|
||||
// Do something
|
||||
}
|
||||
}, [isOverflow]);
|
||||
|
||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||
const isOverflow = useIsOverflowing(scrollableRef);
|
||||
|
||||
Fallback example (for browsers without ResizeObserver):
|
||||
|
||||
return (
|
||||
<div ref={scrollableRef} className="h-64 overflow-y-auto">
|
||||
{Content that might overflow}
|
||||
</div>
|
||||
);
|
||||
*/
|
||||
|
||||
|
||||
export const useIsOverflowing = (ref: React.RefObject<HTMLElement | null>, callback?: (isOverflow: boolean) => void) => {
|
||||
// State to track overflow status
|
||||
const [isOverflow, setIsOverflow] = React.useState<boolean | undefined>(undefined);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const { current } = ref;
|
||||
|
||||
// Function to check if element is overflowing
|
||||
const trigger = () => {
|
||||
if (!current) return;
|
||||
|
||||
// Compare scroll height (total content height) vs client height (visible height)
|
||||
const hasOverflow = current.scrollHeight > current.clientHeight;
|
||||
setIsOverflow(hasOverflow);
|
||||
|
||||
// Call optional callback with overflow state
|
||||
if (callback) callback(hasOverflow);
|
||||
};
|
||||
|
||||
if (current) {
|
||||
// Use ResizeObserver for modern browsers (real-time detection)
|
||||
if ('ResizeObserver' in window) {
|
||||
const resizeObserver = new ResizeObserver(trigger);
|
||||
resizeObserver.observe(current);
|
||||
|
||||
// Cleanup function to disconnect observer
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback for browsers without ResizeObserver support
|
||||
// Add a small delay to ensure the element is fully rendered
|
||||
setTimeout(trigger, 0);
|
||||
}
|
||||
}, [callback, ref]);
|
||||
|
||||
return isOverflow;
|
||||
};
|
@ -1,3 +1,9 @@
|
||||
@import 'material-symbols/rounded.css';
|
||||
|
||||
.material-symbols-rounded {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
|
@ -70,7 +70,7 @@ function HomePageContent() {
|
||||
}, [clearToolSelection]);
|
||||
|
||||
const handleReaderToggle = useCallback(() => {
|
||||
setReaderMode(!readerMode);
|
||||
setReaderMode(true);
|
||||
}, [readerMode]);
|
||||
|
||||
const handleViewChange = useCallback((view: string) => {
|
||||
@ -104,7 +104,7 @@ function HomePageContent() {
|
||||
|
||||
{/* Left: Tool Picker or Selected Tool Panel */}
|
||||
<div
|
||||
className={`h-screen flex flex-col overflow-hidden bg-[var(--bg-surface)] border-r border-[var(--border-subtle)] transition-all duration-300 ease-out ${isRainbowMode ? rainbowStyles.rainbowPaper : ''}`}
|
||||
className={`h-screen flex flex-col overflow-hidden bg-[var(--bg-toolbar)] border-r border-[var(--border-subtle)] transition-all duration-300 ease-out ${isRainbowMode ? rainbowStyles.rainbowPaper : ''}`}
|
||||
style={{
|
||||
width: sidebarsVisible && !readerMode ? '14vw' : '0',
|
||||
padding: sidebarsVisible && !readerMode ? '0.5rem' : '0'
|
||||
@ -163,9 +163,11 @@ function HomePageContent() {
|
||||
{/* Main View */}
|
||||
<Box
|
||||
className="flex-1 h-screen min-w-80 relative flex flex-col"
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-background)'
|
||||
}}
|
||||
style={
|
||||
isRainbowMode
|
||||
? {} // No background color in rainbow mode
|
||||
: { backgroundColor: 'var(--bg-background)' }
|
||||
}
|
||||
>
|
||||
{/* Top Controls */}
|
||||
<TopControls
|
||||
|
@ -72,6 +72,8 @@
|
||||
--bg-surface: #ffffff;
|
||||
--bg-raised: #f9fafb;
|
||||
--bg-muted: #f3f4f6;
|
||||
--bg-background: #f9fafb;
|
||||
--bg-toolbar: #ffffff;
|
||||
--text-primary: #111827;
|
||||
--text-secondary: #4b5563;
|
||||
--text-muted: #6b7280;
|
||||
@ -80,51 +82,101 @@
|
||||
--border-strong: #9ca3af;
|
||||
--hover-bg: #f9fafb;
|
||||
--active-bg: #f3f4f6;
|
||||
|
||||
/* Icon colors for light mode */
|
||||
--icon-user-bg: #9CA3AF;
|
||||
--icon-user-color: #FFFFFF;
|
||||
--icon-notifications-bg: #9CA3AF;
|
||||
--icon-notifications-color: #FFFFFF;
|
||||
--icon-tools-bg: #1E88E5;
|
||||
--icon-tools-color: #FFFFFF;
|
||||
--icon-read-bg: #4CAF50;
|
||||
--icon-read-color: #FFFFFF;
|
||||
--icon-sign-bg: #3BA99C;
|
||||
--icon-sign-color: #FFFFFF;
|
||||
--icon-automate-bg: #A576E3;
|
||||
--icon-automate-color: #FFFFFF;
|
||||
--icon-files-bg: #D3E7F7;
|
||||
--icon-files-color: #0A8BFF;
|
||||
--icon-activity-bg: #D3E7F7;
|
||||
--icon-activity-color: #0A8BFF;
|
||||
--icon-config-bg: #9CA3AF;
|
||||
--icon-config-color: #FFFFFF;
|
||||
|
||||
/* Inactive icon colors for light mode */
|
||||
--icon-inactive-bg: #9CA3AF;
|
||||
--icon-inactive-color: #FFFFFF;
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] {
|
||||
/* Dark theme gray scale (inverted) */
|
||||
--gray-50: 17 24 39;
|
||||
--gray-100: 31 41 55;
|
||||
--gray-200: 55 65 81;
|
||||
--gray-300: 75 85 99;
|
||||
--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;
|
||||
--gray-100: 31 35 41;
|
||||
--gray-200: 42 47 54;
|
||||
--gray-300: 55 65 81;
|
||||
--gray-400: 75 85 99;
|
||||
--gray-500: 107 114 128;
|
||||
--gray-600: 156 163 175;
|
||||
--gray-700: 209 213 219;
|
||||
--gray-800: 229 231 235;
|
||||
--gray-900: 243 244 246;
|
||||
|
||||
/* Dark semantic colors for Tailwind */
|
||||
--surface: 31 41 55;
|
||||
--background: 17 24 39;
|
||||
--border: 75 85 99;
|
||||
--surface: 31 35 41;
|
||||
--background: 42 47 54;
|
||||
--border: 55 65 81;
|
||||
|
||||
/* Dark theme Mantine colors */
|
||||
--color-gray-50: #111827;
|
||||
--color-gray-100: #1f2937;
|
||||
--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;
|
||||
--color-gray-100: #1F2329;
|
||||
--color-gray-200: #2A2F36;
|
||||
--color-gray-300: #374151;
|
||||
--color-gray-400: #4b5563;
|
||||
--color-gray-500: #6b7280;
|
||||
--color-gray-600: #9ca3af;
|
||||
--color-gray-700: #d1d5db;
|
||||
--color-gray-800: #e5e7eb;
|
||||
--color-gray-900: #f3f4f6;
|
||||
|
||||
/* Dark theme semantic colors */
|
||||
--bg-surface: #1f2937;
|
||||
--bg-raised: #374151;
|
||||
--bg-muted: #374151;
|
||||
--bg-surface: #2A2F36;
|
||||
--bg-raised: #1F2329;
|
||||
--bg-muted: #1F2329;
|
||||
--bg-background: #2A2F36;
|
||||
--bg-toolbar: #272A2E;
|
||||
--text-primary: #f9fafb;
|
||||
--text-secondary: #d1d5db;
|
||||
--text-muted: #9ca3af;
|
||||
--border-subtle: #374151;
|
||||
--border-default: #4b5563;
|
||||
--border-strong: #6b7280;
|
||||
--border-subtle: #2A2F36;
|
||||
--border-default: #374151;
|
||||
--border-strong: #4b5563;
|
||||
--hover-bg: #374151;
|
||||
--active-bg: #4b5563;
|
||||
|
||||
/* Icon colors for dark mode */
|
||||
--icon-user-bg: #2A2F36;
|
||||
--icon-user-color: #6E7581;
|
||||
--icon-notifications-bg: #2A2F36;
|
||||
--icon-notifications-color: #6E7581;
|
||||
--icon-tools-bg: #4B525A;
|
||||
--icon-tools-color: #EAEAEA;
|
||||
--icon-read-bg: #4B525A;
|
||||
--icon-read-color: #EAEAEA;
|
||||
--icon-sign-bg: #4B525A;
|
||||
--icon-sign-color: #EAEAEA;
|
||||
--icon-automate-bg: #4B525A;
|
||||
--icon-automate-color: #EAEAEA;
|
||||
--icon-files-bg: #4B525A;
|
||||
--icon-files-color: #EAEAEA;
|
||||
--icon-activity-bg: #4B525A;
|
||||
--icon-activity-color: #EAEAEA;
|
||||
--icon-config-bg: #4B525A;
|
||||
--icon-config-color: #EAEAEA;
|
||||
|
||||
/* Inactive icon colors for dark mode */
|
||||
--icon-inactive-bg: #2A2F36;
|
||||
--icon-inactive-color: #6E7581;
|
||||
|
||||
/* Adjust shadows for dark mode */
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||
|
Loading…
x
Reference in New Issue
Block a user