Skip to content

Commit

Permalink
Merge pull request #185 from Karn-x7/main
Browse files Browse the repository at this point in the history
[enhancement] improves sidebar/ navbar
  • Loading branch information
Pranav0-0Aggarwal authored Jan 3, 2025
2 parents f59149b + 3cf8d3c commit 695e8c3
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 138 deletions.
22 changes: 16 additions & 6 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"lucide-react": "^0.400.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.4.0",
"react-router-dom": "^6.24.1",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
Expand All @@ -41,11 +42,11 @@
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-config-react-app": "^7.0.1",
"postcss": "^8.4.39",
"postcss": "^8.4.49",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
"tailwindcss": "^3.4.17",
Expand Down
104 changes: 94 additions & 10 deletions frontend/src/components/Navigation/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,109 @@
import React, { useState, useEffect, useCallback } from 'react';
import { ThemeToggle } from '@/components/ThemeToggle';

export function Navbar(props: { title?: string }) {
interface NavbarProps {
title?: string;
onNameChange?: (name: string) => void;
}

export function Navbar({ title, onNameChange }: NavbarProps) {
const [isEditing, setIsEditing] = useState(false);
const [name, setName] = useState(title || '');
const [showPlaceholder, setShowPlaceholder] = useState(!title);

// Handle initial load and localStorage
useEffect(() => {
const storedName = localStorage.getItem('pictopy-username');
if (storedName) {
setName(storedName);
setShowPlaceholder(false);
}
}, []);

const handleNameSubmit = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
const inputValue = (e.target as HTMLInputElement).value.trim();
if (inputValue) {
setName(inputValue);
setShowPlaceholder(false);
setIsEditing(false);
localStorage.setItem('pictopy-username', inputValue);
onNameChange?.(inputValue);
}
}
},
[onNameChange]
);

const handleNameClick = useCallback(() => {
if (!isEditing) {
setIsEditing(true);
}
}, [isEditing]);

const handleBlur = useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
const inputValue = e.target.value.trim();
if (inputValue) {
setName(inputValue);
setShowPlaceholder(false);
localStorage.setItem('pictopy-username', inputValue);
onNameChange?.(inputValue);
}
setIsEditing(false);
},
[onNameChange]
);

return (
<header className="flex w-full flex-row items-center justify-center align-middle">
<div className="bg-theme-light mb-4 mt-3 flex h-16 items-center justify-between rounded-2xl border border-gray-200 px-4 sm:px-8 md:px-16 shadow-md backdrop-blur-md backdrop-saturate-150 dark:border-white/10 dark:bg-white/5 w-[90%] sm:w-[70%] md:w-[55%]">
<div className="flex items-center gap-2 sm:gap-4">
<div className="rounded-2xl mb-4 mt-3 flex h-16 w-[90%] transform items-center justify-between border border-gray-200 bg-gradient-to-r from-blue-500 to-purple-600 px-4 shadow-lg backdrop-blur-lg backdrop-saturate-150 transition-all duration-300 ease-in-out hover:scale-105 dark:border-white/10 dark:bg-gradient-to-r dark:from-gray-800 dark:to-black sm:w-[70%] sm:px-8 md:w-[55%] md:px-16">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<img src="/public/PictoPy_Logo.png" className="h-7" alt="PictoPy Logo" />

<img
src="/PictoPy_Logo.png"
className="h-7 transition-opacity duration-200 hover:opacity-80"
alt="PictoPy Logo"
/>
<span className="text-theme-dark dark:text-theme-light font-sans text-base sm:text-lg font-bold drop-shadow-sm">
PictoPy
</span>
</div>
</div>
<div className="flex items-center gap-2 sm:gap-4">
<span className="text-theme-dark dark:text-theme-light font-sans text-sm sm:text-lg font-medium">
Welcome {props.title || 'User'}
</span>

<div className="flex items-center gap-4">
<div className="flex items-center">
<span className="font-sans text-lg font-medium text-white">
Welcome{' '}
</span>
{isEditing || showPlaceholder ? (
<input
type="text"
placeholder="Enter your name"
defaultValue={name}
onKeyDown={handleNameSubmit}
onBlur={handleBlur}
className="ml-2 w-32 rounded-md border border-white/20 bg-white/10 px-2 py-1 text-white placeholder-white/50 transition-colors duration-200 focus:border-yellow-300 focus:outline-none"
autoFocus
aria-label="Enter your name"
/>
) : (
<button
onClick={handleNameClick}
className="rounded ml-2 px-2 text-white transition-colors duration-200 hover:text-yellow-300 focus:outline-none focus:ring-2 focus:ring-yellow-300 focus:ring-offset-2 focus:ring-offset-transparent"
aria-label="Click to edit name"
>
{name || 'User'}
</button>
)}
</div>
<ThemeToggle />
</div>
</div>
</header>
);
}
}

export default Navbar;

89 changes: 45 additions & 44 deletions frontend/src/components/Navigation/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Link, useLocation } from 'react-router-dom';
import { Home, Sparkles, Video, Images, Settings } from 'lucide-react';
import { useState } from 'react';

function Sidebar() {
const Sidebar = () => {
const location = useLocation();
const [isExpanded, setIsExpanded] = useState(false);

const isActive = (path: string) => location.pathname === path;

const handleMouseEnter = () => setIsExpanded(true);
const handleMouseLeave = () => setIsExpanded(false);

const linkClasses = (path: string) => {
const baseClasses =
'group flex flex-col items-center gap-2 p-3 rounded-xl transition-all duration-200';
Expand Down Expand Up @@ -34,50 +39,46 @@ function Sidebar() {
'h-5 w-5 transition-transform duration-200 ease-out group-hover:scale-110';

return (
<div className="sidebar text-theme-dark dark:text-theme-light bg-theme-light m-2 sm:m-4 flex flex-col justify-between rounded-lg sm:rounded-2xl border border-gray-300 p-2 sm:p-4 backdrop-blur-md backdrop-saturate-150 dark:border-white/10 dark:bg-white/5 w-full sm:w-36">
<div className="mt-2 flex flex-col gap-4">
<Link to="/home" className={linkClasses('/home')}>
<Home
className={iconClasses}
strokeWidth={isActive('/home') ? 2.5 : 2}
/>
<span className="text-xs sm:text-sm font-medium">Home</span>
</Link>

<Link to="/ai-tagging" className={linkClasses('/ai-tagging')}>
<Sparkles
className={iconClasses}
strokeWidth={isActive('/ai-tagging') ? 2.5 : 2}
/>
<span className="text-xs sm:text-sm font-medium">AI Tagging</span>
</Link>

<Link to="/videos" className={linkClasses('/videos')}>
<Video
className={iconClasses}
strokeWidth={isActive('/videos') ? 2.5 : 2}
/>
<span className="text-xs sm:text-sm font-medium">Videos</span>
</Link>

<Link to="/albums" className={linkClasses('/albums')}>
<Images
className={iconClasses}
strokeWidth={isActive('/albums') ? 2.5 : 2}
/>
<span className="text-xs sm:text-sm font-medium">Albums</span>
</Link>
<div
className={`relative sidebar bg-theme-light dark:bg-gray-800 text-gray-900 dark:text-gray-200 m-4 flex flex-col justify-between rounded-2xl border border-gray-300 dark:border-gray-700 p-4 shadow-md transition-all duration-300 ${
isExpanded ? 'w-48' : 'w-16'
}`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div className="mt-2 flex flex-col gap-2">
{[
{ path: '/home', label: 'Home', Icon: Home },
{ path: '/ai-tagging', label: 'AI Tagging', Icon: Sparkles },
{ path: '/videos', label: 'Videos', Icon: Video },
{ path: '/albums', label: 'Albums', Icon: Images },
{ path: '/settings', label: 'Settings', Icon: Settings },
].map(({ path, label, Icon }) => (
<Link
to={path}
className={linkClasses(path)}
tabIndex={0}
aria-label={label}
key={path}
>
<Icon
className={`${iconClasses} ${
isActive(path) ? 'scale-110 text-gray-800' : ''
}`}
strokeWidth={isActive(path) ? 2.5 : 1.5}
/>
<span
className={`whitespace-nowrap text-sm font-medium transition-opacity duration-200 ${
isExpanded ? 'opacity-100' : 'opacity-0'
}`}
>
{label}
</span>
</Link>
))}
</div>

<Link to="/settings" className={linkClasses('/settings')}>
<Settings
className={`${iconClasses} transform transition-transform group-hover:rotate-90`}
strokeWidth={isActive('/settings') ? 2.5 : 2}
/>
<span className="text-xs sm:text-sm font-medium">Settings</span>
</Link>
</div>
);
}
};

export default Sidebar;
export default Sidebar;
18 changes: 8 additions & 10 deletions frontend/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
default: 'bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80 focus:ring-primary dark:bg-primary-dark dark:text-primary-foreground-dark dark:hover:bg-primary-dark/90 dark:active:bg-primary-dark/80',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80 focus:ring-destructive dark:bg-destructive-dark dark:text-destructive-foreground-dark dark:hover:bg-destructive-dark/90 dark:active:bg-destructive-dark/80',
outline: 'border border-input hover:bg-accent hover:text-accent-foreground active:bg-accent/90 focus:ring-accent dark:bg-accent-dark dark:text-accent-foreground-dark dark:border-input-dark',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70 focus:ring-secondary dark:bg-secondary-dark dark:text-secondary-foreground-dark dark:hover:bg-secondary-dark/80 dark:active:bg-secondary-dark/70',
ghost: 'hover:bg-accent hover:text-accent-foreground active:bg-accent/90 focus:ring-accent dark:hover:bg-accent-dark dark:text-accent-foreground-dark',
link: 'text-primary underline-offset-4 hover:underline active:text-primary/80 focus:ring-primary dark:text-primary-dark dark:hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
Expand Down Expand Up @@ -54,3 +51,4 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
Button.displayName = 'Button';

export { Button, buttonVariants };

4 changes: 1 addition & 3 deletions frontend/tailwind.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ module.exports = {
},
extend: {
colors: {
'theme-light': '#ffffff',
'theme-dark': '#000000',
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
Expand Down Expand Up @@ -76,4 +74,4 @@ module.exports = {
},
},
plugins: [require("tailwindcss-animate")],
}
};
Loading

0 comments on commit 695e8c3

Please sign in to comment.