Skip to content

Commit 918c469

Browse files
committed
fit and finish
1 parent f066759 commit 918c469

13 files changed

Lines changed: 388 additions & 52 deletions

File tree

ClientApp/src/App.css

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,201 @@
11
@import 'tailwindcss';
22

33
@source '../../Views/';
4+
5+
/* Button Base Styles */
6+
.btn {
7+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
8+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed;
9+
}
10+
11+
.btn-sm {
12+
@apply px-3 py-1.5 text-xs;
13+
}
14+
15+
.btn-lg {
16+
@apply px-6 py-3 text-base;
17+
}
18+
19+
/* Primary Button */
20+
.btn-indigo {
21+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
22+
@apply bg-indigo-600 border-indigo-600 text-white hover:bg-indigo-700 hover:border-indigo-700;
23+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed;
24+
}
25+
26+
/* Secondary Button */
27+
.btn-gray {
28+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
29+
@apply bg-white border-gray-300 text-gray-700 hover:bg-gray-50 hover:border-gray-400;
30+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed;
31+
}
32+
33+
/* Success Button */
34+
.btn-green {
35+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
36+
@apply bg-green-600 border-green-600 text-white hover:bg-green-700 hover:border-green-700;
37+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed;
38+
}
39+
40+
/* Danger Button */
41+
.btn-red {
42+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
43+
@apply bg-red-600 border-red-600 text-white hover:bg-red-700 hover:border-red-700;
44+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-50 disabled:cursor-not-allowed;
45+
}
46+
47+
/* Warning Button */
48+
.btn-yellow {
49+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
50+
@apply bg-yellow-600 border-yellow-600 text-white hover:bg-yellow-700 hover:border-yellow-700;
51+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 disabled:opacity-50 disabled:cursor-not-allowed;
52+
}
53+
54+
/* Outline Variants */
55+
.btn-outline-indigo {
56+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
57+
@apply bg-transparent border-indigo-600 text-indigo-600 hover:bg-indigo-50;
58+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed;
59+
}
60+
61+
.btn-outline-gray {
62+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
63+
@apply bg-transparent border-gray-300 text-gray-700 hover:bg-gray-50;
64+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed;
65+
}
66+
67+
/* Ghost/Text Button */
68+
.btn-ghost {
69+
@apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md border transition-colors duration-200;
70+
@apply bg-transparent border-transparent text-gray-600 hover:bg-gray-100;
71+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed;
72+
}
73+
74+
/* Link Button */
75+
.btn-link {
76+
@apply inline-flex items-center text-sm font-medium text-indigo-600 hover:text-indigo-500 underline-offset-2 hover:underline;
77+
@apply focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2;
78+
}
79+
80+
/* Form Controls */
81+
.form-input {
82+
@apply block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400;
83+
@apply focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 transition-colors duration-200;
84+
}
85+
86+
.form-input[aria-invalid="true"] {
87+
@apply border-red-300 focus:border-red-500 focus:ring-red-500;
88+
}
89+
90+
.form-select {
91+
@apply block w-full px-3 py-2 pr-10 border border-gray-300 rounded-md shadow-sm bg-white placeholder-gray-400;
92+
@apply focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 transition-colors duration-200;
93+
}
94+
95+
.form-textarea {
96+
@apply block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 resize-y;
97+
@apply focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 transition-colors duration-200;
98+
}
99+
100+
.form-checkbox {
101+
@apply h-4 w-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500 focus:ring-offset-0;
102+
}
103+
104+
.form-radio {
105+
@apply h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500 focus:ring-offset-0;
106+
}
107+
108+
/* Loading Spinner */
109+
.btn-spinner {
110+
@apply inline-block h-4 w-4 border-2 border-current border-t-transparent rounded-full animate-spin opacity-75;
111+
}
112+
113+
/* Form Group */
114+
.form-group {
115+
@apply space-y-1;
116+
}
117+
118+
.form-label {
119+
@apply block text-sm font-medium text-gray-700;
120+
}
121+
122+
.form-error {
123+
@apply text-sm text-red-600;
124+
}
125+
126+
.form-help {
127+
@apply text-sm text-gray-500;
128+
}
129+
130+
/* Card */
131+
.card {
132+
@apply bg-white shadow-sm rounded-lg border border-gray-200;
133+
}
134+
135+
.card-header {
136+
@apply px-6 py-4 border-b border-gray-200;
137+
}
138+
139+
.card-body {
140+
@apply px-6 py-4;
141+
}
142+
143+
.card-footer {
144+
@apply px-6 py-4 border-t border-gray-200 bg-gray-50;
145+
}
146+
147+
/* Table */
148+
.table {
149+
@apply min-w-full divide-y divide-gray-200;
150+
}
151+
152+
.table th {
153+
@apply px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50;
154+
}
155+
156+
.table td {
157+
@apply px-6 py-4 whitespace-nowrap text-sm text-gray-900;
158+
}
159+
160+
.table tbody tr:hover {
161+
@apply bg-gray-50;
162+
}
163+
164+
/* Badge */
165+
.badge {
166+
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
167+
}
168+
169+
.badge-gray {
170+
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800;
171+
}
172+
173+
.badge-green {
174+
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800;
175+
}
176+
177+
.badge-red {
178+
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800;
179+
}
180+
181+
.badge-yellow {
182+
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800;
183+
}
184+
185+
.badge-indigo {
186+
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800;
187+
}
188+
189+
/* Background Pattern */
190+
.bg-pattern {
191+
background-image: url('data:image/svg+xml,%3Csvg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg"%3E%3Cg fill="none" fill-rule="evenodd"%3E%3Cg fill="%23ffffff" fill-opacity="0.05"%3E%3Ccircle cx="30" cy="30" r="2"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E');
192+
}
193+
194+
/* Animation Delays */
195+
.animation-delay-1000 {
196+
animation-delay: 1s;
197+
}
198+
199+
.animation-delay-2000 {
200+
animation-delay: 2s;
201+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { ComponentProps } from 'react';
2+
import cx from 'classnames';
3+
4+
type ButtonVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'outline-primary' | 'outline-secondary' | 'ghost' | 'link';
5+
type ButtonSize = 'sm' | 'md' | 'lg';
6+
7+
interface ButtonProps extends ComponentProps<'button'> {
8+
variant?: ButtonVariant;
9+
size?: ButtonSize;
10+
}
11+
12+
const variantClasses: Record<ButtonVariant, string> = {
13+
primary: 'btn-indigo',
14+
secondary: 'btn-gray',
15+
success: 'btn-green',
16+
danger: 'btn-red',
17+
warning: 'btn-yellow',
18+
'outline-primary': 'btn-outline-indigo',
19+
'outline-secondary': 'btn-outline-gray',
20+
ghost: 'btn-ghost',
21+
link: 'btn-link',
22+
};
23+
24+
const sizeClasses: Record<ButtonSize, string> = {
25+
sm: 'btn-sm',
26+
md: '',
27+
lg: 'btn-lg',
28+
};
29+
30+
export default function Button({
31+
variant = 'primary',
32+
size = 'md',
33+
className,
34+
children,
35+
...props
36+
}: ButtonProps) {
37+
const classes = cx(
38+
variantClasses[variant],
39+
sizeClasses[size],
40+
className
41+
);
42+
43+
return (
44+
<button className={classes} {...props}>
45+
{children}
46+
</button>
47+
);
48+
}

ClientApp/src/Components/Button/DeleteButton.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
import { ComponentProps } from 'react';
2+
import cx from 'classnames';
23

34
interface Props extends ComponentProps<'button'> {
45
onDelete: () => void;
6+
variant?: 'link' | 'button';
57
}
68

7-
export default function DeleteButton({ onDelete, children }: Props) {
9+
export default function DeleteButton({
10+
onDelete,
11+
children,
12+
variant = 'link',
13+
className,
14+
...props
15+
}: Props) {
16+
const classes = cx(
17+
variant === 'link'
18+
? 'text-red-600 hover:text-red-800 hover:underline focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2'
19+
: 'btn-red',
20+
className
21+
);
22+
823
return (
924
<button
10-
className="text-red-600 hover:underline focus:outline-none"
25+
className={classes}
1126
type="button"
1227
tabIndex={-1}
1328
onClick={onDelete}
29+
{...props}
1430
>
1531
{children}
1632
</button>

ClientApp/src/Components/Button/LoadingButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function LoadingButton({
1212
...props
1313
}: LoadingButtonProps) {
1414
const classNames = cx(
15-
'flex items-center',
15+
'flex items-center cursor-pointer',
1616
'focus:outline-none',
1717
{
1818
'pointer-events-none bg-opacity-75 select-none': loading,
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
import { ComponentProps } from 'react';
2+
import cx from 'classnames';
23

34
interface CheckboxInputProps extends ComponentProps<'input'> {
45
label?: string;
56
}
67

7-
export function CheckboxInput({ label, name, ...props }: CheckboxInputProps) {
8+
export function CheckboxInput({
9+
label,
10+
name,
11+
className,
12+
...props
13+
}: CheckboxInputProps) {
814
return (
9-
<label className="flex items-center select-none" htmlFor={name}>
15+
<label className="flex items-center select-none cursor-pointer" htmlFor={name}>
1016
<input
1117
id={name}
1218
name={name}
1319
type="checkbox"
14-
className="form-checkbox mr-2 rounded text-indigo-600 focus:ring-indigo-600"
20+
className={cx('form-checkbox mr-2', className)}
1521
{...props}
1622
/>
17-
<span className="text-sm">{label}</span>
23+
<span className="text-sm text-gray-700">{label}</span>
1824
</label>
1925
);
2026
}

ClientApp/src/Components/Form/FieldGroup.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@ interface FieldGroupProps {
22
name?: string;
33
label?: string;
44
error?: string;
5+
helpText?: string;
56
children: React.ReactNode;
67
}
78

89
export default function FieldGroup({
910
label,
1011
name,
1112
error,
13+
helpText,
1214
children,
1315
}: FieldGroupProps) {
1416
return (
15-
<div className="space-y-2">
17+
<div className="form-group">
1618
{label && (
17-
<label
18-
className="block text-gray-800 select-none"
19-
htmlFor={name}
20-
>
21-
{label}:
19+
<label className="form-label" htmlFor={name}>
20+
{label}
2221
</label>
2322
)}
2423
{children}
25-
{error && <div className="mt-2 text-sm text-red-500">{error}</div>}
24+
{helpText && <div className="form-help">{helpText}</div>}
25+
{error && <div className="form-error">{error}</div>}
2626
</div>
2727
);
2828
}

0 commit comments

Comments
 (0)