mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-04 03:25: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-browser-languagedetector": "^8.1.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
"material-symbols": "^0.33.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"pdfjs-dist": "^3.11.174",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
@ -4175,6 +4176,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"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": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"i18next-browser-languagedetector": "^8.1.0",
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
"material-symbols": "^0.33.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"pdfjs-dist": "^3.11.174",
|
||||||
"react": "^19.1.0",
|
"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 React, { useState, useRef } from "react";
|
||||||
import { ActionIcon, Stack, Tooltip } from "@mantine/core";
|
import { ActionIcon, Stack, Tooltip, Divider } from "@mantine/core";
|
||||||
import MenuBookIcon from "@mui/icons-material/MenuBook";
|
import MenuBookIcon from "@mui/icons-material/MenuBookRounded";
|
||||||
import AppsIcon from "@mui/icons-material/Apps";
|
import AppsIcon from "@mui/icons-material/AppsRounded";
|
||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
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 { useRainbowThemeContext } from "./RainbowThemeProvider";
|
||||||
import rainbowStyles from '../../styles/rainbow.module.css';
|
import rainbowStyles from '../../styles/rainbow.module.css';
|
||||||
import AppConfigModal from './AppConfigModal';
|
import AppConfigModal from './AppConfigModal';
|
||||||
|
import { useIsOverflowing } from '../../hooks/useIsOverflowing';
|
||||||
|
import './QuickAccessBar.css';
|
||||||
|
|
||||||
interface QuickAccessBarProps {
|
interface QuickAccessBarProps {
|
||||||
onToolsClick: () => void;
|
onToolsClick: () => void;
|
||||||
@ -16,6 +22,86 @@ interface QuickAccessBarProps {
|
|||||||
readerMode: boolean;
|
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 = ({
|
const QuickAccessBar = ({
|
||||||
onToolsClick,
|
onToolsClick,
|
||||||
onReaderToggle,
|
onReaderToggle,
|
||||||
@ -26,55 +112,201 @@ const QuickAccessBar = ({
|
|||||||
}: QuickAccessBarProps) => {
|
}: QuickAccessBarProps) => {
|
||||||
const { isRainbowMode } = useRainbowThemeContext();
|
const { isRainbowMode } = useRainbowThemeContext();
|
||||||
const [configModalOpen, setConfigModalOpen] = useState(false);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`h-screen flex flex-col w-20 ${isRainbowMode ? rainbowStyles.rainbowPaper : ''}`}
|
className={`h-screen flex flex-col w-20 quick-access-bar-main ${isRainbowMode ? 'rainbow-mode' : ''}`}
|
||||||
style={{
|
|
||||||
padding: '1rem 0.5rem',
|
|
||||||
backgroundColor: 'var(--bg-muted)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Stack gap="lg" align="center" className="flex-1">
|
{/* Fixed header outside scrollable area */}
|
||||||
{/* All Tools Button */}
|
<div className="quick-access-header">
|
||||||
<div className="flex flex-col items-center gap-1">
|
<NavHeader
|
||||||
<ActionIcon
|
activeButton={activeButton}
|
||||||
size="xl"
|
setActiveButton={setActiveButton}
|
||||||
variant={leftPanelView === 'toolPicker' && !readerMode ? "filled" : "subtle"}
|
onReaderToggle={onReaderToggle}
|
||||||
onClick={onToolsClick}
|
onToolsClick={onToolsClick}
|
||||||
>
|
/>
|
||||||
<AppsIcon sx={{ fontSize: 28 }} />
|
</div>
|
||||||
</ActionIcon>
|
|
||||||
<span className="text-xs text-center leading-tight" style={{ color: 'var(--text-secondary)' }}>Tools</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Reader Mode Button */}
|
{/* Conditional divider when overflowing */}
|
||||||
<div className="flex flex-col items-center gap-1">
|
{isOverflow && (
|
||||||
<ActionIcon
|
<Divider
|
||||||
size="xl"
|
size="xs"
|
||||||
variant={readerMode ? "filled" : "subtle"}
|
className="overflow-divider"
|
||||||
onClick={onReaderToggle}
|
/>
|
||||||
>
|
)}
|
||||||
<MenuBookIcon sx={{ fontSize: 28 }} />
|
|
||||||
</ActionIcon>
|
|
||||||
<span className="text-xs text-center leading-tight" style={{ color: 'var(--text-secondary)' }}>Read</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Spacer */}
|
{/* Scrollable content area */}
|
||||||
<div className="flex-1" />
|
<div
|
||||||
|
ref={scrollableRef}
|
||||||
{/* Config Modal Button (for testing) */}
|
className="quick-access-bar flex-1"
|
||||||
<div className="flex flex-col items-center gap-1">
|
onWheel={(e) => {
|
||||||
<ActionIcon
|
// Prevent the wheel event from bubbling up to parent containers
|
||||||
size="lg"
|
e.stopPropagation();
|
||||||
variant="subtle"
|
}}
|
||||||
onClick={() => setConfigModalOpen(true)}
|
>
|
||||||
>
|
<div className="scrollable-content">
|
||||||
<SettingsIcon sx={{ fontSize: 20 }} />
|
{/* Top section with main buttons */}
|
||||||
</ActionIcon>
|
<Stack gap="lg" align="center">
|
||||||
<span className="text-xs text-center leading-tight" style={{ color: 'var(--text-secondary)' }}>Config</span>
|
{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>
|
</div>
|
||||||
</Stack>
|
</div>
|
||||||
|
|
||||||
<AppConfigModal
|
<AppConfigModal
|
||||||
opened={configModalOpen}
|
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/PageEditor";
|
||||||
declare module "../components/Viewer";
|
declare module "../components/Viewer";
|
||||||
declare module "*.js";
|
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 {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
@ -70,7 +70,7 @@ function HomePageContent() {
|
|||||||
}, [clearToolSelection]);
|
}, [clearToolSelection]);
|
||||||
|
|
||||||
const handleReaderToggle = useCallback(() => {
|
const handleReaderToggle = useCallback(() => {
|
||||||
setReaderMode(!readerMode);
|
setReaderMode(true);
|
||||||
}, [readerMode]);
|
}, [readerMode]);
|
||||||
|
|
||||||
const handleViewChange = useCallback((view: string) => {
|
const handleViewChange = useCallback((view: string) => {
|
||||||
@ -104,7 +104,7 @@ function HomePageContent() {
|
|||||||
|
|
||||||
{/* Left: Tool Picker or Selected Tool Panel */}
|
{/* Left: Tool Picker or Selected Tool Panel */}
|
||||||
<div
|
<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={{
|
style={{
|
||||||
width: sidebarsVisible && !readerMode ? '14vw' : '0',
|
width: sidebarsVisible && !readerMode ? '14vw' : '0',
|
||||||
padding: sidebarsVisible && !readerMode ? '0.5rem' : '0'
|
padding: sidebarsVisible && !readerMode ? '0.5rem' : '0'
|
||||||
@ -163,9 +163,11 @@ function HomePageContent() {
|
|||||||
{/* Main View */}
|
{/* Main View */}
|
||||||
<Box
|
<Box
|
||||||
className="flex-1 h-screen min-w-80 relative flex flex-col"
|
className="flex-1 h-screen min-w-80 relative flex flex-col"
|
||||||
style={{
|
style={
|
||||||
backgroundColor: 'var(--bg-background)'
|
isRainbowMode
|
||||||
}}
|
? {} // No background color in rainbow mode
|
||||||
|
: { backgroundColor: 'var(--bg-background)' }
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{/* Top Controls */}
|
{/* Top Controls */}
|
||||||
<TopControls
|
<TopControls
|
||||||
|
@ -72,6 +72,8 @@
|
|||||||
--bg-surface: #ffffff;
|
--bg-surface: #ffffff;
|
||||||
--bg-raised: #f9fafb;
|
--bg-raised: #f9fafb;
|
||||||
--bg-muted: #f3f4f6;
|
--bg-muted: #f3f4f6;
|
||||||
|
--bg-background: #f9fafb;
|
||||||
|
--bg-toolbar: #ffffff;
|
||||||
--text-primary: #111827;
|
--text-primary: #111827;
|
||||||
--text-secondary: #4b5563;
|
--text-secondary: #4b5563;
|
||||||
--text-muted: #6b7280;
|
--text-muted: #6b7280;
|
||||||
@ -80,51 +82,101 @@
|
|||||||
--border-strong: #9ca3af;
|
--border-strong: #9ca3af;
|
||||||
--hover-bg: #f9fafb;
|
--hover-bg: #f9fafb;
|
||||||
--active-bg: #f3f4f6;
|
--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"] {
|
[data-mantine-color-scheme="dark"] {
|
||||||
/* Dark theme gray scale (inverted) */
|
/* Dark theme gray scale (inverted) */
|
||||||
--gray-50: 17 24 39;
|
--gray-50: 17 24 39;
|
||||||
--gray-100: 31 41 55;
|
--gray-100: 31 35 41;
|
||||||
--gray-200: 55 65 81;
|
--gray-200: 42 47 54;
|
||||||
--gray-300: 75 85 99;
|
--gray-300: 55 65 81;
|
||||||
--gray-400: 107 114 128;
|
--gray-400: 75 85 99;
|
||||||
--gray-500: 156 163 175;
|
--gray-500: 107 114 128;
|
||||||
--gray-600: 209 213 219;
|
--gray-600: 156 163 175;
|
||||||
--gray-700: 229 231 235;
|
--gray-700: 209 213 219;
|
||||||
--gray-800: 243 244 246;
|
--gray-800: 229 231 235;
|
||||||
--gray-900: 249 250 251;
|
--gray-900: 243 244 246;
|
||||||
|
|
||||||
/* Dark semantic colors for Tailwind */
|
/* Dark semantic colors for Tailwind */
|
||||||
--surface: 31 41 55;
|
--surface: 31 35 41;
|
||||||
--background: 17 24 39;
|
--background: 42 47 54;
|
||||||
--border: 75 85 99;
|
--border: 55 65 81;
|
||||||
|
|
||||||
/* Dark theme Mantine colors */
|
/* Dark theme Mantine colors */
|
||||||
--color-gray-50: #111827;
|
--color-gray-50: #111827;
|
||||||
--color-gray-100: #1f2937;
|
--color-gray-100: #1F2329;
|
||||||
--color-gray-200: #374151;
|
--color-gray-200: #2A2F36;
|
||||||
--color-gray-300: #4b5563;
|
--color-gray-300: #374151;
|
||||||
--color-gray-400: #6b7280;
|
--color-gray-400: #4b5563;
|
||||||
--color-gray-500: #9ca3af;
|
--color-gray-500: #6b7280;
|
||||||
--color-gray-600: #d1d5db;
|
--color-gray-600: #9ca3af;
|
||||||
--color-gray-700: #e5e7eb;
|
--color-gray-700: #d1d5db;
|
||||||
--color-gray-800: #f3f4f6;
|
--color-gray-800: #e5e7eb;
|
||||||
--color-gray-900: #f9fafb;
|
--color-gray-900: #f3f4f6;
|
||||||
|
|
||||||
/* Dark theme semantic colors */
|
/* Dark theme semantic colors */
|
||||||
--bg-surface: #1f2937;
|
--bg-surface: #2A2F36;
|
||||||
--bg-raised: #374151;
|
--bg-raised: #1F2329;
|
||||||
--bg-muted: #374151;
|
--bg-muted: #1F2329;
|
||||||
|
--bg-background: #2A2F36;
|
||||||
|
--bg-toolbar: #272A2E;
|
||||||
--text-primary: #f9fafb;
|
--text-primary: #f9fafb;
|
||||||
--text-secondary: #d1d5db;
|
--text-secondary: #d1d5db;
|
||||||
--text-muted: #9ca3af;
|
--text-muted: #9ca3af;
|
||||||
--border-subtle: #374151;
|
--border-subtle: #2A2F36;
|
||||||
--border-default: #4b5563;
|
--border-default: #374151;
|
||||||
--border-strong: #6b7280;
|
--border-strong: #4b5563;
|
||||||
--hover-bg: #374151;
|
--hover-bg: #374151;
|
||||||
--active-bg: #4b5563;
|
--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 */
|
/* Adjust shadows 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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user