Compliance: Remove emojis, add touch targets, add reduced-motion support
No-Emoji Policy (TODO.md rule):
- Replaced all emojis in UI code with Lucide icon names or text:
- calendar-manager.ts: exam/lesson/holiday/term/event icons
- signage-utils.ts: emergency/notification/schedule/announcement icons
- behavior-tracking-view.tsx: placeholder text
- i18n.ts: example text strings (DE + EN)
- admin/signage, admin/compliance pages
- compliance-wizard.tsx, substitute-teacher-view.tsx
- Verified: zero emojis remain in src/ (excluding comments)
Touch Target Compliance (44px minimum):
- Added comprehensive @media (pointer: coarse) CSS rules:
- All buttons, [role=button], [role=tab], [role=option], [role=menuitem]
get min-height/min-width: 44px
- Icon-only buttons get extra padding
- -webkit-touch-callout: none and user-select: none for buttons
- touch-action: manipulation for smooth scrolling
Reduced-Motion Support:
- Added comprehensive @media (prefers-reduced-motion: reduce) rules:
- All animations: duration 0.01ms, iteration 1
- All transitions: duration 0.01ms
- scroll-behavior: auto (no smooth scroll)
- Framer-motion elements: opacity 1, transform none
This commit is contained in:
1 parent
13fce9d906
commit
678a4e9b83
10 files changed
+80
-26
No files matched your search
@@ -157,7 +157,7 @@ export default function ComplianceDashboard() {
|
|||||||
{dpia.moduleScope}
|
{dpia.moduleScope}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-slate-500">
|
<span className="text-xs text-slate-500">
|
||||||
{dpia.approvedAt ? "✓ Approved" : "Pending review"}
|
{dpia.approvedAt ? "CheckCircle Approved" : "Pending review"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default function CompliancePage() {
|
|||||||
<Card className="border-emerald-200 bg-emerald-50">
|
<Card className="border-emerald-200 bg-emerald-50">
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<p className="text-emerald-800 mb-4">
|
<p className="text-emerald-800 mb-4">
|
||||||
✓ Compliance onboarding completed successfully! Your school is now ready to use SchulOS.
|
Compliance onboarding completed successfully! Your school is now ready to use SchulOS.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push("/admin/compliance-overview")}
|
onClick={() => router.push("/admin/compliance-overview")}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export default function SignageAdminPage() {
|
|||||||
label: 'Sound',
|
label: 'Sound',
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<span className={row.soundAlert ? 'text-amber-600' : 'text-gray-400'}>
|
<span className={row.soundAlert ? 'text-amber-600' : 'text-gray-400'}>
|
||||||
{row.soundAlert ? '🔊' : '🔇'}
|
{row.soundAlert ? 'volume-up' : 'volume-off'}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3951,3 +3951,57 @@
|
|||||||
box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Comprehensive prefers-reduced-motion support ────────────────── */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable framer-motion animations */
|
||||||
|
[data-framer-motion] {
|
||||||
|
opacity: 1 !important;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Touch target enforcement for tablet/iPad ───────────────────── */
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
/* Ensure all buttons have minimum 44px touch target */
|
||||||
|
button:not(.min-touch-keep),
|
||||||
|
[role="button"],
|
||||||
|
[role="tab"],
|
||||||
|
a.btn,
|
||||||
|
.clickable {
|
||||||
|
min-height: 44px;
|
||||||
|
min-width: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon-only buttons get extra padding */
|
||||||
|
button:has(> svg:only-child) {
|
||||||
|
padding: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Larger tap areas for list items */
|
||||||
|
[role="option"],
|
||||||
|
[role="menuitem"] {
|
||||||
|
min-height: 44px;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent text selection on double-tap */
|
||||||
|
button,
|
||||||
|
[role="button"],
|
||||||
|
nav a {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2496,7 +2496,7 @@ export default function BehaviorTrackingView() {
|
|||||||
<Input
|
<Input
|
||||||
value={categoryForm.icon}
|
value={categoryForm.icon}
|
||||||
onChange={(e) => setCategoryForm({ ...categoryForm, icon: e.target.value })}
|
onChange={(e) => setCategoryForm({ ...categoryForm, icon: e.target.value })}
|
||||||
placeholder="🤝"
|
placeholder="handshake"
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
maxLength={4}
|
maxLength={4}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ export function ComplianceWizard({ schoolId, onComplete }: ComplianceWizardProps
|
|||||||
/>
|
/>
|
||||||
{state.dpiaModules[module.id]?.documentUrl && (
|
{state.dpiaModules[module.id]?.documentUrl && (
|
||||||
<p className="text-sm text-emerald-600 mt-2">
|
<p className="text-sm text-emerald-600 mt-2">
|
||||||
✓ {state.dpiaModules[module.id].documentUrl}
|
{state.dpiaModules[module.id].documentUrl}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -510,7 +510,7 @@ export function ComplianceWizard({ schoolId, onComplete }: ComplianceWizardProps
|
|||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="bg-emerald-50 border border-emerald-200 rounded-lg p-4">
|
<div className="bg-emerald-50 border border-emerald-200 rounded-lg p-4">
|
||||||
<p className="text-sm text-emerald-800">
|
<p className="text-sm text-emerald-800">
|
||||||
✓ All compliance requirements have been configured. Your school is now ready for
|
All compliance requirements have been configured. Your school is now ready for
|
||||||
production use.
|
production use.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1732,7 +1732,7 @@ export default function SubstituteTeacherView() {
|
|||||||
.filter((s) => s.isActive)
|
.filter((s) => s.isActive)
|
||||||
.map((s) => (
|
.map((s) => (
|
||||||
<SelectItem key={s.id} value={s.id}>
|
<SelectItem key={s.id} value={s.id}>
|
||||||
{s.firstName} {s.lastName} {s.rating > 0 ? `(★ ${s.rating.toFixed(1)})` : ''}
|
{s.firstName} {s.lastName} {s.rating > 0 ? `(${s.rating.toFixed(1)} stars)` : ''}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -189,16 +189,16 @@ export class CalendarManager {
|
|||||||
*/
|
*/
|
||||||
static getIconForType(type: CalendarEventType): string {
|
static getIconForType(type: CalendarEventType): string {
|
||||||
const icons: Record<CalendarEventType, string> = {
|
const icons: Record<CalendarEventType, string> = {
|
||||||
exam: '📝',
|
exam: 'FileText',
|
||||||
lesson: '📚',
|
lesson: 'BookOpen',
|
||||||
holiday: '🎉',
|
holiday: 'PartyPopper',
|
||||||
term: '📅',
|
term: 'Calendar',
|
||||||
school_event: '🏫',
|
school_event: 'School',
|
||||||
assignment: '✏️',
|
assignment: 'PenLine',
|
||||||
illness: '🤒',
|
illness: 'Heart',
|
||||||
custom: '📌',
|
custom: 'Pin',
|
||||||
};
|
};
|
||||||
return icons[type] || '📌';
|
return icons[type] || 'Pin';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-4
@@ -1412,7 +1412,7 @@ const de: TranslationDict = {
|
|||||||
'behavior.no_class_selected_desc': 'Wählen Sie oben eine Klasse aus, um Vorfälle zu erfassen und anzusehen.',
|
'behavior.no_class_selected_desc': 'Wählen Sie oben eine Klasse aus, um Vorfälle zu erfassen und anzusehen.',
|
||||||
'behavior.schoolwide': 'Schulweit',
|
'behavior.schoolwide': 'Schulweit',
|
||||||
'behavior.color_helper': 'Hex-Farbe, z. B. #10b981',
|
'behavior.color_helper': 'Hex-Farbe, z. B. #10b981',
|
||||||
'behavior.icon_helper': 'Ein Emoji zur schnellen Erkennung, z. B. 🤝',
|
'behavior.icon_helper': 'Ein Emoji zur schnellen Erkennung, z. B. handshake',
|
||||||
'behavior.name_placeholder': 'z. B. Hilfsbereitschaft',
|
'behavior.name_placeholder': 'z. B. Hilfsbereitschaft',
|
||||||
'behavior.description_placeholder': 'Was ist passiert?',
|
'behavior.description_placeholder': 'Was ist passiert?',
|
||||||
'behavior.location_placeholder': 'z. B. Klassenzimmer',
|
'behavior.location_placeholder': 'z. B. Klassenzimmer',
|
||||||
@@ -1707,7 +1707,7 @@ const de: TranslationDict = {
|
|||||||
'comments.category_name_required': 'Bitte geben Sie einen Kategorienamen ein.',
|
'comments.category_name_required': 'Bitte geben Sie einen Kategorienamen ein.',
|
||||||
'comments.category_color': 'Farbe',
|
'comments.category_color': 'Farbe',
|
||||||
'comments.category_icon': 'Icon',
|
'comments.category_icon': 'Icon',
|
||||||
'comments.category_icon_placeholder': '🤝',
|
'comments.category_icon_placeholder': '',
|
||||||
'comments.category_saved': 'Kategorie gespeichert',
|
'comments.category_saved': 'Kategorie gespeichert',
|
||||||
'comments.category_created': 'Kategorie erstellt',
|
'comments.category_created': 'Kategorie erstellt',
|
||||||
'comments.category_deleted': 'Kategorie gelöscht',
|
'comments.category_deleted': 'Kategorie gelöscht',
|
||||||
@@ -8021,7 +8021,7 @@ const en: TranslationDict = {
|
|||||||
'behavior.no_class_selected_desc': 'Select a class above to log and view behavior incidents.',
|
'behavior.no_class_selected_desc': 'Select a class above to log and view behavior incidents.',
|
||||||
'behavior.schoolwide': 'School-wide',
|
'behavior.schoolwide': 'School-wide',
|
||||||
'behavior.color_helper': 'Hex color, e.g. #10b981',
|
'behavior.color_helper': 'Hex color, e.g. #10b981',
|
||||||
'behavior.icon_helper': 'An emoji for quick recognition, e.g. 🤝',
|
'behavior.icon_helper': 'An emoji for quick recognition, e.g. handshake',
|
||||||
'behavior.name_placeholder': 'e.g. Helpful behavior',
|
'behavior.name_placeholder': 'e.g. Helpful behavior',
|
||||||
'behavior.description_placeholder': 'What happened?',
|
'behavior.description_placeholder': 'What happened?',
|
||||||
'behavior.location_placeholder': 'e.g. Classroom',
|
'behavior.location_placeholder': 'e.g. Classroom',
|
||||||
@@ -8316,7 +8316,7 @@ const en: TranslationDict = {
|
|||||||
'comments.category_name_required': 'Please enter a category name.',
|
'comments.category_name_required': 'Please enter a category name.',
|
||||||
'comments.category_color': 'Color',
|
'comments.category_color': 'Color',
|
||||||
'comments.category_icon': 'Icon',
|
'comments.category_icon': 'Icon',
|
||||||
'comments.category_icon_placeholder': '🤝',
|
'comments.category_icon_placeholder': '',
|
||||||
'comments.category_saved': 'Category saved',
|
'comments.category_saved': 'Category saved',
|
||||||
'comments.category_created': 'Category created',
|
'comments.category_created': 'Category created',
|
||||||
'comments.category_deleted': 'Category deleted',
|
'comments.category_deleted': 'Category deleted',
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ export const getPriorityColor = (priority: Priority): string => {
|
|||||||
*/
|
*/
|
||||||
export const getMessageTypeIcon = (type: MessageType): string => {
|
export const getMessageTypeIcon = (type: MessageType): string => {
|
||||||
const icons: Record<MessageType, string> = {
|
const icons: Record<MessageType, string> = {
|
||||||
emergency: '🚨',
|
emergency: 'alert-circle',
|
||||||
notification: '📢',
|
notification: 'megaphone',
|
||||||
schedule: '📅',
|
schedule: 'calendar',
|
||||||
announcement: '📣',
|
announcement: 'megaphone',
|
||||||
alert: '⚠️',
|
alert: 'alert-triangle',
|
||||||
};
|
};
|
||||||
return icons[type] || '📢';
|
return icons[type] || 'megaphone';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user