From e23add88810b42fe5436f7e35bb27ff4ad8f40cc Mon Sep 17 00:00:00 2001 From: Tim Lappe Date: Sat, 26 Apr 2025 03:43:59 +0200 Subject: [PATCH] Added plus icon and tabbar animations --- .cursor/mcp.json | 8 + frontend/src/App.css | 4 + .../src/components/navigation/TabView.css | 45 ++++++ .../src/components/navigation/TabView.tsx | 40 ++++- frontend/src/index.css | 4 +- frontend/src/pages/home/Home.css | 152 ++++++++++++++++-- frontend/src/pages/home/Home.tsx | 47 +++++- 7 files changed, 279 insertions(+), 21 deletions(-) create mode 100644 .cursor/mcp.json diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000..7f3354b --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "Framelink Figma MCP": { + "command": "npx", + "args": ["-y", "figma-developer-mcp", "--figma-api-key=figd_EBG-hMtUUCufQTlaytbgfziehQe7RDA3u4kyUEHL", "--stdio"] + } + } + } \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css index 76a7d83..0dea1bf 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -39,3 +39,7 @@ transform: rotate(360deg); } } + +button { + -webkit-tap-highlight-color: transparent !important; +} \ No newline at end of file diff --git a/frontend/src/components/navigation/TabView.css b/frontend/src/components/navigation/TabView.css index 340665f..d4d34a8 100644 --- a/frontend/src/components/navigation/TabView.css +++ b/frontend/src/components/navigation/TabView.css @@ -11,6 +11,17 @@ box-sizing: border-box; } +@keyframes fadeInTab { + from { + opacity: 0; + transform: translateX(10px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + .tab-bar { display: flex; background-color: #ffffff; @@ -38,6 +49,21 @@ transition: all 0.3s ease; border-radius: 12px; margin: 0 4px; + position: relative; + overflow: hidden; +} + +.tab-bar-item::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 3px; + background-color: #3182ce; + transition: all 0.3s ease; + transform: translateX(-50%); + border-radius: 3px 3px 0 0; } .tab-bar-item:hover { @@ -45,6 +71,10 @@ background-color: #f1f5f9; } +.tab-bar-item:hover::after { + width: 20px; +} + .tab-bar-item.active { color: #3182ce; background-color: #ebf8ff; @@ -52,15 +82,29 @@ transform: translateY(-2px); } +.tab-bar-item.active::after { + width: 40px; +} + .tab-icon { font-size: 22px; margin-bottom: 6px; + transition: transform 0.3s ease; +} + +.tab-bar-item.active .tab-icon { + transform: scale(1.1); } .tab-label { font-size: 12px; font-weight: 600; white-space: nowrap; + transition: all 0.3s ease; +} + +.tab-bar-item.active .tab-label { + transform: scale(1.05); } .tab-content { @@ -76,4 +120,5 @@ width: 100%; max-width: 100%; box-sizing: border-box; + animation: fadeInTab 0.4s ease-out; } \ No newline at end of file diff --git a/frontend/src/components/navigation/TabView.tsx b/frontend/src/components/navigation/TabView.tsx index dd2825a..172bb58 100644 --- a/frontend/src/components/navigation/TabView.tsx +++ b/frontend/src/components/navigation/TabView.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; import './TabView.css'; @@ -21,6 +21,8 @@ export const TabView: React.FC = ({ }) => { const [active, setActive] = useState(activeTab || tabs[0]?.id || ''); const [ActiveComponent, setActiveComponent] = useState | null>(null); + const [isTransitioning, setIsTransitioning] = useState(false); + const contentRef = useRef(null); useEffect(() => { if (activeTab && activeTab !== active) { @@ -31,18 +33,46 @@ export const TabView: React.FC = ({ useEffect(() => { const activeTabData = tabs.find(tab => tab.id === active); if (activeTabData) { - setActiveComponent(() => activeTabData.component); + // Add transitioning effect + setIsTransitioning(true); + + // Fade out current content + if (contentRef.current) { + contentRef.current.style.opacity = '0'; + contentRef.current.style.transform = 'translateX(-10px)'; + } + + // Set new component after brief transition + setTimeout(() => { + setActiveComponent(() => activeTabData.component); + + // Fade in new content + if (contentRef.current) { + contentRef.current.style.opacity = '1'; + contentRef.current.style.transform = 'translateX(0)'; + } + + setIsTransitioning(false); + }, 100); } }, [active, tabs]); const handleTabClick = (tabId: string) => { - setActive(tabId); - onTabChange?.(tabId); + if (tabId !== active && !isTransitioning) { + setActive(tabId); + onTabChange?.(tabId); + } }; return (
-
+
{ActiveComponent && }
diff --git a/frontend/src/index.css b/frontend/src/index.css index 4ee0b61..7fda517 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,3 +1,5 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + * { box-sizing: border-box; } @@ -10,7 +12,7 @@ html, body { body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; diff --git a/frontend/src/pages/home/Home.css b/frontend/src/pages/home/Home.css index a9d2595..1c31a87 100644 --- a/frontend/src/pages/home/Home.css +++ b/frontend/src/pages/home/Home.css @@ -10,13 +10,141 @@ margin: 0 auto; width: 100%; box-sizing: border-box; + position: relative; +} + +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.875rem; } .greeting { font-size: 2rem; - font-weight: 700; - margin-bottom: 1.875rem; + font-weight: 600; color: #000000; + line-height: 0.75em; + font-family: 'Inter', sans-serif; + margin: 0; +} + +.add-button { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: #F2ADAD; + color: #000000; + border: none; + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, background-color 0.2s; +} + +.add-button:hover { + transform: scale(1.05); + background-color: #f09e9e; +} + +.add-button span { + margin-top: -2px; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideDown { + from { + transform: translateY(-20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slideUp { + from { + transform: translateY(0); + opacity: 1; + } + to { + transform: translateY(-20px); + opacity: 0; + } +} + +.textbox-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: transparent; + display: flex; + align-items: flex-start; + justify-content: center; + z-index: 1000; + padding-top: 20px; + animation: fadeIn 0.3s ease-out; +} + +.textbox-overlay.closing { + animation: fadeIn 0.3s ease-out reverse; +} + +.textbox-container { + background: white; + border-radius: 8px; + padding: 0; + width: 100%; + max-width: 90%; + position: relative; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); + animation: slideDown 0.3s ease-out; +} + +.textbox-container.closing { + animation: slideUp 0.3s ease-out; +} + +.large-textbox { + width: 100%; + height: 60px; + padding: 16px 50px 16px 16px; + border: none; + border-radius: 8px; + font-family: 'Inter', sans-serif; + font-size: 20px; + resize: none; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.close-button { + position: absolute; + top: 15px; + right: 15px; + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: #666; + z-index: 1001; +} + +.close-button:hover { + color: #000; } .events-section { @@ -24,10 +152,12 @@ } .section-title { - font-size: 1.5rem; - font-weight: 600; + font-size: 1.25rem; + font-weight: 700; margin-bottom: 1rem; color: #000000; + font-family: 'Inter', sans-serif; + line-height: 1.2em; } .events-list { @@ -60,13 +190,13 @@ } .event-item { - background-color: #ec6a5e; - border-radius: 1rem; + background-color: #F2ADAD; + border-radius: 0.5rem; padding: 1.125rem 1.25rem; - color: #ffffff; + color: #000000; cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; - box-shadow: 0 2px 8px rgba(236, 106, 94, 0.3); + box-shadow: 0 2px 8px rgba(242, 173, 173, 0.3); display: flex; flex-direction: column; min-height: 80px; @@ -74,7 +204,7 @@ .event-item:hover { transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(236, 106, 94, 0.4); + box-shadow: 0 4px 12px rgba(242, 173, 173, 0.4); } .event-title { @@ -105,14 +235,13 @@ } .error { - color: #ec6a5e; + color: #F2ADAD; } /* Responsive adjustments */ @media (max-width: 768px) { .greeting { font-size: 1.75rem; - margin-bottom: 1.5rem; } .section-title { @@ -142,7 +271,6 @@ .greeting { font-size: 1.5rem; - margin-bottom: 1.25rem; } .tomorrow-events .event-item { diff --git a/frontend/src/pages/home/Home.tsx b/frontend/src/pages/home/Home.tsx index e2d023b..fe44bc2 100644 --- a/frontend/src/pages/home/Home.tsx +++ b/frontend/src/pages/home/Home.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { getEvents, Event } from '../../lib/api/endpoints'; import './Home.css'; @@ -9,6 +9,9 @@ const Home: React.FC = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [userName, setUserName] = useState('John'); + const [isTextboxOpen, setIsTextboxOpen] = useState(false); + const [isClosing, setIsClosing] = useState(false); + const textInputRef = useRef(null); useEffect(() => { const fetchEvents = async () => { @@ -61,6 +64,25 @@ const Home: React.FC = () => { fetchEvents(); }, []); + useEffect(() => { + if (isTextboxOpen && textInputRef.current) { + textInputRef.current.focus(); + } + }, [isTextboxOpen]); + + const handleOpenTextbox = () => { + setIsClosing(false); + setIsTextboxOpen(true); + }; + + const handleCloseTextbox = () => { + setIsClosing(true); + setTimeout(() => { + setIsTextboxOpen(false); + setIsClosing(false); + }, 300); // Match animation duration + }; + const EventItem = ({ event }: { event: Event }) => (
{event.title}
@@ -82,7 +104,12 @@ const Home: React.FC = () => { return (
-

Hallo {userName}!

+
+

Hallo {userName}

+ +

Heute

@@ -111,7 +138,7 @@ const Home: React.FC = () => {
-

Diese Woche

+

Restliche Woche

{weekEvents.length > 0 ? ( weekEvents.map(event => ( @@ -122,6 +149,20 @@ const Home: React.FC = () => { )}
+ + {isTextboxOpen && ( +
+
+ + +
+
+ )}
); };