Skip to content

Commit 6bcea48

Browse files
Add modal focus management, file upload improvements, and UI fixes
- Auto-focus text input fields when modals open (accessibility.js) - File uploads now append instead of replace across all dropzones - Add confirm modal component with keyboard navigation - Fix policy filter row alignment on mobile - Improve toast/alert system with progress bars - Simplify modal opening with centralized focus handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 2e23125 commit 6bcea48

23 files changed

+1815
-334
lines changed

app/assets/css/components/alerts.css

Lines changed: 299 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,304 @@
1-
/* Alert Styles */
1+
/* Alert/Toast Notification Styles */
22

3-
/* ===== Alerts ===== */
4-
.alert {
5-
padding: var(--spacing-sm);
3+
/* ===== Toast Container ===== */
4+
.toast-container {
5+
position: fixed;
6+
z-index: 9999;
7+
pointer-events: none;
8+
padding: var(--spacing-md);
9+
}
10+
11+
/* Desktop: Top-right positioning */
12+
.toast-container {
13+
top: 80px;
14+
right: 0;
15+
left: auto;
16+
bottom: auto;
17+
width: 100%;
18+
max-width: 420px;
19+
display: flex;
20+
flex-direction: column;
21+
align-items: flex-end;
22+
gap: var(--spacing-sm);
23+
}
24+
25+
/* ===== Toast Base ===== */
26+
.toast {
27+
display: flex;
28+
align-items: flex-start;
29+
gap: var(--spacing-md);
30+
padding: var(--spacing-md) var(--spacing-lg);
31+
border-radius: var(--radius-lg);
32+
background-color: var(--bg-primary);
33+
border: 1px solid var(--border-color);
34+
box-shadow: var(--shadow-xl), 0 0 0 1px rgba(0, 0, 0, 0.05);
35+
pointer-events: auto;
36+
max-width: 100%;
37+
width: 100%;
38+
opacity: 0;
39+
transform: translateX(100%);
40+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
41+
}
42+
43+
.toast.show {
44+
opacity: 1;
45+
transform: translateX(0);
46+
}
47+
48+
.toast.hide {
49+
opacity: 0;
50+
transform: translateX(100%);
51+
}
52+
53+
/* ===== Toast Icon ===== */
54+
.toast-icon {
55+
flex-shrink: 0;
56+
width: 24px;
57+
height: 24px;
58+
display: flex;
59+
align-items: center;
60+
justify-content: center;
61+
border-radius: var(--radius-full);
62+
margin-top: 2px;
63+
}
64+
65+
.toast-icon svg {
66+
width: 16px;
67+
height: 16px;
68+
}
69+
70+
/* ===== Toast Content ===== */
71+
.toast-content {
72+
flex: 1;
73+
min-width: 0;
74+
}
75+
76+
.toast-title {
77+
font-weight: 600;
78+
font-size: var(--text-sm);
79+
color: var(--text-primary);
80+
margin-bottom: 2px;
81+
}
82+
83+
.toast-message {
84+
font-size: var(--text-sm);
85+
color: var(--text-secondary);
86+
line-height: 1.5;
87+
word-wrap: break-word;
88+
}
89+
90+
/* ===== Close Button ===== */
91+
.toast-close {
92+
flex-shrink: 0;
93+
background: none;
94+
border: none;
95+
cursor: pointer;
96+
padding: var(--spacing-xs);
97+
margin: -4px -4px -4px 0;
98+
color: var(--text-secondary);
99+
opacity: 0.5;
100+
transition: opacity 0.2s ease, transform 0.2s ease;
6101
border-radius: var(--radius-sm);
102+
}
103+
104+
.toast-close:hover {
105+
opacity: 1;
106+
background-color: rgba(0, 0, 0, 0.05);
107+
}
108+
109+
.toast-close svg {
110+
width: 18px;
111+
height: 18px;
112+
display: block;
113+
}
114+
115+
/* ===== Progress Bar ===== */
116+
.toast-progress {
117+
position: absolute;
118+
bottom: 0;
119+
left: 0;
120+
width: 100%;
121+
height: 3px;
122+
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
123+
transition: width linear;
124+
}
125+
126+
.toast {
127+
position: relative;
128+
overflow: hidden;
129+
}
130+
131+
/* ===== Success Toast ===== */
132+
.toast-success .toast-icon {
133+
background-color: rgba(16, 185, 129, 0.15);
134+
color: var(--success-green);
135+
}
136+
137+
.toast-success .toast-title {
138+
color: var(--success-green);
139+
}
140+
141+
.toast-success .toast-progress {
142+
background-color: var(--success-green);
143+
}
144+
145+
146+
/* ===== Error Toast ===== */
147+
.toast-error .toast-icon {
148+
background-color: rgba(239, 68, 68, 0.15);
149+
color: var(--danger-red);
150+
}
151+
152+
.toast-error .toast-title {
153+
color: var(--danger-red);
154+
}
155+
156+
.toast-error .toast-progress {
157+
background-color: var(--danger-red);
158+
}
159+
160+
161+
/* ===== Warning Toast ===== */
162+
.toast-warning .toast-icon {
163+
background-color: rgba(245, 158, 11, 0.15);
164+
color: var(--warning-amber);
165+
}
166+
167+
.toast-warning .toast-title {
168+
color: var(--warning-amber);
169+
}
170+
171+
.toast-warning .toast-progress {
172+
background-color: var(--warning-amber);
173+
}
174+
175+
176+
/* ===== Info Toast ===== */
177+
.toast-info .toast-icon {
178+
background-color: rgba(59, 130, 246, 0.15);
179+
color: var(--primary-blue);
180+
}
181+
182+
.toast-info .toast-title {
183+
color: var(--primary-blue);
184+
}
185+
186+
.toast-info .toast-progress {
187+
background-color: var(--primary-blue);
188+
}
189+
190+
191+
/* ===== Dark Mode ===== */
192+
html.dark-mode .toast {
193+
background-color: var(--bg-primary);
194+
border-color: var(--border-color);
195+
box-shadow: var(--shadow-xl), 0 0 0 1px rgba(255, 255, 255, 0.05);
196+
}
197+
198+
html.dark-mode .toast-close:hover {
199+
background-color: rgba(255, 255, 255, 0.1);
200+
}
201+
202+
html.dark-mode .toast-success .toast-icon {
203+
background-color: rgba(16, 185, 129, 0.2);
204+
color: var(--primary-green-light);
205+
}
206+
207+
html.dark-mode .toast-success .toast-title {
208+
color: var(--primary-green-light);
209+
}
210+
211+
html.dark-mode .toast-error .toast-icon {
212+
background-color: rgba(239, 68, 68, 0.2);
213+
color: var(--danger-red-light);
214+
}
215+
216+
html.dark-mode .toast-error .toast-title {
217+
color: var(--danger-red-light);
218+
}
219+
220+
html.dark-mode .toast-warning .toast-icon {
221+
background-color: rgba(245, 158, 11, 0.2);
222+
}
223+
224+
html.dark-mode .toast-info .toast-icon {
225+
background-color: rgba(59, 130, 246, 0.2);
226+
color: var(--primary-blue-light);
227+
}
228+
229+
html.dark-mode .toast-info .toast-title {
230+
color: var(--primary-blue-light);
231+
}
232+
233+
/* ===== Mobile Responsive ===== */
234+
@media (max-width: 768px) {
235+
.toast-container {
236+
top: auto;
237+
bottom: 0;
238+
left: 0;
239+
right: 0;
240+
max-width: 100%;
241+
padding: var(--spacing-sm);
242+
align-items: stretch;
243+
}
244+
245+
.toast {
246+
transform: translateY(100%);
247+
border-radius: var(--radius-md);
248+
padding: var(--spacing-md);
249+
}
250+
251+
.toast.show {
252+
transform: translateY(0);
253+
}
254+
255+
.toast.hide {
256+
transform: translateY(100%);
257+
}
258+
259+
.toast-progress {
260+
border-radius: 0 0 var(--radius-md) var(--radius-md);
261+
}
262+
263+
.toast-icon {
264+
width: 20px;
265+
height: 20px;
266+
}
267+
268+
.toast-icon svg {
269+
width: 14px;
270+
height: 14px;
271+
}
272+
273+
.toast-message {
274+
font-size: var(--text-xs);
275+
}
276+
}
277+
278+
/* Safe area insets for modern mobile devices */
279+
@supports (padding-bottom: env(safe-area-inset-bottom)) {
280+
@media (max-width: 768px) {
281+
.toast-container {
282+
padding-bottom: calc(var(--spacing-sm) + env(safe-area-inset-bottom));
283+
}
284+
}
285+
}
286+
287+
/* ===== Legacy Alert Classes (for Flask flash messages) ===== */
288+
.alert {
289+
display: flex;
290+
align-items: center;
291+
gap: var(--spacing-sm);
292+
padding: var(--spacing-md) var(--spacing-lg);
293+
border-radius: var(--radius-md);
7294
margin-bottom: var(--spacing-md);
295+
font-size: var(--text-sm);
296+
}
297+
298+
.alert svg {
299+
flex-shrink: 0;
300+
width: 20px;
301+
height: 20px;
8302
}
9303

10304
.alert-error {
@@ -31,7 +325,7 @@ html.dark-mode .alert-success {
31325
border-color: rgba(16, 185, 129, 0.3);
32326
}
33327

34-
/* Message styles */
328+
/* Message styles (compact variant) */
35329
.message {
36330
padding: var(--spacing-sm) var(--spacing-md);
37331
border-radius: var(--radius-md);

app/assets/css/components/buttons.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@
5151
background-color: var(--border-color);
5252
}
5353

54+
.btn-danger {
55+
background-color: var(--danger-red);
56+
color: white;
57+
border: 1px solid transparent;
58+
font-weight: 600;
59+
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
60+
}
61+
62+
.btn-danger:hover {
63+
background-color: #dc2626;
64+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
65+
transform: translateY(-1px);
66+
}
67+
5468
.btn-text {
5569
background-color: transparent;
5670
color: var(--text-secondary);

0 commit comments

Comments
 (0)