05customization

Settings dialog

The settings module — the optional preferences dialog, its KloqPreferences model, and the account helpers behind it.

@/components/kloq/settings is its own module because it is genuinely optional: the calendar works with no settings surface at all, and a host with its own preferences screen wants the KloqPreferences model without pulling in a dialog it will never render.

The dialog owns no settings

SettingsDialog is a value / onChange pair over a plain KloqPreferences object and nothing else — no context, no localStorage, no writes into <Kloq>. That is forced by the architecture rather than chosen: "show weekends" and "start week on" are props on the calendar, so the only honest thing a settings surface can do is report intent upward and let the host pass it back down. A dialog that secretly mutated the calendar would be a second source of truth, and the two would drift the first time the host changed a prop.

app/calendar.tsx
import { Kloq } from "@/components/kloq";
import {
  SettingsDialog,
  DEFAULT_PREFERENCES,
  type KloqPreferences,
} from "@/components/kloq/settings";

const [prefs, setPrefs] = useState<KloqPreferences>(DEFAULT_PREFERENCES);
const [open, setOpen] = useState(false);

<>
  <SettingsDialog open={open} onOpenChange={setOpen}
    value={prefs} onChange={setPrefs} />
  <Kloq
    weekStartsOn={prefs.weekStartsOn}
    showWeekNumbers={prefs.showWeekNumbers}
    showDeclinedEvents={prefs.showDeclinedEvents}
    navigationKeys={prefs.navigationKeys}
    locale={prefs.language || undefined}
  />
</>

The KloqPreferences model

Flat and serializable on purpose: persist the whole object under one key. Every field maps to either a <Kloq> prop or a host behaviour, and nothing is optional — a settings screen with tri-state values renders switches that are neither on nor off. DEFAULT_PREFERENCES matches the calendar's own defaults.

showWeekends?
boolean

Show Saturday and Sunday columns.

showDeclinedEvents?
boolean

Maps to the showDeclinedEvents prop.

showWeekNumbers?
boolean

ISO week numbers in the gutter.

weekStartsOn?
WeekStart

0 Sunday, 1 Monday, 6 Saturday.

navigationKeys?
NavigationKeys

"range" — ←/→ step the visible range; "day" — always one day.

language?
string

BCP 47 tag, or "" for "follow the system". LANGUAGE_OPTIONS is the built-in list; pass your own.

desktopNotifications?
boolean

The host may show desktop notifications at all.

eventReminders?
boolean

Fire an alert before each event — delivery is the host's job.

defaultReminderMinutes?
number

The default reminder offset stamped on new events.

dailyAgenda?
boolean

A once-a-morning summary of the day.

SettingsDialogProps

Four panes — "general", "accounts", "conferencing", "notifications" (SettingsSection; defaultSection picks which opens first). Beyond the value / onChange pair, the props follow one honesty rule: a button with nothing behind it isn't rendered. Omit onAddAccount and there is no "Add calendar account" button — the package owns no OAuth flow, so a connect button that couldn't connect would be a lie.

open / onOpenChange?
boolean / (open) => void

Standard dialog control.

value / onChange?
KloqPreferences

The whole preferences object, fully controlled.

accounts / sources?
CalendarAccount[] / CalendarSource[]

Rows to render. Omit to read the calendar-source registry.

conferencing?
readonly ConferencingProvider[]

Options offered; default CONFERENCING_PROVIDERS.

languages?
readonly {id; label}[]

Default LANGUAGE_OPTIONS.

onAddAccount?
() => void

Omit and the button isn't rendered.

onDisconnectAccount / onSetAccountConferencing?
(accountId, …) => void

Forwarded to the account rows. Omit → the registry handles it.

defaultSection?
SettingsSection

Which pane opens first; default "general".

Accounts as first-class

Accounts are the one exception to "the dialog owns nothing" — they already have a registry (registerCalendarSources), and the account rows write through it unless the host passes handlers: the same controlled/uncontrolled split as everything else. The module re-exports the grouping's helpers so a host's own settings screen can build the same rows: findAccount, defaultAccount, setDefaultAccount, disconnectAccount, providerLabel, conferencingLabel, accountDefaultCalendar, setAccountDefaultCalendar, resolveDefaultCalendar, and setAccountConferencing.

SegmentedChoice

The package ships no Select and no RadioGroup, so every "pick one of a few" control in the dialog is SegmentedChoice — a segmented control built from buttons with a real role="radiogroup" / role="radio" pair, exported for hosts building matching settings UI. For 2–5 options it is the better control anyway: every choice is visible and it costs one click, not two.

on this page