diff --git a/taskncoffee-app/package-lock.json b/taskncoffee-app/package-lock.json
index 99654ad..cf11434 100644
--- a/taskncoffee-app/package-lock.json
+++ b/taskncoffee-app/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@fingerprintjs/fingerprintjs": "^4.6.2",
+ "@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/vite": "^4.1.14",
@@ -1011,6 +1012,33 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@radix-ui/react-avatar": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
+ "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-is-hydrated": "0.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -1026,6 +1054,21 @@
}
}
},
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-label": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
@@ -1090,6 +1133,54 @@
}
}
},
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-is-hydrated": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
+ "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.38",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz",
@@ -5463,6 +5554,15 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/vite": {
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
diff --git a/taskncoffee-app/package.json b/taskncoffee-app/package.json
index 11e5171..9c5f625 100644
--- a/taskncoffee-app/package.json
+++ b/taskncoffee-app/package.json
@@ -11,6 +11,7 @@
},
"dependencies": {
"@fingerprintjs/fingerprintjs": "^4.6.2",
+ "@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/vite": "^4.1.14",
diff --git a/taskncoffee-app/src/App.jsx b/taskncoffee-app/src/App.jsx
index d08d21b..17fff3a 100644
--- a/taskncoffee-app/src/App.jsx
+++ b/taskncoffee-app/src/App.jsx
@@ -3,6 +3,7 @@ import { LoginPage } from './pages/Login'
import { SignUp } from './pages/SignUp'
import { AuthLayout } from './layouts/AuthLayout'
import { Routes, Route, Navigate } from 'react-router'
+import MenuDockVertical from './layouts/DashLayout'
function App() {
@@ -18,6 +19,11 @@ function App() {
} />
+
+
+ //
+ } />
} />
)
diff --git a/taskncoffee-app/src/components/ui/avatar.jsx b/taskncoffee-app/src/components/ui/avatar.jsx
new file mode 100644
index 0000000..9ad399b
--- /dev/null
+++ b/taskncoffee-app/src/components/ui/avatar.jsx
@@ -0,0 +1,45 @@
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+function Avatar({
+ className,
+ ...props
+}) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/taskncoffee-app/src/components/ui/shadcn-io/menu-dock/index.jsx b/taskncoffee-app/src/components/ui/shadcn-io/menu-dock/index.jsx
new file mode 100644
index 0000000..82ec31b
--- /dev/null
+++ b/taskncoffee-app/src/components/ui/shadcn-io/menu-dock/index.jsx
@@ -0,0 +1,185 @@
+'use client';;
+import React, { useState, useRef, useEffect, useMemo } from 'react';
+import { Home, Briefcase, Calendar, Shield, Settings } from 'lucide-react';
+import { cn } from '@/lib/utils';
+
+const defaultItems = [
+ { label: 'home', icon: Home },
+ { label: 'work', icon: Briefcase },
+ { label: 'calendar', icon: Calendar },
+ { label: 'security', icon: Shield },
+ { label: 'settings', icon: Settings },
+];
+
+export const MenuDock = ({
+ items,
+ className,
+ variant = 'default',
+ orientation = 'horizontal',
+ showLabels = true,
+ animated = true
+}) => {
+
+ const finalItems = useMemo(() => {
+ const isValid = items && Array.isArray(items) && items.length >= 2 && items.length <= 8;
+ if (!isValid) {
+ console.warn(
+ "MenuDock: 'items' prop is invalid or missing. Using default items.",
+ items
+ );
+ return defaultItems;
+ }
+ return items;
+ }, [items]);
+
+ const [activeIndex, setActiveIndex] = useState(0);
+ const [underlineWidth, setUnderlineWidth] = useState(0);
+ const [underlineLeft, setUnderlineLeft] = useState(0);
+
+ const textRefs = useRef([]);
+ const itemRefs = useRef([]);
+
+ useEffect(() => {
+ if (activeIndex >= finalItems.length) {
+ setActiveIndex(0);
+ }
+ }, [finalItems, activeIndex]);
+
+ useEffect(() => {
+ const updateUnderline = () => {
+ const activeButton = itemRefs.current[activeIndex];
+ const activeText = textRefs.current[activeIndex];
+
+ if (activeButton && activeText && showLabels && orientation === 'horizontal') {
+ const buttonRect = activeButton.getBoundingClientRect();
+ const textRect = activeText.getBoundingClientRect();
+ const containerRect = activeButton.parentElement?.getBoundingClientRect();
+
+ if (containerRect) {
+ setUnderlineWidth(textRect.width);
+ setUnderlineLeft(
+ buttonRect.left - containerRect.left + (buttonRect.width - textRect.width) / 2
+ );
+ }
+ }
+ };
+
+ updateUnderline();
+ window.addEventListener('resize', updateUnderline);
+ return () => window.removeEventListener('resize', updateUnderline);
+ }, [activeIndex, finalItems, showLabels, orientation]);
+
+ const handleItemClick = (index, item) => {
+ setActiveIndex(index);
+ item.onClick?.();
+ };
+
+ const getVariantStyles = () => {
+ switch (variant) {
+ case 'compact':
+ return {
+ container: 'p-1',
+ item: 'p-2 min-w-12',
+ icon: 'h-4 w-4',
+ text: 'text-xs'
+ };
+ case 'large':
+ return {
+ container: 'p-3',
+ item: 'p-3 min-w-16',
+ icon: 'h-6 w-6',
+ text: 'text-base'
+ };
+ default:
+ return {
+ container: 'p-2',
+ item: 'p-2 min-w-14',
+ icon: 'h-5 w-5',
+ text: 'text-sm'
+ };
+ }
+ };
+
+ const styles = getVariantStyles();
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/taskncoffee-app/src/layouts/DashLayout.jsx b/taskncoffee-app/src/layouts/DashLayout.jsx
new file mode 100644
index 0000000..0378b4f
--- /dev/null
+++ b/taskncoffee-app/src/layouts/DashLayout.jsx
@@ -0,0 +1,26 @@
+'use client';
+import { MenuDock } from '@/components/ui/shadcn-io/menu-dock';
+import { Home, Settings, Bell } from 'lucide-react';
+import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
+const sidebarItems = [
+ { label: 'home', icon: Home, onClick: () => console.log('Home clicked') },
+ { label: 'notifications', icon: Bell, onClick: () => console.log('Notifications clicked') },
+ { label: 'settings', icon: Settings, onClick: () => console.log('Settings clicked') },
+];
+export default function MenuDockVertical() {
+ return (
+
+ );
+}
\ No newline at end of file