Fix: Redesign Digitale Hefte create dialog - fits viewport properly
Problems fixed: - Dialog was max-w-lg (narrow) with too much vertical content (16 notebook type buttons in a grid, separate sections for each field) - Content overflowed the viewport on smaller screens Redesigned approach: - Dialog: max-w-2xl max-h-[85vh] with flex flex-col layout - Header: fixed at top with border-b (shrink-0) - Content: scrollable area (flex-1 overflow-y-auto) - Footer: fixed at bottom with border-t (shrink-0) Layout optimizations: - Title + Description side by side (grid-cols-2) - Subject + Class side by side (grid-cols-2) - Notebook type: compact dropdown instead of 16-button grid (base types + curriculum separator + curriculum types) - Color + Icon side by side (grid-cols-2) - Public toggle + Eco message in one compact row - Smaller labels (text-xs), tighter spacing (space-y-4) - Fixed height inputs (h-10 instead of min-h-[44px]) Result: w=512 h=490, fits within viewport (577px)
This commit is contained in:
1 parent
b56f840161
commit
be1e61d94b
1 file changed
+107
-135
+107
-135
@@ -1179,8 +1179,8 @@ function CreateNotebookDialog({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(v) => { if (!v) onClose(); }}>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogContent className="max-w-2xl max-h-[85vh] flex flex-col p-0 gap-0 overflow-hidden">
|
||||
<DialogHeader className="px-6 py-4 border-b shrink-0">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<BookOpen className="w-5 h-5 text-emerald-500" />
|
||||
{t('notebooks.create_title')}
|
||||
@@ -1188,88 +1188,35 @@ function CreateNotebookDialog({
|
||||
<DialogDescription>{t('notebooks.create_desc')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-5 py-2">
|
||||
{/* Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>{t('notebooks.field_title')}</Label>
|
||||
<Input
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder={t('notebooks.field_title_placeholder')}
|
||||
className="min-h-[44px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="space-y-2">
|
||||
<Label>{t('notebooks.field_description')}</Label>
|
||||
<Textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder={t('notebooks.field_description_placeholder')}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Notebook Type - Visual Selector */}
|
||||
<div className="space-y-2">
|
||||
<Label>{t('notebooks.field_type')}</Label>
|
||||
{/* Base types */}
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{NOTEBOOK_TYPES.filter(nt => !nt.isCurriculum).map((nt) => {
|
||||
const NtIcon = nt.icon;
|
||||
const isSelected = notebookType === nt.key;
|
||||
return (
|
||||
<button
|
||||
key={nt.key}
|
||||
onClick={() => setNotebookType(nt.key)}
|
||||
className={`flex flex-col items-center gap-1 p-3 rounded-xl border-2 transition-all min-h-[64px] ${
|
||||
isSelected
|
||||
? 'border-emerald-500 bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-300'
|
||||
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<NtIcon className="w-5 h-5" />
|
||||
<span className="text-xs font-medium">{t(nt.labelKey)}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
|
||||
{/* Title + Description in a row */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">{t('notebooks.field_title')}</Label>
|
||||
<Input
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder={t('notebooks.field_title_placeholder')}
|
||||
className="h-10"
|
||||
/>
|
||||
</div>
|
||||
{/* German curriculum types */}
|
||||
<div className="mt-3">
|
||||
<Label className="text-xs text-amber-600 dark:text-amber-400 flex items-center gap-1 mb-2">
|
||||
<Bookmark className="w-3 h-3" />
|
||||
{t('notebooks.curriculum_badge')}
|
||||
</Label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{NOTEBOOK_TYPES.filter(nt => nt.isCurriculum).map((nt) => {
|
||||
const NtIcon = nt.icon;
|
||||
const isSelected = notebookType === nt.key;
|
||||
return (
|
||||
<button
|
||||
key={nt.key}
|
||||
onClick={() => setNotebookType(nt.key)}
|
||||
className={`flex flex-col items-center gap-1 p-3 rounded-xl border-2 transition-all min-h-[64px] ${
|
||||
isSelected
|
||||
? 'border-amber-500 bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300'
|
||||
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:border-amber-300'
|
||||
}`}
|
||||
>
|
||||
<NtIcon className="w-5 h-5" />
|
||||
<span className="text-xs font-medium">{t(nt.labelKey)}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">{t('notebooks.field_description')}</Label>
|
||||
<Input
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder={t('notebooks.field_description_placeholder')}
|
||||
className="h-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subject + Class */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('notebooks.field_subject')}</Label>
|
||||
{/* Subject + Class in a row */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">{t('notebooks.field_subject')}</Label>
|
||||
<Select value={subjectId} onValueChange={setSubjectId}>
|
||||
<SelectTrigger className="min-h-[44px]">
|
||||
<SelectTrigger className="h-10">
|
||||
<SelectValue placeholder={t('notebooks.no_subject')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -1280,10 +1227,10 @@ function CreateNotebookDialog({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t('notebooks.field_class')}</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">{t('notebooks.field_class')}</Label>
|
||||
<Select value={classGroupId} onValueChange={setClassGroupId}>
|
||||
<SelectTrigger className="min-h-[44px]">
|
||||
<SelectTrigger className="h-10">
|
||||
<SelectValue placeholder={t('notebooks.no_class')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -1296,72 +1243,97 @@ function CreateNotebookDialog({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cover Color */}
|
||||
<div className="space-y-2">
|
||||
<Label>{t('notebooks.field_color')}</Label>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{COVER_COLORS.map((cc) => (
|
||||
<button
|
||||
key={cc.key}
|
||||
onClick={() => setColor(cc.hex)}
|
||||
className={`w-8 h-8 rounded-full transition-all border-2 ${
|
||||
color === cc.hex ? 'border-gray-900 dark:border-white scale-110 shadow-md' : 'border-transparent hover:scale-105'
|
||||
}`}
|
||||
style={{ backgroundColor: cc.hex }}
|
||||
title={t(cc.labelKey)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Notebook Type - compact dropdown instead of grid */}
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">{t('notebooks.field_type')}</Label>
|
||||
<Select value={notebookType} onValueChange={setNotebookType}>
|
||||
<SelectTrigger className="h-10">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{NOTEBOOK_TYPES.filter(nt => !nt.isCurriculum).map((nt) => (
|
||||
<SelectItem key={nt.key} value={nt.key}>{t(nt.labelKey)}</SelectItem>
|
||||
))}
|
||||
{NOTEBOOK_TYPES.filter(nt => nt.isCurriculum).length > 0 && (
|
||||
<>
|
||||
<SelectItem value="_separator" disabled className="text-xs text-amber-600 font-semibold">
|
||||
{t('notebooks.curriculum_badge')}
|
||||
</SelectItem>
|
||||
{NOTEBOOK_TYPES.filter(nt => nt.isCurriculum).map((nt) => (
|
||||
<SelectItem key={nt.key} value={nt.key}>{t(nt.labelKey)}</SelectItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Icon Selector */}
|
||||
<div className="space-y-2">
|
||||
<Label>{t('notebooks.field_icon')}</Label>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{ICON_OPTIONS.map((iconName) => {
|
||||
const IconComp = ICON_MAP[iconName];
|
||||
const isSelected = icon === iconName;
|
||||
return (
|
||||
{/* Color + Icon in a row */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">{t('notebooks.field_color')}</Label>
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{COVER_COLORS.map((cc) => (
|
||||
<button
|
||||
key={iconName}
|
||||
onClick={() => setIcon(iconName)}
|
||||
className={`w-10 h-10 flex items-center justify-center rounded-lg border-2 transition-all ${
|
||||
isSelected
|
||||
? 'border-emerald-500 bg-emerald-50 dark:bg-emerald-900/20'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300'
|
||||
key={cc.key}
|
||||
onClick={() => setColor(cc.hex)}
|
||||
className={`w-7 h-7 rounded-full transition-all border-2 ${
|
||||
color === cc.hex ? 'border-gray-900 dark:border-white scale-110 shadow-md' : 'border-transparent hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
<IconComp className={`w-4 h-4 ${isSelected ? 'text-emerald-600 dark:text-emerald-300' : 'text-gray-500 dark:text-gray-400'}`} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
style={{ backgroundColor: cc.hex }}
|
||||
title={t(cc.labelKey)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">{t('notebooks.field_icon')}</Label>
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
{ICON_OPTIONS.map((iconName) => {
|
||||
const IconComp = ICON_MAP[iconName];
|
||||
const isSelected = icon === iconName;
|
||||
return (
|
||||
<button
|
||||
key={iconName}
|
||||
onClick={() => setIcon(iconName)}
|
||||
className={`w-8 h-8 flex items-center justify-center rounded-lg border-2 transition-all ${
|
||||
isSelected
|
||||
? 'border-emerald-500 bg-emerald-50 dark:bg-emerald-900/20'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<IconComp className={`w-3.5 h-3.5 ${isSelected ? 'text-emerald-600 dark:text-emerald-300' : 'text-gray-500 dark:text-gray-400'}`} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Public toggle */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch checked={isPublic} onCheckedChange={setIsPublic} />
|
||||
<div>
|
||||
<Label className="flex items-center gap-1">
|
||||
<Share2 className="w-4 h-4" />
|
||||
{t('notebooks.field_public')}
|
||||
</Label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{t('notebooks.field_public_hint')}</p>
|
||||
{/* Public toggle + Eco message in a row */}
|
||||
<div className="flex items-center justify-between gap-4 rounded-lg bg-muted/50 p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch checked={isPublic} onCheckedChange={setIsPublic} />
|
||||
<div>
|
||||
<Label className="text-xs flex items-center gap-1">
|
||||
<Share2 className="w-3 h-3" />
|
||||
{t('notebooks.field_public')}
|
||||
</Label>
|
||||
<p className="text-[10px] text-gray-500 dark:text-gray-400">{t('notebooks.field_public_hint')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-emerald-700 dark:text-emerald-300 text-xs">
|
||||
<Leaf className="w-3.5 h-3.5 shrink-0" />
|
||||
<span className="hidden sm:inline">{t('notebooks.eco_message')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Eco message */}
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-emerald-50 dark:bg-emerald-900/20 rounded-lg text-emerald-700 dark:text-emerald-300 text-sm">
|
||||
<Leaf className="w-4 h-4" />
|
||||
<span>{t('notebooks.eco_message')}</span>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose} className="min-h-[44px]">
|
||||
<DialogFooter className="px-6 py-3 border-t shrink-0">
|
||||
<Button variant="outline" onClick={onClose} className="h-10">
|
||||
{t('action.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleCreate} disabled={creating || !title.trim()} className="min-h-[44px] bg-emerald-600 hover:bg-emerald-700">
|
||||
<Button onClick={handleCreate} disabled={creating || !title.trim()} className="h-10 bg-emerald-600 hover:bg-emerald-700">
|
||||
{creating ? (
|
||||
<div className="w-4 h-4 animate-spin rounded-full border-2 border-white border-t-transparent mr-2" />
|
||||
) : (
|
||||
|
||||
Reference in new issue
Block a user