Adopt shared WebUI layout primitives

This commit is contained in:
2026-08-18 10:42:51 +02:00
parent b6f939eaba
commit a6e10fd120
6 changed files with 47 additions and 64 deletions
@@ -6,7 +6,7 @@ import {
type FormEvent, type FormEvent,
} from "react"; } from "react";
import { ArrowRightLeft, ListChecks, RefreshCw, Trash2 } from "lucide-react"; import { ArrowRightLeft, ListChecks, RefreshCw, Trash2 } from "lucide-react";
import { import { FormGrid, DialogForm,
ActionBlockerHint, ActionBlockerHint,
Button, Button,
ColorPickerField, ColorPickerField,
@@ -372,7 +372,7 @@ export function CalendarCollectionDialog({
</> </>
}> }>
<form id={formId} className="calendar-dialog-form" onSubmit={submit}> <DialogForm id={formId} className="calendar-dialog-form" onSubmit={submit}>
<div className="calendar-dialog-documentation"> <div className="calendar-dialog-documentation">
<DocumentationHelpLink reference={CALENDAR_SOURCE_DOCUMENTATION} /> <DocumentationHelpLink reference={CALENDAR_SOURCE_DOCUMENTATION} />
</div> </div>
@@ -443,7 +443,7 @@ export function CalendarCollectionDialog({
</span> </span>
} }
</div> </div>
<div className="calendar-caldav-setup"> <FormGrid columns={2} gap="small" collapseAt="narrow" className="calendar-caldav-setup">
<label className="calendar-dialog-wide"> <label className="calendar-dialog-wide">
<span>{calendarSourceUrlLabel(sourceMode)}</span> <span>{calendarSourceUrlLabel(sourceMode)}</span>
<input <input
@@ -532,7 +532,7 @@ export function CalendarCollectionDialog({
} }
{effectiveCollectionUrl && <span>{effectiveCollectionUrl}</span>} {effectiveCollectionUrl && <span>{effectiveCollectionUrl}</span>}
</div> </div>
</div> </FormGrid>
{discoveryError && <p className="calendar-form-error">{discoveryError}</p>} {discoveryError && <p className="calendar-form-error">{discoveryError}</p>}
{calendarSourceUsesCalDav(sourceMode) && discoveredCalendars.length > 0 && {calendarSourceUsesCalDav(sourceMode) && discoveredCalendars.length > 0 &&
<label> <label>
@@ -548,13 +548,13 @@ export function CalendarCollectionDialog({
} }
<details className="calendar-advanced-settings"> <details className="calendar-advanced-settings">
<summary>i18n:govoplan-calendar.advanced.4d064726</summary> <summary>i18n:govoplan-calendar.advanced.4d064726</summary>
<div className="calendar-dialog-grid-two"> <FormGrid columns={2} gap="small" collapseAt="narrow">
<label> <label>
<span>i18n:govoplan-calendar.display_name.c7874aaa</span> <span>i18n:govoplan-calendar.display_name.c7874aaa</span>
<input value={displayName} onChange={(item) => setDisplayName(item.target.value)} maxLength={255} disabled={saving || !canEditMutableSourceSettings} /> <input value={displayName} onChange={(item) => setDisplayName(item.target.value)} maxLength={255} disabled={saving || !canEditMutableSourceSettings} />
</label> </label>
</div> </FormGrid>
<div className="calendar-sync-settings"> <FormGrid columns={2} gap="small" collapseAt="narrow">
<ToggleSwitch label="i18n:govoplan-calendar.automatic_sync.084644b2" checked={syncEnabled} disabled={saving || !canEditMutableSourceSettings} onChange={setSyncEnabled} /> <ToggleSwitch label="i18n:govoplan-calendar.automatic_sync.084644b2" checked={syncEnabled} disabled={saving || !canEditMutableSourceSettings} onChange={setSyncEnabled} />
<label> <label>
<span>i18n:govoplan-calendar.interval.011efcd5</span> <span>i18n:govoplan-calendar.interval.011efcd5</span>
@@ -574,7 +574,7 @@ export function CalendarCollectionDialog({
<option value="overwrite">i18n:govoplan-calendar.overwrite_remote.39625e32</option> <option value="overwrite">i18n:govoplan-calendar.overwrite_remote.39625e32</option>
</select> </select>
</label> </label>
</div> </FormGrid>
</details> </details>
{source && {source &&
<div className="calendar-sync-status-panel"> <div className="calendar-sync-status-panel">
@@ -614,7 +614,7 @@ export function CalendarCollectionDialog({
{needsSourceSecret && <p className="calendar-form-note">i18n:govoplan-calendar.enter_a_password_or_token_for_this_source.74c09a54</p>} {needsSourceSecret && <p className="calendar-form-note">i18n:govoplan-calendar.enter_a_password_or_token_for_this_source.74c09a54</p>}
</section> </section>
} }
</form> </DialogForm>
</Dialog>); </Dialog>);
} }
@@ -4,7 +4,7 @@ import {
type FormEvent, type FormEvent,
} from "react"; } from "react";
import { Trash2 } from "lucide-react"; import { Trash2 } from "lucide-react";
import { import { FormGrid, DialogForm,
ActionBlockerHint, ActionBlockerHint,
Button, Button,
ConfirmDialog, ConfirmDialog,
@@ -301,7 +301,7 @@ export function CalendarEventDialog({
</> </>
}> }>
<form id={formId} className="calendar-dialog-form" onSubmit={submit}> <DialogForm id={formId} className="calendar-dialog-form" onSubmit={submit}>
<div className="calendar-dialog-documentation"> <div className="calendar-dialog-documentation">
<DocumentationHelpLink reference={CALENDAR_DOCUMENTATION} /> <DocumentationHelpLink reference={CALENDAR_DOCUMENTATION} />
</div> </div>
@@ -398,7 +398,7 @@ export function CalendarEventDialog({
<summary>i18n:govoplan-calendar.vevent.9cf5be75</summary> <summary>i18n:govoplan-calendar.vevent.9cf5be75</summary>
<section className="calendar-vevent-section"> <section className="calendar-vevent-section">
<h3>i18n:govoplan-calendar.identity.7e5a975b</h3> <h3>i18n:govoplan-calendar.identity.7e5a975b</h3>
<div className="calendar-dialog-grid-three"> <FormGrid columns={3} gap="small" collapseAt="narrow">
<label> <label>
<span>i18n:govoplan-calendar.uid.d946adf5</span> <span>i18n:govoplan-calendar.uid.d946adf5</span>
<input value={uid} onChange={(item) => setUid(item.target.value)} maxLength={255} disabled={saving || !canWrite || Boolean(event)} /> <input value={uid} onChange={(item) => setUid(item.target.value)} maxLength={255} disabled={saving || !canWrite || Boolean(event)} />
@@ -411,11 +411,11 @@ export function CalendarEventDialog({
<span>i18n:govoplan-calendar.sequence.5c8f4e0e</span> <span>i18n:govoplan-calendar.sequence.5c8f4e0e</span>
<input type="number" min={0} step={1} value={sequence} onChange={(item) => setSequence(item.target.value)} disabled={saving || !canWrite} /> <input type="number" min={0} step={1} value={sequence} onChange={(item) => setSequence(item.target.value)} disabled={saving || !canWrite} />
</label> </label>
</div> </FormGrid>
</section> </section>
<section className="calendar-vevent-section"> <section className="calendar-vevent-section">
<h3>i18n:govoplan-calendar.state.a7250206</h3> <h3>i18n:govoplan-calendar.state.a7250206</h3>
<div className="calendar-dialog-grid-three"> <FormGrid columns={3} gap="small" collapseAt="narrow">
<label> <label>
<span>i18n:govoplan-calendar.status.bae7d5be</span> <span>i18n:govoplan-calendar.status.bae7d5be</span>
<select value={status} onChange={(item) => setStatus(item.target.value)} disabled={saving || !canWrite}> <select value={status} onChange={(item) => setStatus(item.target.value)} disabled={saving || !canWrite}>
@@ -447,11 +447,11 @@ export function CalendarEventDialog({
<span>i18n:govoplan-calendar.categories.6ccb6007</span> <span>i18n:govoplan-calendar.categories.6ccb6007</span>
<input value={categoriesText} onChange={(item) => setCategoriesText(item.target.value)} disabled={saving || !canWrite} /> <input value={categoriesText} onChange={(item) => setCategoriesText(item.target.value)} disabled={saving || !canWrite} />
</label> </label>
</div> </FormGrid>
</section> </section>
<section className="calendar-vevent-section"> <section className="calendar-vevent-section">
<h3>i18n:govoplan-calendar.participants.cd56e083</h3> <h3>i18n:govoplan-calendar.participants.cd56e083</h3>
<div className="calendar-dialog-grid-two"> <FormGrid columns={2} gap="small" collapseAt="narrow">
<label> <label>
<span>i18n:govoplan-calendar.organizer_json.3add6f9f</span> <span>i18n:govoplan-calendar.organizer_json.3add6f9f</span>
<textarea value={organizerJson} onChange={(item) => setOrganizerJson(item.target.value)} rows={5} disabled={saving || !canWrite} /> <textarea value={organizerJson} onChange={(item) => setOrganizerJson(item.target.value)} rows={5} disabled={saving || !canWrite} />
@@ -460,7 +460,7 @@ export function CalendarEventDialog({
<span>i18n:govoplan-calendar.attendees_json.aeb487bb</span> <span>i18n:govoplan-calendar.attendees_json.aeb487bb</span>
<textarea value={attendeesJson} onChange={(item) => setAttendeesJson(item.target.value)} rows={5} disabled={saving || !canWrite} /> <textarea value={attendeesJson} onChange={(item) => setAttendeesJson(item.target.value)} rows={5} disabled={saving || !canWrite} />
</label> </label>
</div> </FormGrid>
</section> </section>
<section className="calendar-vevent-section"> <section className="calendar-vevent-section">
<h3>i18n:govoplan-calendar.recurrence.f7ad40f5</h3> <h3>i18n:govoplan-calendar.recurrence.f7ad40f5</h3>
@@ -468,7 +468,7 @@ export function CalendarEventDialog({
<span>i18n:govoplan-calendar.rrule.c7b2f8a3</span> <span>i18n:govoplan-calendar.rrule.c7b2f8a3</span>
<input value={rruleText} onChange={(item) => setRruleText(item.target.value)} disabled={saving || !canWrite} /> <input value={rruleText} onChange={(item) => setRruleText(item.target.value)} disabled={saving || !canWrite} />
</label> </label>
<div className="calendar-dialog-grid-two"> <FormGrid columns={2} gap="small" collapseAt="narrow">
<label> <label>
<span>i18n:govoplan-calendar.rdate_json.5b51fca4</span> <span>i18n:govoplan-calendar.rdate_json.5b51fca4</span>
<textarea value={rdateJson} onChange={(item) => setRdateJson(item.target.value)} rows={5} disabled={saving || !canWrite} /> <textarea value={rdateJson} onChange={(item) => setRdateJson(item.target.value)} rows={5} disabled={saving || !canWrite} />
@@ -477,11 +477,11 @@ export function CalendarEventDialog({
<span>i18n:govoplan-calendar.exdate_json.7d0c538d</span> <span>i18n:govoplan-calendar.exdate_json.7d0c538d</span>
<textarea value={exdateJson} onChange={(item) => setExdateJson(item.target.value)} rows={5} disabled={saving || !canWrite} /> <textarea value={exdateJson} onChange={(item) => setExdateJson(item.target.value)} rows={5} disabled={saving || !canWrite} />
</label> </label>
</div> </FormGrid>
</section> </section>
<section className="calendar-vevent-section"> <section className="calendar-vevent-section">
<h3>i18n:govoplan-calendar.related.917df91e</h3> <h3>i18n:govoplan-calendar.related.917df91e</h3>
<div className="calendar-dialog-grid-three"> <FormGrid columns={3} gap="small" collapseAt="narrow">
<label> <label>
<span>i18n:govoplan-calendar.reminders_json.ca25e08f</span> <span>i18n:govoplan-calendar.reminders_json.ca25e08f</span>
<textarea value={remindersJson} onChange={(item) => setRemindersJson(item.target.value)} rows={5} disabled={saving || !canWrite} /> <textarea value={remindersJson} onChange={(item) => setRemindersJson(item.target.value)} rows={5} disabled={saving || !canWrite} />
@@ -494,11 +494,11 @@ export function CalendarEventDialog({
<span>i18n:govoplan-calendar.related_to_json.2d4e8f59</span> <span>i18n:govoplan-calendar.related_to_json.2d4e8f59</span>
<textarea value={relatedToJson} onChange={(item) => setRelatedToJson(item.target.value)} rows={5} disabled={saving || !canWrite} /> <textarea value={relatedToJson} onChange={(item) => setRelatedToJson(item.target.value)} rows={5} disabled={saving || !canWrite} />
</label> </label>
</div> </FormGrid>
</section> </section>
<section className="calendar-vevent-section"> <section className="calendar-vevent-section">
<h3>i18n:govoplan-calendar.source.6da13add</h3> <h3>i18n:govoplan-calendar.source.6da13add</h3>
<div className="calendar-dialog-grid-three"> <FormGrid columns={3} gap="small" collapseAt="narrow">
<label> <label>
<span>i18n:govoplan-calendar.source_kind.7eda9bc4</span> <span>i18n:govoplan-calendar.source_kind.7eda9bc4</span>
<input value={sourceKind} onChange={(item) => setSourceKind(item.target.value)} maxLength={30} disabled={saving || !canWrite} /> <input value={sourceKind} onChange={(item) => setSourceKind(item.target.value)} maxLength={30} disabled={saving || !canWrite} />
@@ -511,11 +511,11 @@ export function CalendarEventDialog({
<span>i18n:govoplan-calendar.etag.11d00f6e</span> <span>i18n:govoplan-calendar.etag.11d00f6e</span>
<input value={etag} onChange={(item) => setEtag(item.target.value)} maxLength={255} disabled={saving || !canWrite} /> <input value={etag} onChange={(item) => setEtag(item.target.value)} maxLength={255} disabled={saving || !canWrite} />
</label> </label>
</div> </FormGrid>
</section> </section>
<section className="calendar-vevent-section"> <section className="calendar-vevent-section">
<h3>i18n:govoplan-calendar.raw.da433cd4</h3> <h3>i18n:govoplan-calendar.raw.da433cd4</h3>
<div className="calendar-dialog-grid-two"> <FormGrid columns={2} gap="small" collapseAt="narrow">
<label> <label>
<span>i18n:govoplan-calendar.icalendar_json.fb6cc33e</span> <span>i18n:govoplan-calendar.icalendar_json.fb6cc33e</span>
<textarea value={icalendarJson} onChange={(item) => setICalendarJson(item.target.value)} rows={8} disabled={saving || !canWrite} /> <textarea value={icalendarJson} onChange={(item) => setICalendarJson(item.target.value)} rows={8} disabled={saving || !canWrite} />
@@ -524,10 +524,10 @@ export function CalendarEventDialog({
<span>i18n:govoplan-calendar.metadata_json.b0e4c283</span> <span>i18n:govoplan-calendar.metadata_json.b0e4c283</span>
<textarea value={metadataJson} onChange={(item) => setMetadataJson(item.target.value)} rows={8} disabled={saving || !canWrite} /> <textarea value={metadataJson} onChange={(item) => setMetadataJson(item.target.value)} rows={8} disabled={saving || !canWrite} />
</label> </label>
</div> </FormGrid>
</section> </section>
</details> </details>
</form> </DialogForm>
</Dialog> </Dialog>
<ConfirmDialog <ConfirmDialog
open={Boolean(event && confirmingDelete)} open={Boolean(event && confirmingDelete)}
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { RefreshCw, RotateCcw, ScanSearch, Trash2 } from "lucide-react"; import { RefreshCw, RotateCcw, ScanSearch, Trash2 } from "lucide-react";
import { import { ActionToolbar,
ActionBlockerHint, ActionBlockerHint,
Button, Button,
ConfirmDialog, ConfirmDialog,
@@ -113,7 +113,7 @@ export function CalendarOutboxDialog({
<p className="calendar-outbox-calendar-name">{calendar.name}</p> <p className="calendar-outbox-calendar-name">{calendar.name}</p>
<DocumentationHelpLink reference={CALENDAR_RECOVERY_DOCUMENTATION} /> <DocumentationHelpLink reference={CALENDAR_RECOVERY_DOCUMENTATION} />
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>} {error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
<div className="calendar-outbox-toolbar"> <ActionToolbar className="calendar-outbox-toolbar">
<SegmentedControl<OutboxFilter> <SegmentedControl<OutboxFilter>
value={filter} value={filter}
ariaLabel="i18n:govoplan-calendar.outbox_filter.8305af40" ariaLabel="i18n:govoplan-calendar.outbox_filter.8305af40"
@@ -128,7 +128,7 @@ export function CalendarOutboxDialog({
<div><dt>i18n:govoplan-calendar.conflicts_dead.31252c3a</dt><dd>{conflictCount}</dd></div> <div><dt>i18n:govoplan-calendar.conflicts_dead.31252c3a</dt><dd>{conflictCount}</dd></div>
<div><dt>i18n:govoplan-calendar.shown.498e85a1</dt><dd>{visibleOperations.length}</dd></div> <div><dt>i18n:govoplan-calendar.shown.498e85a1</dt><dd>{visibleOperations.length}</dd></div>
</dl> </dl>
</div> </ActionToolbar>
{loading && !operations.length {loading && !operations.length
? <p className="calendar-form-note">i18n:govoplan-calendar.loading_outbound_changes.3fc59656</p> ? <p className="calendar-form-note">i18n:govoplan-calendar.loading_outbound_changes.3fc59656</p>
: visibleOperations.length : visibleOperations.length
+9 -9
View File
@@ -8,7 +8,7 @@ import {
type WheelEvent as ReactWheelEvent } from type WheelEvent as ReactWheelEvent } from
"react"; "react";
import { CalendarDays, ChevronLeft, ChevronRight, Pencil, Plus, RefreshCw } from "lucide-react"; import { CalendarDays, ChevronLeft, ChevronRight, Pencil, Plus, RefreshCw } from "lucide-react";
import { import { ToolbarGroup, ActionToolbar,
AdminIconButton, AdminIconButton,
Button, Button,
DismissibleAlert, DismissibleAlert,
@@ -854,8 +854,8 @@ export default function CalendarPage({ settings, auth }: {settings: ApiSettings;
</aside> </aside>
<section className="calendar-main-panel" aria-label="i18n:govoplan-calendar.calendar.adab5090"> <section className="calendar-main-panel" aria-label="i18n:govoplan-calendar.calendar.adab5090">
<div className="calendar-view-toolbar" aria-label="i18n:govoplan-calendar.calendar_controls.974f4fa1"> <ActionToolbar className="calendar-view-toolbar" aria-label="i18n:govoplan-calendar.calendar_controls.974f4fa1">
<div className="calendar-toolbar-left"> <ToolbarGroup grow className="calendar-toolbar-left">
<SegmentedControl <SegmentedControl
className="calendar-mode-switch" className="calendar-mode-switch"
size="equal" size="equal"
@@ -870,9 +870,9 @@ export default function CalendarPage({ settings, auth }: {settings: ApiSettings;
}} }}
options={modeOptions} options={modeOptions}
/> />
</div> </ToolbarGroup>
<div className="calendar-toolbar-center"> <ToolbarGroup align="center" className="calendar-toolbar-center">
<div className="calendar-icon-group" aria-label="i18n:govoplan-calendar.calendar_navigation.7ba43cd2"> <div className="calendar-icon-group" aria-label="i18n:govoplan-calendar.calendar_navigation.7ba43cd2">
<AdminIconButton <AdminIconButton
label="i18n:govoplan-calendar.previous.50f94286" label="i18n:govoplan-calendar.previous.50f94286"
@@ -892,9 +892,9 @@ export default function CalendarPage({ settings, auth }: {settings: ApiSettings;
<strong>{heading}</strong> <strong>{heading}</strong>
</div> </div>
</div> </div>
</div> </ToolbarGroup>
<div className="calendar-toolbar-right"> <ToolbarGroup align="end" className="calendar-toolbar-right">
<DocumentationHelpLink reference={CALENDAR_DOCUMENTATION} /> <DocumentationHelpLink reference={CALENDAR_DOCUMENTATION} />
<AdminIconButton <AdminIconButton
label="i18n:govoplan-calendar.refresh.56e3badc" label="i18n:govoplan-calendar.refresh.56e3badc"
@@ -906,8 +906,8 @@ export default function CalendarPage({ settings, auth }: {settings: ApiSettings;
<Plus size={17} /> i18n:govoplan-calendar.new.6403f2b7 <Plus size={17} /> i18n:govoplan-calendar.new.6403f2b7
</Button> </Button>
} }
</div> </ToolbarGroup>
</div> </ActionToolbar>
<div className={`calendar-view-shell is-${mode}`}> <div className={`calendar-view-shell is-${mode}`}>
{mode === "continuous" ? {mode === "continuous" ?
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Save } from "lucide-react"; import { Save } from "lucide-react";
import { import { FormGrid, ContentGrid,
Button, Button,
Card, Card,
DismissibleAlert, DismissibleAlert,
@@ -124,12 +124,12 @@ export default function CalendarSettingsPanel({
}); });
return ( return (
<div className="dashboard-grid settings-dashboard-grid calendar-settings-panel"> <ContentGrid columns={2} collapseAt="workspace" className="calendar-settings-panel">
<div className="calendar-settings-documentation"> <div className="calendar-settings-documentation">
<DocumentationHelpLink reference={CALENDAR_DOCUMENTATION} /> <DocumentationHelpLink reference={CALENDAR_DOCUMENTATION} />
</div> </div>
<Card title="Calendar display"> <Card title="Calendar display">
<div className="form-grid"> <FormGrid columns={1} collapseAt="standard" className="">
<ToggleSwitch <ToggleSwitch
label="Dim weekends" label="Dim weekends"
help="Use a quieter background for Saturday and Sunday in week views." help="Use a quieter background for Saturday and Sunday in week views."
@@ -147,7 +147,7 @@ export default function CalendarSettingsPanel({
setDraft((current) => ({ ...current, dim_off_hours })) setDraft((current) => ({ ...current, dim_off_hours }))
} }
/> />
<div className="calendar-dialog-grid-two"> <FormGrid columns={2} gap="small" collapseAt="narrow">
<FormField label="Workday starts"> <FormField label="Workday starts">
<input <input
type="number" type="number"
@@ -178,11 +178,11 @@ export default function CalendarSettingsPanel({
} }
/> />
</FormField> </FormField>
</div> </FormGrid>
</div> </FormGrid>
</Card> </Card>
<Card title="Continuous view"> <Card title="Continuous view">
<div className="form-grid"> <FormGrid columns={1} collapseAt="standard" className="">
<ToggleSwitch <ToggleSwitch
label="Virtualize distant weeks" label="Virtualize distant weeks"
help="Keep the continuous calendar responsive by rendering only nearby weeks." help="Keep the continuous calendar responsive by rendering only nearby weeks."
@@ -240,9 +240,9 @@ export default function CalendarSettingsPanel({
{message} {message}
</DismissibleAlert> </DismissibleAlert>
)} )}
</div> </FormGrid>
</Card> </Card>
</div> </ContentGrid>
); );
} }
-17
View File
@@ -944,19 +944,6 @@
gap: 10px; gap: 10px;
} }
.calendar-dialog-grid-two,
.calendar-dialog-grid-three,
.calendar-caldav-setup,
.calendar-sync-settings {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.calendar-dialog-grid-three {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.calendar-dialog-wide { .calendar-dialog-wide {
grid-column: 1 / -1; grid-column: 1 / -1;
} }
@@ -1524,10 +1511,6 @@
.calendar-dialog-row, .calendar-dialog-row,
.calendar-dialog-date-row, .calendar-dialog-date-row,
.calendar-dialog-name-color-row, .calendar-dialog-name-color-row,
.calendar-dialog-grid-two,
.calendar-dialog-grid-three,
.calendar-caldav-setup,
.calendar-sync-settings,
.calendar-sync-status-panel dl { .calendar-sync-status-panel dl {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }