From 2c35b781ddf86a933988ebc6df7dbbefbdee576b Mon Sep 17 00:00:00 2001 From: IluaAir Date: Fri, 10 Oct 2025 23:58:57 +0300 Subject: [PATCH] start creating dashboard --- taskncoffee-app/package-lock.json | 100 ++++++++++ taskncoffee-app/package.json | 1 + taskncoffee-app/src/App.jsx | 6 + taskncoffee-app/src/components/ui/avatar.jsx | 45 +++++ .../ui/shadcn-io/menu-dock/index.jsx | 185 ++++++++++++++++++ taskncoffee-app/src/layouts/DashLayout.jsx | 26 +++ 6 files changed, 363 insertions(+) create mode 100644 taskncoffee-app/src/components/ui/avatar.jsx create mode 100644 taskncoffee-app/src/components/ui/shadcn-io/menu-dock/index.jsx create mode 100644 taskncoffee-app/src/layouts/DashLayout.jsx 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 ( +
+
+ + + CN + + +
+
+ ); +} \ No newline at end of file