Added plus icon and tabbar animations
This commit is contained in:
parent
d542a9fcbc
commit
e23add8881
8
.cursor/mcp.json
Normal file
8
.cursor/mcp.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"Framelink Figma MCP": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "figma-developer-mcp", "--figma-api-key=figd_EBG-hMtUUCufQTlaytbgfziehQe7RDA3u4kyUEHL", "--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,3 +39,7 @@
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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<TabBarProps> = ({
|
||||
}) => {
|
||||
const [active, setActive] = useState(activeTab || tabs[0]?.id || '');
|
||||
const [ActiveComponent, setActiveComponent] = useState<React.ComponentType<any> | null>(null);
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab && activeTab !== active) {
|
||||
@ -31,18 +33,46 @@ export const TabView: React.FC<TabBarProps> = ({
|
||||
useEffect(() => {
|
||||
const activeTabData = tabs.find(tab => tab.id === active);
|
||||
if (activeTabData) {
|
||||
// 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) => {
|
||||
if (tabId !== active && !isTransitioning) {
|
||||
setActive(tabId);
|
||||
onTabChange?.(tabId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tab-container">
|
||||
<div className="tab-content">
|
||||
<div
|
||||
className="tab-content"
|
||||
ref={contentRef}
|
||||
style={{
|
||||
transition: 'opacity 0.3s ease, transform 0.3s ease',
|
||||
}}
|
||||
>
|
||||
{ActiveComponent && <ActiveComponent />}
|
||||
</div>
|
||||
<div className="tab-bar">
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<string | null>(null);
|
||||
const [userName, setUserName] = useState('John');
|
||||
const [isTextboxOpen, setIsTextboxOpen] = useState(false);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const textInputRef = useRef<HTMLInputElement>(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 }) => (
|
||||
<div className="event-item">
|
||||
<div className="event-title">{event.title}</div>
|
||||
@ -82,7 +104,12 @@ const Home: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="home-container">
|
||||
<h1 className="greeting">Hallo {userName}!</h1>
|
||||
<div className="header-container">
|
||||
<h1 className="greeting">Hallo {userName}</h1>
|
||||
<button className="add-button" onClick={handleOpenTextbox}>
|
||||
<span>+</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section className="events-section">
|
||||
<h2 className="section-title">Heute</h2>
|
||||
@ -111,7 +138,7 @@ const Home: React.FC = () => {
|
||||
</section>
|
||||
|
||||
<section className="events-section">
|
||||
<h2 className="section-title">Diese Woche</h2>
|
||||
<h2 className="section-title">Restliche Woche</h2>
|
||||
<div className="events-list week-events">
|
||||
{weekEvents.length > 0 ? (
|
||||
weekEvents.map(event => (
|
||||
@ -122,6 +149,20 @@ const Home: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{isTextboxOpen && (
|
||||
<div className={`textbox-overlay ${isClosing ? 'closing' : ''}`}>
|
||||
<div className={`textbox-container ${isClosing ? 'closing' : ''}`}>
|
||||
<button className="close-button" onClick={handleCloseTextbox}>×</button>
|
||||
<input
|
||||
ref={textInputRef}
|
||||
className="large-textbox"
|
||||
type="text"
|
||||
placeholder="Neuen Termin eingeben..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user