feat: Add Focus Tools (ADHD-friendly productivity features)
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Add Focus Tools dashboard with collapsible help section - Implement Quick Wins page for tasks under 15 minutes - Add Single Task Focus mode to reduce overwhelm - Create Body Doubling page for virtual co-working - Add visual timer, break reminders, and energy tracking - Implement hyperfocus protection alerts - Add ADHD settings panel with customizable options - Include full English and German translations - Fix larger touch targets CSS to not break button layouts - Add Playwright tests for Focus Tools features
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
import React from 'react';
|
||||
import { useADHDMode, defaultADHDSettings } from '../providers/ADHDModeProvider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Brain,
|
||||
Timer,
|
||||
Sparkles,
|
||||
Coffee,
|
||||
Target,
|
||||
Battery,
|
||||
Zap,
|
||||
Shield,
|
||||
RotateCcw,
|
||||
} from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ADHDModeToggle } from '../ADHDModeToggle';
|
||||
|
||||
export function ADHDSettingsPanel() {
|
||||
const { t } = useTranslation();
|
||||
const { isEnabled, settings, updateSettings } = useADHDMode();
|
||||
|
||||
const handleReset = () => {
|
||||
updateSettings(defaultADHDSettings);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Main Toggle */}
|
||||
<ADHDModeToggle showLabel={true} />
|
||||
|
||||
{/* Settings (only shown when enabled) */}
|
||||
{isEnabled && (
|
||||
<>
|
||||
{/* Visual & Interaction Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Target className="h-5 w-5" />
|
||||
{t('adhd.settings.visualTitle')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('adhd.settings.visualDesc')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Reduced Animations */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">{t('adhd.settings.reducedAnimations')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('adhd.settings.reducedAnimationsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.reducedAnimations}
|
||||
onCheckedChange={(checked) => updateSettings({ reducedAnimations: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Larger Touch Targets */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">{t('adhd.settings.largerTargets')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('adhd.settings.largerTargetsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.largerTargets}
|
||||
onCheckedChange={(checked) => updateSettings({ largerTargets: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Single Task Mode Default */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">{t('adhd.settings.singleTaskDefault')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('adhd.settings.singleTaskDefaultDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.singleTaskModeDefault}
|
||||
onCheckedChange={(checked) => updateSettings({ singleTaskModeDefault: checked })}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Timer & Focus Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Timer className="h-5 w-5" />
|
||||
{t('adhd.settings.timerTitle')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('adhd.settings.timerDesc')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Visual Timer */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">{t('adhd.settings.visualTimer')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('adhd.settings.visualTimerDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.visualTimerEnabled}
|
||||
onCheckedChange={(checked) => updateSettings({ visualTimerEnabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Hyperfocus Protection */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base flex items-center gap-2">
|
||||
<Shield className="h-4 w-4" />
|
||||
{t('adhd.settings.hyperfocusProtection')}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('adhd.settings.hyperfocusProtectionDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.hyperfocusProtection}
|
||||
onCheckedChange={(checked) => updateSettings({ hyperfocusProtection: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Hyperfocus Max Minutes */}
|
||||
{settings.hyperfocusProtection && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>{t('adhd.settings.hyperfocusMaxMinutes')}</Label>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{settings.hyperfocusMaxMinutes} min
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[settings.hyperfocusMaxMinutes]}
|
||||
onValueChange={([value]) => updateSettings({ hyperfocusMaxMinutes: value })}
|
||||
min={30}
|
||||
max={180}
|
||||
step={15}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Break Reminder Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Coffee className="h-5 w-5" />
|
||||
{t('adhd.settings.breakTitle')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('adhd.settings.breakDesc')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Break Reminder Interval */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>{t('adhd.settings.breakInterval')}</Label>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{settings.breakReminderInterval} min
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[settings.breakReminderInterval]}
|
||||
onValueChange={([value]) => updateSettings({ breakReminderInterval: value })}
|
||||
min={15}
|
||||
max={90}
|
||||
step={5}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Motivation Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Sparkles className="h-5 w-5" />
|
||||
{t('adhd.settings.motivationTitle')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('adhd.settings.motivationDesc')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Positive Messaging Level */}
|
||||
<div className="space-y-2">
|
||||
<Label>{t('adhd.settings.messagingLevel')}</Label>
|
||||
<Select
|
||||
value={settings.positiveMessagingLevel}
|
||||
onValueChange={(value: 'minimal' | 'normal' | 'high') =>
|
||||
updateSettings({ positiveMessagingLevel: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="minimal">{t('adhd.settings.messagingMinimal')}</SelectItem>
|
||||
<SelectItem value="normal">{t('adhd.settings.messagingNormal')}</SelectItem>
|
||||
<SelectItem value="high">{t('adhd.settings.messagingHigh')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('adhd.settings.messagingLevelDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick Win Threshold */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-2">
|
||||
<Zap className="h-4 w-4 text-yellow-500" />
|
||||
{t('adhd.settings.quickWinThreshold')}
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{settings.quickWinThreshold} min
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[settings.quickWinThreshold]}
|
||||
onValueChange={([value]) => updateSettings({ quickWinThreshold: value })}
|
||||
min={5}
|
||||
max={30}
|
||||
step={5}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('adhd.settings.quickWinThresholdDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Energy Check-ins */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Battery className="h-5 w-5" />
|
||||
{t('adhd.settings.energyTitle')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('adhd.settings.energyDesc')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">{t('adhd.settings.energyCheckIns')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('adhd.settings.energyCheckInsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.energyCheckInsEnabled}
|
||||
onCheckedChange={(checked) => updateSettings({ energyCheckInsEnabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Reset Button */}
|
||||
<div className="flex justify-end">
|
||||
<Button variant="outline" onClick={handleReset}>
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
{t('adhd.settings.reset')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user