Skip to content

Commit cfb3cc2

Browse files
makingclaude
andcommitted
Modernize Todo UI
- Added Tailwind CSS for modern styling - Improved overall responsive layout with better components - Added React Icons for enhanced visuals - Created cool gradient title effect - Fixed table layout to prevent content wrapping - Enhanced UX with loading states and animations - Improved toggle switch for completed tasks - Added task count information 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent fd2f1d5 commit cfb3cc2

File tree

8 files changed

+1468
-193
lines changed

8 files changed

+1468
-193
lines changed

todo-frontend/ui/package-lock.json

Lines changed: 1103 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

todo-frontend/ui/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@
1818
"@types/react": "^18.3.12",
1919
"@types/react-dom": "^18.3.1",
2020
"@vitejs/plugin-react": "^4.3.3",
21+
"autoprefixer": "^10.4.15",
2122
"eslint": "^9.13.0",
2223
"eslint-plugin-react-hooks": "^5.0.0",
2324
"eslint-plugin-react-refresh": "^0.4.14",
2425
"globals": "^15.11.0",
26+
"postcss": "^8.4.29",
27+
"react-icons": "^4.11.0",
28+
"tailwindcss": "^3.3.3",
2529
"typescript": "~5.6.2",
2630
"typescript-eslint": "^8.11.0",
2731
"vite": "^5.4.10"

todo-frontend/ui/postcss.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}

todo-frontend/ui/src/TodoList.tsx

Lines changed: 187 additions & 99 deletions
Large diffs are not rendered by default.

todo-frontend/ui/src/components.tsx

Lines changed: 89 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import {ReactNode, CSSProperties, ChangeEvent} from 'react';
1+
import { ReactNode, CSSProperties, ChangeEvent, ButtonHTMLAttributes } from 'react';
22

33
// Container for the whole TodoList app
44
interface ContainerProps {
55
children: ReactNode;
66
}
77

8-
const Container = ({children}: ContainerProps) => (
9-
<div style={{maxWidth: '800px', margin: '0 auto', fontFamily: 'Arial, sans-serif'}}>
8+
const Container = ({ children }: ContainerProps) => (
9+
<div className="max-w-4xl mx-auto px-4 py-8 font-sans">
1010
{children}
1111
</div>
1212
);
@@ -16,66 +16,72 @@ interface HeaderProps {
1616
children: ReactNode;
1717
}
1818

19-
const Header = ({children}: HeaderProps) => (
20-
<h1 style={{textAlign: 'center', color: '#333', marginBottom: '20px'}}>
19+
const Header = ({ children }: HeaderProps) => (
20+
<h1 className="text-4xl font-bold text-center text-transparent bg-clip-text bg-gradient-to-r from-primary-dark via-primary to-primary-light drop-shadow-sm py-2 mb-6">
2121
{children}
2222
</h1>
2323
);
2424

2525
// Styled button with hover effect
26-
interface StyledButtonProps {
26+
interface StyledButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
2727
children: ReactNode;
28-
onClick?: () => void;
29-
type?: 'button' | 'submit' | 'reset';
30-
style?: CSSProperties;
28+
variant?: 'primary' | 'success' | 'danger';
3129
}
3230

33-
const StyledButton = ({children, onClick, type = 'button', style}: StyledButtonProps) => (
34-
<button
35-
type={type}
36-
onClick={onClick}
37-
style={{
38-
padding: '10px 20px',
39-
fontSize: '16px',
40-
backgroundColor: '#28a745',
41-
color: '#fff',
42-
border: 'none',
43-
borderRadius: '4px',
44-
cursor: 'pointer',
45-
transition: 'background-color 0.3s',
46-
...style,
47-
}}
48-
onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = '#218838')}
49-
onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = '#28a745')}
50-
>
51-
{children}
52-
</button>
53-
);
31+
const StyledButton = ({
32+
children,
33+
onClick,
34+
type = 'button',
35+
variant = 'primary',
36+
className = '',
37+
...rest
38+
}: StyledButtonProps) => {
39+
const baseClass = 'btn';
40+
const variantClass = `btn-${variant}`;
41+
42+
return (
43+
<button
44+
type={type}
45+
onClick={onClick}
46+
className={`${baseClass} ${variantClass} ${className}`}
47+
{...rest}
48+
>
49+
{children}
50+
</button>
51+
);
52+
};
5453

5554
// Small icon button for action controls (toggle and delete)
5655
interface IconButtonProps {
5756
icon: ReactNode;
58-
color: string;
5957
onClick: () => void;
6058
title: string;
59+
variant?: 'primary' | 'success' | 'danger';
6160
}
6261

63-
const IconButton = ({icon, color, onClick, title}: IconButtonProps) => (
64-
<button
65-
onClick={onClick}
66-
title={title}
67-
style={{
68-
backgroundColor: 'transparent',
69-
border: 'none',
70-
cursor: 'pointer',
71-
fontSize: '16px',
72-
color,
73-
margin: '0 5px',
74-
}}
75-
>
76-
{icon}
77-
</button>
78-
);
62+
const IconButton = ({
63+
icon,
64+
onClick,
65+
title,
66+
variant = 'primary'
67+
}: IconButtonProps) => {
68+
const variantClasses = {
69+
primary: 'text-primary hover:text-primary-dark',
70+
success: 'text-success hover:text-success-dark',
71+
danger: 'text-danger hover:text-danger-dark',
72+
};
73+
74+
return (
75+
<button
76+
onClick={onClick}
77+
title={title}
78+
className={`bg-transparent border-none cursor-pointer p-2 transition-colors ${variantClasses[variant]}`}
79+
aria-label={title}
80+
>
81+
{icon}
82+
</button>
83+
);
84+
};
7985

8086
// Input field for entering todo title
8187
interface StyledInputProps {
@@ -84,21 +90,14 @@ interface StyledInputProps {
8490
placeholder?: string;
8591
}
8692

87-
const StyledInput = ({value, onChange, placeholder}: StyledInputProps) => (
93+
const StyledInput = ({ value, onChange, placeholder }: StyledInputProps) => (
8894
<input
8995
type="text"
9096
value={value}
9197
onChange={onChange}
9298
placeholder={placeholder}
93-
required={true}
94-
style={{
95-
padding: '10px',
96-
width: '70%',
97-
fontSize: '16px',
98-
border: '1px solid #ddd',
99-
borderRadius: '4px',
100-
marginRight: '10px',
101-
}}
99+
required
100+
className="px-4 py-2 border border-gray-300 rounded-md w-full md:w-96 shadow-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
102101
/>
103102
);
104103

@@ -107,16 +106,12 @@ interface StyledTableProps {
107106
children: ReactNode;
108107
}
109108

110-
const StyledTable = ({children}: StyledTableProps) => (
111-
<table
112-
style={{
113-
width: '100%',
114-
borderCollapse: 'collapse',
115-
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
116-
}}
117-
>
118-
{children}
119-
</table>
109+
const StyledTable = ({ children }: StyledTableProps) => (
110+
<div className="w-full overflow-x-auto rounded-lg shadow-md bg-white animate-fade-in">
111+
<table className="w-full border-collapse">
112+
{children}
113+
</table>
114+
</div>
120115
);
121116

122117
// Table cell component with optional width
@@ -127,22 +122,32 @@ interface TableCellProps {
127122
width?: string;
128123
}
129124

130-
const TableCell = ({children, header = false, center = false, width}: TableCellProps) => {
131-
const baseStyle: CSSProperties = {
132-
padding: '10px',
133-
textAlign: center ? 'center' : 'left',
134-
backgroundColor: header ? '#f4f4f4' : 'inherit',
135-
borderBottom: header ? '2px solid #ddd' : '1px solid #ddd',
136-
width: width || 'auto',
137-
whiteSpace: 'nowrap',
138-
overflow: 'hidden',
139-
textOverflow: 'ellipsis',
140-
};
141-
125+
const TableCell = ({
126+
children,
127+
header = false,
128+
center = false,
129+
width
130+
}: TableCellProps) => {
131+
// Added truncate and fixed height classes to prevent line wrapping
132+
const baseClass = "p-3 text-sm border-b border-gray-200 whitespace-nowrap overflow-hidden text-ellipsis";
133+
const alignClass = center ? "text-center" : "text-left";
134+
const headerClass = header ? "bg-gray-100 font-semibold text-gray-700" : "";
135+
136+
const style: CSSProperties = {};
137+
if (width) {
138+
style.width = width;
139+
// Add max-width to ensure proper truncation
140+
style.maxWidth = width;
141+
}
142+
142143
return header ? (
143-
<th style={baseStyle}>{children}</th>
144+
<th className={`${baseClass} ${alignClass} ${headerClass}`} style={style}>
145+
{children}
146+
</th>
144147
) : (
145-
<td style={baseStyle}>{children}</td>
148+
<td className={`${baseClass} ${alignClass}`} style={style}>
149+
{children}
150+
</td>
146151
);
147152
};
148153

@@ -151,9 +156,9 @@ interface WelcomeMessageProps {
151156
username: string;
152157
}
153158

154-
const WelcomeMessage = ({username}: WelcomeMessageProps) => (
155-
<p style={{textAlign: 'center', fontSize: '18px', color: '#555'}}>
156-
Welcome, {username}!
159+
const WelcomeMessage = ({ username }: WelcomeMessageProps) => (
160+
<p className="text-center text-lg text-gray-600 mb-6">
161+
Welcome, <span className="font-semibold">{username}</span>!
157162
</p>
158163
);
159164

todo-frontend/ui/src/index.css

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
@layer base {
6+
body {
7+
@apply bg-gray-50 text-gray-900 min-h-screen;
8+
}
9+
10+
h1, h2, h3, h4, h5, h6 {
11+
@apply font-semibold;
12+
}
13+
}
14+
15+
@layer components {
16+
.todo-row {
17+
@apply transition-all duration-200 hover:bg-gray-50;
18+
}
19+
20+
.todo-row-completed {
21+
@apply bg-green-50;
22+
}
23+
24+
.btn {
25+
@apply px-4 py-2 rounded-md font-medium transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
26+
}
27+
28+
.btn-primary {
29+
@apply bg-primary text-white hover:bg-primary-dark focus:ring-primary;
30+
}
31+
32+
.btn-success {
33+
@apply bg-success text-white hover:bg-success-dark focus:ring-success;
34+
}
35+
36+
.btn-danger {
37+
@apply bg-danger text-white hover:bg-danger-dark focus:ring-danger;
38+
}
39+
}

todo-frontend/ui/src/main.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {StrictMode} from 'react'
22
import {createRoot} from 'react-dom/client'
33
import TodoList from './TodoList.tsx'
4+
import './index.css'
45

56
createRoot(document.getElementById('root')!).render(
67
<StrictMode>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/** @type {import('tailwindcss').Config} */
2+
export default {
3+
content: [
4+
'./index.html',
5+
'./src/**/*.{js,ts,jsx,tsx}',
6+
],
7+
theme: {
8+
extend: {
9+
colors: {
10+
primary: {
11+
light: '#4dabf7',
12+
DEFAULT: '#228be6',
13+
dark: '#1971c2',
14+
},
15+
success: {
16+
light: '#69db7c',
17+
DEFAULT: '#40c057',
18+
dark: '#2b9a3f',
19+
},
20+
danger: {
21+
light: '#ff8787',
22+
DEFAULT: '#fa5252',
23+
dark: '#e03131',
24+
}
25+
},
26+
animation: {
27+
'fade-in': 'fadeIn 0.3s ease-in-out',
28+
},
29+
keyframes: {
30+
fadeIn: {
31+
'0%': { opacity: 0 },
32+
'100%': { opacity: 1 },
33+
},
34+
},
35+
},
36+
},
37+
plugins: [],
38+
}
39+

0 commit comments

Comments
 (0)