From 16e9c8cd1acedcd13e4d72f04719d2265cffc9a1 Mon Sep 17 00:00:00 2001 From: v0agent Date: Sat, 6 Jun 2026 19:00:42 +0000 Subject: [PATCH] feat: add navigation and style for MiniRkx floating button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Huỳnh Thương <252359928+Huynhthuongg@users.noreply.github.com> --- src/App.tsx | 10 +- src/components/MiniRkxMenu.tsx | 22 ++-- src/styles/minirkx.css | 199 +++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 src/styles/minirkx.css diff --git a/src/App.tsx b/src/App.tsx index 3e8cc98..491f85b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import MainContent from './components/MainContent' import MiniRkxButton from './components/MiniRkxButton' import MiniRkxMenu from './components/MiniRkxMenu' import ApiSettingsModal from './components/ApiSettingsModal' +import './styles/minirkx.css' export default function App() { const [sidebarOpen, setSidebarOpen] = useState(false) @@ -12,12 +13,18 @@ export default function App() { const [showApiModal, setShowApiModal] = useState(false) const [prompt, setPrompt] = useState('') const [isRecording, setIsRecording] = useState(false) + const [currentPage, setCurrentPage] = useState('home') const handleMenuCommand = (command: string) => { - console.log(`Menu command: ${command}`) + console.log(`[v0] Menu command: ${command}`) // Handle menu commands here } + const handleNavigate = (page: string) => { + console.log(`[v0] Navigating to: ${page}`) + setCurrentPage(page) + } + const handleSubmitPrompt = () => { if (!prompt.trim()) return console.log(`Submitting prompt: ${prompt}`) @@ -68,6 +75,7 @@ export default function App() { isOpen={menuOpen} onClose={() => setMenuOpen(false)} onMenuCommand={handleMenuCommand} + onNavigate={handleNavigate} /> {/* API Settings Modal */} diff --git a/src/components/MiniRkxMenu.tsx b/src/components/MiniRkxMenu.tsx index b5765b0..2699132 100644 --- a/src/components/MiniRkxMenu.tsx +++ b/src/components/MiniRkxMenu.tsx @@ -2,20 +2,24 @@ interface MiniRkxMenuProps { isOpen: boolean onClose: () => void onMenuCommand: (command: string) => void + onNavigate?: (page: string) => void } const menuItems = [ - { icon: '🏠', label: 'Trang chủ', command: 'Trang chủ' }, - { icon: '📚', label: 'Thư viện', command: 'Thư viện' }, - { icon: '📂', label: 'Dự án', command: 'Dự án' }, - { icon: '⚙️', label: 'Ứng dụng', command: 'Ứng dụng' }, - { icon: '💻', label: 'Codex', command: 'Codex' }, - { icon: '🔧', label: 'Cài đặt', command: 'Cài đặt' }, + { icon: '🏠', label: 'Trang chủ', command: 'home', page: 'home' }, + { icon: '📚', label: 'Thư viện', command: 'library', page: 'library' }, + { icon: '📂', label: 'Dự án', command: 'projects', page: 'projects' }, + { icon: '⚙️', label: 'Ứng dụng', command: 'apps', page: 'apps' }, + { icon: '💻', label: 'Codex', command: 'codex', page: 'codex' }, + { icon: '🔧', label: 'Cài đặt', command: 'settings', page: 'settings' }, ] -export default function MiniRkxMenu({ isOpen, onClose, onMenuCommand }: MiniRkxMenuProps) { - const handleItemClick = (command: string) => { +export default function MiniRkxMenu({ isOpen, onClose, onMenuCommand, onNavigate }: MiniRkxMenuProps) { + const handleItemClick = (command: string, page: string) => { onMenuCommand(command) + if (onNavigate) { + onNavigate(page) + } onClose() } @@ -28,7 +32,7 @@ export default function MiniRkxMenu({ isOpen, onClose, onMenuCommand }: MiniRkxM
handleItemClick(item.command)} + onClick={() => handleItemClick(item.command, item.page)} >
{item.icon}
diff --git a/src/styles/minirkx.css b/src/styles/minirkx.css new file mode 100644 index 0000000..ae7861f --- /dev/null +++ b/src/styles/minirkx.css @@ -0,0 +1,199 @@ +/* MiniRkx Floating Button Styles */ +.minirkx-button { + position: fixed; + bottom: 32px; + right: 32px; + width: 72px; + height: 72px; + border-radius: 18px; + background: linear-gradient(135deg, #0052cc 0%, #00d2ff 100%); + border: 2px solid rgba(0, 210, 255, 0.4); + z-index: 35; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0 24px rgba(0, 210, 255, 0.3), 0 8px 32px rgba(0, 82, 204, 0.2); + user-select: none; + -webkit-user-select: none; + padding: 0; +} + +.minirkx-button:hover { + transform: scale(1.08); + box-shadow: 0 0 32px rgba(0, 210, 255, 0.5), 0 12px 48px rgba(0, 82, 204, 0.3); + border-color: rgba(0, 210, 255, 0.6); +} + +.minirkx-button:active { + transform: scale(0.95); +} + +.minirkx-button.open { + transform: rotate(45deg); +} + +.minirkx-logo { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; +} + +.minirkx-logo svg { + width: 100%; + height: 100%; + filter: drop-shadow(0 0 8px rgba(0, 210, 255, 0.4)); +} + +@keyframes minirkx-pulse-glow { + 0%, 100% { + box-shadow: 0 0 24px rgba(0, 210, 255, 0.3), 0 8px 32px rgba(0, 82, 204, 0.2); + } + 50% { + box-shadow: 0 0 36px rgba(0, 210, 255, 0.5), 0 12px 48px rgba(0, 82, 204, 0.35); + } +} + +.minirkx-button.pulse { + animation: minirkx-pulse-glow 2s ease-in-out infinite; +} + +/* Honeycomb Menu */ +.minirkx-menu { + position: fixed; + inset: 0; + z-index: 30; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; +} + +.minirkx-menu.open { + pointer-events: auto; + opacity: 1; +} + +.minirkx-menu-backdrop { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0); + transition: background 0.3s ease; + cursor: pointer; +} + +.minirkx-menu.open .minirkx-menu-backdrop { + background: rgba(0, 0, 0, 0.4); +} + +.minirkx-honeycomb { + position: absolute; + bottom: 32px; + right: 32px; + width: 360px; + height: 360px; + display: flex; + align-items: center; + justify-content: center; +} + +.minirkx-hex-item { + position: absolute; + width: 80px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transform: scale(0) translate(0, 0); + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.minirkx-menu.open .minirkx-hex-item { + opacity: 1; + transform: scale(1) translate(var(--tx), var(--ty)); +} + +.minirkx-hex-item:nth-child(1) { --tx: 0px; --ty: -90px; transition-delay: 0.05s; } +.minirkx-hex-item:nth-child(2) { --tx: 78px; --ty: -45px; transition-delay: 0.1s; } +.minirkx-hex-item:nth-child(3) { --tx: 78px; --ty: 45px; transition-delay: 0.15s; } +.minirkx-hex-item:nth-child(4) { --tx: 0px; --ty: 90px; transition-delay: 0.2s; } +.minirkx-hex-item:nth-child(5) { --tx: -78px; --ty: 45px; transition-delay: 0.25s; } +.minirkx-hex-item:nth-child(6) { --tx: -78px; --ty: -45px; transition-delay: 0.3s; } + +.minirkx-hex-content { + width: 100%; + height: 100%; + border-radius: 12px; + background: linear-gradient(135deg, rgba(0, 82, 204, 0.8) 0%, rgba(0, 210, 255, 0.8) 100%); + border: 2px solid rgba(0, 210, 255, 0.4); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + transition: all 0.3s ease; + box-shadow: 0 4px 16px rgba(0, 82, 204, 0.2); + user-select: none; + -webkit-user-select: none; +} + +.minirkx-hex-item:hover .minirkx-hex-content { + transform: scale(1.12); + border-color: rgba(0, 210, 255, 0.8); + box-shadow: 0 8px 32px rgba(0, 210, 255, 0.4); + background: linear-gradient(135deg, rgba(0, 82, 204, 1) 0%, rgba(0, 210, 255, 1) 100%); +} + +.minirkx-hex-icon { + font-size: 24px; + line-height: 1; +} + +.minirkx-hex-label { + font-size: 10px; + font-weight: 700; + color: white; + text-align: center; + letter-spacing: 0.5px; + text-transform: uppercase; +} + +@media (max-width: 768px) { + .minirkx-button { + width: 64px; + height: 64px; + bottom: 24px; + right: 24px; + } + + .minirkx-honeycomb { + width: 280px; + height: 280px; + bottom: 24px; + right: 24px; + } + + .minirkx-hex-item:nth-child(1) { --tx: 0px; --ty: -70px; } + .minirkx-hex-item:nth-child(2) { --tx: 60px; --ty: -35px; } + .minirkx-hex-item:nth-child(3) { --tx: 60px; --ty: 35px; } + .minirkx-hex-item:nth-child(4) { --tx: 0px; --ty: 70px; } + .minirkx-hex-item:nth-child(5) { --tx: -60px; --ty: 35px; } + .minirkx-hex-item:nth-child(6) { --tx: -60px; --ty: -35px; } + + .minirkx-hex-content { + width: 72px; + height: 72px; + } + + .minirkx-hex-icon { + font-size: 20px; + } + + .minirkx-hex-label { + font-size: 9px; + } +}