click animation and fixes
This commit is contained in:
parent
e6bc95b7e6
commit
12ed7e1b9f
27
App.tsx
27
App.tsx
@ -22,6 +22,7 @@ const defaultConfig = {
|
||||
titleSize: 'medium',
|
||||
subtitleSize: 'medium',
|
||||
alignment: 'middle',
|
||||
horizontalAlignment: 'middle',
|
||||
clock: {
|
||||
enabled: true,
|
||||
size: 'medium',
|
||||
@ -235,16 +236,16 @@ const App: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getTileSizeClass = (size: string) => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 'w-28 h-28';
|
||||
case 'medium':
|
||||
return 'w-32 h-32';
|
||||
case 'large':
|
||||
return 'w-36 h-36';
|
||||
const getHorizontalAlignmentClass = (alignment: string) => {
|
||||
switch (alignment) {
|
||||
case 'left':
|
||||
return 'justify-start';
|
||||
case 'middle':
|
||||
return 'justify-center';
|
||||
case 'right':
|
||||
return 'justify-end';
|
||||
default:
|
||||
return 'w-32 h-32';
|
||||
return 'justify-center';
|
||||
}
|
||||
};
|
||||
|
||||
@ -312,8 +313,8 @@ const App: React.FC = () => {
|
||||
<div className="flex flex-col gap-8 w-full mt-16">
|
||||
{categories.map((category) => (
|
||||
<div key={category.id} className="w-full">
|
||||
<div className="flex justify-center items-center mb-4">
|
||||
<h2 className="text-2xl font-bold text-white text-center">{category.name}</h2>
|
||||
<div className={`flex ${getHorizontalAlignmentClass(config.horizontalAlignment)} items-center mb-4 w-full ${config.horizontalAlignment !== 'middle' ? 'px-8' : ''}`}>
|
||||
<h2 className={`text-2xl font-bold text-white ${config.horizontalAlignment === 'left' ? 'text-left' : config.horizontalAlignment === 'right' ? 'text-right' : 'text-center'} ${config.horizontalAlignment !== 'middle' ? 'w-full' : ''}`}>{category.name}</h2>
|
||||
{isEditing && (
|
||||
<button
|
||||
onClick={() => {
|
||||
@ -326,7 +327,7 @@ const App: React.FC = () => {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-6">
|
||||
<div className={`flex flex-wrap ${getHorizontalAlignmentClass(config.horizontalAlignment)} gap-6`}>
|
||||
{category.websites.map((website) => (
|
||||
<WebsiteTile
|
||||
key={website.id}
|
||||
@ -334,7 +335,7 @@ const App: React.FC = () => {
|
||||
isEditing={isEditing}
|
||||
onEdit={setEditingWebsite}
|
||||
onMove={handleMoveWebsite}
|
||||
className={getTileSizeClass(config.tileSize)}
|
||||
tileSize={config.tileSize}
|
||||
/>
|
||||
))}
|
||||
{isEditing && (
|
||||
|
@ -20,6 +20,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
subtitleSize: currentConfig.subtitleSize || 'medium',
|
||||
alignment: currentConfig.alignment || 'middle',
|
||||
tileSize: currentConfig.tileSize || 'medium',
|
||||
horizontalAlignment: currentConfig.horizontalAlignment || 'middle',
|
||||
wallpaperBlur: currentConfig.wallpaperBlur || 0,
|
||||
wallpaperBrightness: currentConfig.wallpaperBrightness || 100,
|
||||
wallpaperOpacity: currentConfig.wallpaperOpacity || 100,
|
||||
@ -328,6 +329,19 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-slate-300 text-sm font-semibold">Horizontal Alignment</label>
|
||||
<Dropdown
|
||||
name="horizontalAlignment"
|
||||
value={config.horizontalAlignment}
|
||||
onChange={handleChange}
|
||||
options={[
|
||||
{ value: 'left', label: 'Left' },
|
||||
{ value: 'middle', label: 'Middle' },
|
||||
{ value: 'right', label: 'Right' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Website } from '../types';
|
||||
import { icons, ArrowLeft, ArrowRight, Pencil } from 'lucide-react';
|
||||
|
||||
@ -7,30 +7,85 @@ interface WebsiteTileProps {
|
||||
isEditing: boolean;
|
||||
onEdit: (website: Website) => void;
|
||||
onMove: (website: Website, direction: 'left' | 'right') => void;
|
||||
className?: string;
|
||||
tileSize?: string;
|
||||
}
|
||||
|
||||
const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, onMove, className }) => {
|
||||
const getTileSizeClass = (size: string | undefined) => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 'w-28 h-28';
|
||||
case 'medium':
|
||||
return 'w-32 h-32';
|
||||
case 'large':
|
||||
return 'w-36 h-36';
|
||||
default:
|
||||
return 'w-32 h-32';
|
||||
}
|
||||
};
|
||||
|
||||
const getIconSize = (size: string | undefined) => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 8;
|
||||
case 'medium':
|
||||
return 10;
|
||||
case 'large':
|
||||
return 12;
|
||||
default:
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, onMove, tileSize }) => {
|
||||
const LucideIcon = icons[website.icon as keyof typeof icons];
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
if (isEditing) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
|
||||
// Simulate loading time (dev purpose)
|
||||
// e.preventDefault();
|
||||
// setTimeout(() => {
|
||||
// setIsLoading(false);
|
||||
// }, 3500); // Small delay to show spinner before navigation
|
||||
};
|
||||
|
||||
const iconSizeClass = `w-${getIconSize(tileSize)} h-${getIconSize(tileSize)}`;
|
||||
const iconSizeLoadingClass = `w-${getIconSize(tileSize) - 4} h-${getIconSize(tileSize) - 4}`;
|
||||
|
||||
return (
|
||||
<div className={`relative ${className} transition-all duration-300 ease-in-out`}>
|
||||
<div className={`relative ${getTileSizeClass(tileSize)} transition-all duration-300 ease-in-out`}>
|
||||
<a
|
||||
href={isEditing ? undefined : website.url}
|
||||
target="_self"
|
||||
rel="noopener noreferrer"
|
||||
onClick={handleClick}
|
||||
className="group flex flex-col items-center justify-center p-4 bg-black/25 backdrop-blur-md border border-white/10 rounded-2xl w-full h-full transform transition-all duration-300 ease-in-out hover:scale-105 hover:bg-white/25 shadow-lg focus:outline-none focus:ring-2 focus:ring-cyan-400 focus:ring-opacity-75"
|
||||
>
|
||||
<div className="mb-2 transition-transform duration-300 group-hover:-translate-y-1">
|
||||
{LucideIcon ? (
|
||||
<LucideIcon className="h-10 w-10 text-white" />
|
||||
) : (
|
||||
<img src={website.icon} alt={`${website.name} icon`} className="h-10 w-10 object-contain" />
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center mb-6">
|
||||
<svg className="animate-spin h-10 w-10 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<div className={`flex items-center transition-all duration-300 ease-in ${isLoading ? 'mt-18' : 'flex-col'} ${isLoading ? 'gap-2' : ''}`}>
|
||||
<div className={`transition-all duration-300 ease-in ${isLoading ? iconSizeLoadingClass : iconSizeClass}`}>
|
||||
{LucideIcon ? (
|
||||
<LucideIcon className={`text-white ${isLoading ? iconSizeLoadingClass : iconSizeClass}`} />
|
||||
) : (
|
||||
<img src={website.icon} alt={`${website.name} icon`} className="object-contain" />
|
||||
)}
|
||||
</div>
|
||||
<span className={`text-slate-100 font-medium text-base tracking-wide text-center transition-all duration-300 ease-in ${isLoading ? 'text-sm' : ''}`}>
|
||||
{website.name}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-slate-100 font-medium text-base tracking-wide text-center">
|
||||
{website.name}
|
||||
</span>
|
||||
</a>
|
||||
{isEditing && (
|
||||
<div className="absolute bottom-2 left-0 right-0 flex justify-center gap-2">
|
||||
|
Loading…
x
Reference in New Issue
Block a user