mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01: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:
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}
|
||||
|
||||
Reference in New Issue
Block a user