Files
picpeak/frontend/src/components/common/index.ts
T
Paul Nothaft a3fcb5bc9e feat(common): generic Promise-based ConfirmDialog primitive (#640 part C)
Ports 8digit/picpeak@88bfde1 — replaces `window.confirm()` with a styled,
themed, accessible in-app modal. Usage:

    const confirm = useConfirm();
    const ok = await confirm({
      title: 'Delete event?',
      message: 'This will permanently remove the gallery and all photos.',
      variant: 'danger',
      confirmLabel: 'Delete',
    });
    if (ok) doDelete();

Three variants: 'primary' (default, no icon), 'danger' (red AlertCircle +
red confirm button), 'warning' (amber AlertTriangle). Keyboard support:
Escape cancels, Enter confirms (unless focus is in an input/textarea/select
so an open form doesn't get hijacked), backdrop click cancels. Cancel button
is focused by default — a stray Enter cannot accidentally confirm a
destructive action.

Wraps at App.tsx level, inside GlobalThemeProvider so the modal respects
the theme tokens, above the toast container so a confirm appearing under a
toast still gets the click. Provider exports through components/common
alongside the rest of the shared primitives.

This PR only lands the primitive. Existing window.confirm() call-sites are
left untouched — sweeping them is follow-up work that can land in any
cadence (each sweep is one component, no architectural risk). Existing
structured-input flows (PublishGalleryDialog, DuplicateEventDialog,
PasswordResetModal, etc.) stay as-is — they collect data, not yes/no.

No new i18n entries — uses common.cancel / common.confirm / common.close
which already exist in EN + DE.

### Test plan

- [x] tsc --noEmit clean
- [x] eslint clean on changed files
- [ ] Manual: pick any existing window.confirm() site (e.g. EventDetailsPage
      delete button), swap to useConfirm(), verify the modal renders with
      theme tokens, Escape cancels, Enter confirms, backdrop click cancels,
      focus lands on Cancel
- [ ] Manual: variant='danger' renders red confirm button + AlertCircle icon
- [ ] Manual: open the dialog from inside another modal (e.g. a settings
      panel) — z-[9999] keeps the confirm on top of any other overlay
2026-06-18 22:36:40 +02:00

33 lines
1.5 KiB
TypeScript

export { Button } from './Button';
export { CMSContentBlock } from './CMSContentBlock';
export { Input } from './Input';
export { CountrySelect } from './CountrySelect';
export { LocalizedDateInput } from './LocalizedDateInput';
export { TimeField, parseTimeToHHMM } from './TimeField';
export { SortableHeader, useColumnSort } from './SortableHeader';
export type { SortDir, SortPair, SortColumnMap } from './SortableHeader';
export { Card, CardHeader, CardContent, CardFooter } from './Card';
export { Loading, LoadingSkeleton } from './Loading';
export { ErrorBoundary, PageErrorBoundary } from './ErrorBoundary';
export {
Skeleton,
SkeletonGroup,
SkeletonCard,
SkeletonTable,
SkeletonGalleryGrid,
SkeletonList
} from './Skeleton';
export { OfflineIndicator, useOnlineStatus } from './OfflineIndicator';
export { SkipLink } from './SkipLink';
export { DynamicFavicon } from './DynamicFavicon';
export { RobotsMetaTags } from './RobotsMetaTags';
export { LanguageSelector, SUPPORTED_LANGUAGES } from './LanguageSelector';
export { AuthenticatedImage } from './AuthenticatedImage';
export { AuthenticatedVideo } from './AuthenticatedVideo';
export { ProtectedImage } from './ProtectedImage';
export { ProtectionWarning } from './ProtectionWarning';
export { ReCaptcha } from './ReCaptcha';
export { PasswordGenerator } from './PasswordGenerator';
export { MarkdownContent } from './MarkdownContent';
export { ConfirmDialogProvider, useConfirm, type ConfirmOptions, type ConfirmVariant } from './ConfirmDialog';