@hoardodile/ui 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/README.md +70 -0
- package/dist/components/alert.d.ts +13 -0
- package/dist/components/alert.js +97 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/app-dialog.d.ts +62 -0
- package/dist/components/app-dialog.js +656 -0
- package/dist/components/app-dialog.js.map +1 -0
- package/dist/components/badge.d.ts +11 -0
- package/dist/components/badge.js +70 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/breadcrumb.d.ts +12 -0
- package/dist/components/breadcrumb.js +617 -0
- package/dist/components/breadcrumb.js.map +1 -0
- package/dist/components/button.d.ts +17 -0
- package/dist/components/button.js +78 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/caption-bar.d.ts +44 -0
- package/dist/components/caption-bar.js +827 -0
- package/dist/components/caption-bar.js.map +1 -0
- package/dist/components/chart-card.d.ts +15 -0
- package/dist/components/chart-card.js +27 -0
- package/dist/components/chart-card.js.map +1 -0
- package/dist/components/checkbox.d.ts +6 -0
- package/dist/components/checkbox.js +76 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip-button.d.ts +28 -0
- package/dist/components/chip-button.js +87 -0
- package/dist/components/chip-button.js.map +1 -0
- package/dist/components/close-confirm-dialog.d.ts +19 -0
- package/dist/components/close-confirm-dialog.js +743 -0
- package/dist/components/close-confirm-dialog.js.map +1 -0
- package/dist/components/color-picker.d.ts +32 -0
- package/dist/components/color-picker.js +667 -0
- package/dist/components/color-picker.js.map +1 -0
- package/dist/components/combobox.d.ts +32 -0
- package/dist/components/combobox.js +1211 -0
- package/dist/components/combobox.js.map +1 -0
- package/dist/components/confirm-by-typing-dialog.d.ts +48 -0
- package/dist/components/confirm-by-typing-dialog.js +759 -0
- package/dist/components/confirm-by-typing-dialog.js.map +1 -0
- package/dist/components/confirm-dialog.d.ts +34 -0
- package/dist/components/confirm-dialog.js +710 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/count-badge.d.ts +13 -0
- package/dist/components/count-badge.js +39 -0
- package/dist/components/count-badge.js.map +1 -0
- package/dist/components/dialog.d.ts +40 -0
- package/dist/components/dialog.js +571 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +45 -0
- package/dist/components/dropdown-menu.js +1003 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/dropdown-select.d.ts +29 -0
- package/dist/components/dropdown-select.js +1036 -0
- package/dist/components/dropdown-select.js.map +1 -0
- package/dist/components/empty.d.ts +15 -0
- package/dist/components/empty.js +119 -0
- package/dist/components/empty.js.map +1 -0
- package/dist/components/filter-rail.d.ts +48 -0
- package/dist/components/filter-rail.js +231 -0
- package/dist/components/filter-rail.js.map +1 -0
- package/dist/components/fixed-action-bar.d.ts +24 -0
- package/dist/components/fixed-action-bar.js +48 -0
- package/dist/components/fixed-action-bar.js.map +1 -0
- package/dist/components/font-picker.d.ts +45 -0
- package/dist/components/font-picker.js +759 -0
- package/dist/components/font-picker.js.map +1 -0
- package/dist/components/form.d.ts +17 -0
- package/dist/components/form.js +150 -0
- package/dist/components/form.js.map +1 -0
- package/dist/components/group-label.d.ts +12 -0
- package/dist/components/group-label.js +10 -0
- package/dist/components/group-label.js.map +1 -0
- package/dist/components/icon-tile.d.ts +16 -0
- package/dist/components/icon-tile.js +67 -0
- package/dist/components/icon-tile.js.map +1 -0
- package/dist/components/icon-toggle.d.ts +24 -0
- package/dist/components/icon-toggle.js +81 -0
- package/dist/components/icon-toggle.js.map +1 -0
- package/dist/components/icon.d.ts +2 -0
- package/dist/components/icon.js +49 -0
- package/dist/components/icon.js.map +1 -0
- package/dist/components/image-crop-panel.d.ts +80 -0
- package/dist/components/image-crop-panel.js +1104 -0
- package/dist/components/image-crop-panel.js.map +1 -0
- package/dist/components/image-cropper.d.ts +63 -0
- package/dist/components/image-cropper.js +166 -0
- package/dist/components/image-cropper.js.map +1 -0
- package/dist/components/input-group.d.ts +22 -0
- package/dist/components/input-group.js +270 -0
- package/dist/components/input-group.js.map +1 -0
- package/dist/components/input.d.ts +16 -0
- package/dist/components/input.js +60 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/label.d.ts +5 -0
- package/dist/components/label.js +40 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/list-empty-row.d.ts +16 -0
- package/dist/components/list-empty-row.js +40 -0
- package/dist/components/list-empty-row.js.map +1 -0
- package/dist/components/management-empty.d.ts +14 -0
- package/dist/components/management-empty.js +40 -0
- package/dist/components/management-empty.js.map +1 -0
- package/dist/components/management-skeleton.d.ts +11 -0
- package/dist/components/management-skeleton.js +72 -0
- package/dist/components/management-skeleton.js.map +1 -0
- package/dist/components/meta-chip.d.ts +17 -0
- package/dist/components/meta-chip.js +49 -0
- package/dist/components/meta-chip.js.map +1 -0
- package/dist/components/mobile-drawer.d.ts +29 -0
- package/dist/components/mobile-drawer.js +331 -0
- package/dist/components/mobile-drawer.js.map +1 -0
- package/dist/components/page-header.d.ts +20 -0
- package/dist/components/page-header.js +44 -0
- package/dist/components/page-header.js.map +1 -0
- package/dist/components/page-scaffold.d.ts +34 -0
- package/dist/components/page-scaffold.js +157 -0
- package/dist/components/page-scaffold.js.map +1 -0
- package/dist/components/pagination-bar.d.ts +22 -0
- package/dist/components/pagination-bar.js +703 -0
- package/dist/components/pagination-bar.js.map +1 -0
- package/dist/components/pagination.d.ts +22 -0
- package/dist/components/pagination.js +680 -0
- package/dist/components/pagination.js.map +1 -0
- package/dist/components/panel-toolbar.d.ts +37 -0
- package/dist/components/panel-toolbar.js +781 -0
- package/dist/components/panel-toolbar.js.map +1 -0
- package/dist/components/pill-tabs.d.ts +33 -0
- package/dist/components/pill-tabs.js +66 -0
- package/dist/components/pill-tabs.js.map +1 -0
- package/dist/components/plugin-download-consent.d.ts +52 -0
- package/dist/components/plugin-download-consent.js +780 -0
- package/dist/components/plugin-download-consent.js.map +1 -0
- package/dist/components/popover.d.ts +26 -0
- package/dist/components/popover.js +343 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +10 -0
- package/dist/components/progress.js +103 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/query-state-view.d.ts +36 -0
- package/dist/components/query-state-view.js +17 -0
- package/dist/components/query-state-view.js.map +1 -0
- package/dist/components/radio-group.d.ts +8 -0
- package/dist/components/radio-group.js +60 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/scroll-area.d.ts +7 -0
- package/dist/components/scroll-area.js +80 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/search-field.d.ts +49 -0
- package/dist/components/search-field.js +586 -0
- package/dist/components/search-field.js.map +1 -0
- package/dist/components/secondary-stat.d.ts +13 -0
- package/dist/components/secondary-stat.js +56 -0
- package/dist/components/secondary-stat.js.map +1 -0
- package/dist/components/section-header.d.ts +23 -0
- package/dist/components/section-header.js +76 -0
- package/dist/components/section-header.js.map +1 -0
- package/dist/components/section-label.d.ts +20 -0
- package/dist/components/section-label.js +46 -0
- package/dist/components/section-label.js.map +1 -0
- package/dist/components/section-tabs.d.ts +47 -0
- package/dist/components/section-tabs.js +145 -0
- package/dist/components/section-tabs.js.map +1 -0
- package/dist/components/separator.d.ts +14 -0
- package/dist/components/separator.js +49 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +22 -0
- package/dist/components/sheet.js +469 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/sidebar.d.ts +69 -0
- package/dist/components/sidebar.js +1645 -0
- package/dist/components/sidebar.js.map +1 -0
- package/dist/components/skeleton.d.ts +8 -0
- package/dist/components/skeleton.js +41 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/slider.d.ts +6 -0
- package/dist/components/slider.js +75 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sortable-chip-list.d.ts +26 -0
- package/dist/components/sortable-chip-list.js +59 -0
- package/dist/components/sortable-chip-list.js.map +1 -0
- package/dist/components/spinner.d.ts +5 -0
- package/dist/components/spinner.js +65 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/components/surface.d.ts +13 -0
- package/dist/components/surface.js +51 -0
- package/dist/components/surface.js.map +1 -0
- package/dist/components/switch.d.ts +8 -0
- package/dist/components/switch.js +53 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/table.d.ts +12 -0
- package/dist/components/table.js +129 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/textarea.d.ts +15 -0
- package/dist/components/textarea.js +58 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toast.d.ts +19 -0
- package/dist/components/toast.js +805 -0
- package/dist/components/toast.js.map +1 -0
- package/dist/components/toggle.d.ts +12 -0
- package/dist/components/toggle.js +63 -0
- package/dist/components/toggle.js.map +1 -0
- package/dist/components/tooltip.d.ts +9 -0
- package/dist/components/tooltip.js +82 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/hooks/use-dialog-footer-actions.d.ts +18 -0
- package/dist/hooks/use-dialog-footer-actions.js +11 -0
- package/dist/hooks/use-dialog-footer-actions.js.map +1 -0
- package/dist/hooks/use-form-field.d.ts +33 -0
- package/dist/hooks/use-form-field.js +36 -0
- package/dist/hooks/use-form-field.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +34 -0
- package/dist/hooks/use-mobile.js +29 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/hooks/use-pinch-pan.d.ts +56 -0
- package/dist/hooks/use-pinch-pan.js +223 -0
- package/dist/hooks/use-pinch-pan.js.map +1 -0
- package/dist/hooks/useMobileBackToClose.d.ts +61 -0
- package/dist/hooks/useMobileBackToClose.js +234 -0
- package/dist/hooks/useMobileBackToClose.js.map +1 -0
- package/dist/icon-BOgL5bpL.d.ts +85 -0
- package/dist/icons/actions.d.ts +4 -0
- package/dist/icons/actions.js +520 -0
- package/dist/icons/actions.js.map +1 -0
- package/dist/icons/icon-style.d.ts +2 -0
- package/dist/icons/icon-style.js +51 -0
- package/dist/icons/icon-style.js.map +1 -0
- package/dist/icons/marks.d.ts +15 -0
- package/dist/icons/marks.js +48 -0
- package/dist/icons/marks.js.map +1 -0
- package/dist/icons/registry.d.ts +177 -0
- package/dist/icons/registry.js +1180 -0
- package/dist/icons/registry.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +653 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/merge-config.d.ts +15 -0
- package/dist/lib/merge-config.js +20 -0
- package/dist/lib/merge-config.js.map +1 -0
- package/dist/lib/pagination.d.ts +19 -0
- package/dist/lib/pagination.js +26 -0
- package/dist/lib/pagination.js.map +1 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +26 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles/theme.css +480 -0
- package/dist/styles/theme.css.map +1 -0
- package/dist/styles/theme.d.ts +2 -0
- package/dist/viewport.d.ts +64 -0
- package/dist/viewport.js +13 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +122 -0
- package/src/components/alert.tsx +76 -0
- package/src/components/app-dialog.tsx +187 -0
- package/src/components/badge.tsx +52 -0
- package/src/components/breadcrumb.tsx +129 -0
- package/src/components/button.tsx +70 -0
- package/src/components/caption-bar.test.tsx +151 -0
- package/src/components/caption-bar.tsx +392 -0
- package/src/components/chart-card.tsx +31 -0
- package/src/components/checkbox.tsx +28 -0
- package/src/components/chip-button.tsx +69 -0
- package/src/components/close-confirm-dialog.tsx +99 -0
- package/src/components/color-picker.tsx +186 -0
- package/src/components/combobox.tsx +335 -0
- package/src/components/confirm-by-typing-dialog.tsx +130 -0
- package/src/components/confirm-dialog.test.tsx +39 -0
- package/src/components/confirm-dialog.tsx +98 -0
- package/src/components/count-badge.tsx +23 -0
- package/src/components/dialog.tsx +315 -0
- package/src/components/dropdown-menu.tsx +311 -0
- package/src/components/dropdown-select.tsx +211 -0
- package/src/components/empty.tsx +104 -0
- package/src/components/filter-rail.tsx +139 -0
- package/src/components/fixed-action-bar.tsx +46 -0
- package/src/components/font-picker.tsx +257 -0
- package/src/components/form.tsx +127 -0
- package/src/components/group-label.tsx +13 -0
- package/src/components/icon-tile.tsx +35 -0
- package/src/components/icon-toggle.tsx +57 -0
- package/src/components/icon.tsx +66 -0
- package/src/components/image-crop-panel.test.tsx +167 -0
- package/src/components/image-crop-panel.tsx +465 -0
- package/src/components/image-cropper.test.tsx +144 -0
- package/src/components/image-cropper.tsx +265 -0
- package/src/components/input-group.tsx +156 -0
- package/src/components/input.tsx +45 -0
- package/src/components/label.tsx +20 -0
- package/src/components/list-empty-row.tsx +27 -0
- package/src/components/management-empty.tsx +25 -0
- package/src/components/management-skeleton.tsx +23 -0
- package/src/components/meta-chip.tsx +40 -0
- package/src/components/mobile-drawer.test.tsx +52 -0
- package/src/components/mobile-drawer.tsx +98 -0
- package/src/components/page-header.tsx +47 -0
- package/src/components/page-scaffold.test.tsx +24 -0
- package/src/components/page-scaffold.tsx +131 -0
- package/src/components/pagination-bar.test.tsx +87 -0
- package/src/components/pagination-bar.tsx +139 -0
- package/src/components/pagination.tsx +139 -0
- package/src/components/panel-toolbar.tsx +99 -0
- package/src/components/pill-tabs.test.tsx +84 -0
- package/src/components/pill-tabs.tsx +77 -0
- package/src/components/plugin-download-consent.test.tsx +90 -0
- package/src/components/plugin-download-consent.tsx +183 -0
- package/src/components/popover.tsx +133 -0
- package/src/components/progress.tsx +88 -0
- package/src/components/query-state-view.tsx +47 -0
- package/src/components/radio-group.tsx +36 -0
- package/src/components/scroll-area.tsx +52 -0
- package/src/components/search-field.test.tsx +86 -0
- package/src/components/search-field.tsx +119 -0
- package/src/components/secondary-stat.tsx +24 -0
- package/src/components/section-header.tsx +54 -0
- package/src/components/section-label.tsx +40 -0
- package/src/components/section-tabs.test.tsx +154 -0
- package/src/components/section-tabs.tsx +190 -0
- package/src/components/separator.tsx +38 -0
- package/src/components/sheet.tsx +170 -0
- package/src/components/sidebar.tsx +725 -0
- package/src/components/skeleton.tsx +18 -0
- package/src/components/slider.tsx +52 -0
- package/src/components/sortable-chip-list.tsx +56 -0
- package/src/components/spinner.tsx +35 -0
- package/src/components/surface.tsx +37 -0
- package/src/components/switch.tsx +30 -0
- package/src/components/table.tsx +116 -0
- package/src/components/textarea.tsx +41 -0
- package/src/components/toast.tsx +239 -0
- package/src/components/toggle.tsx +45 -0
- package/src/components/tooltip.tsx +64 -0
- package/src/hooks/use-dialog-footer-actions.ts +23 -0
- package/src/hooks/use-form-field.ts +62 -0
- package/src/hooks/use-mobile.ts +54 -0
- package/src/hooks/use-pinch-pan.test.tsx +42 -0
- package/src/hooks/use-pinch-pan.ts +340 -0
- package/src/hooks/useMobileBackToClose.test.tsx +140 -0
- package/src/hooks/useMobileBackToClose.ts +508 -0
- package/src/icons/actions.ts +10 -0
- package/src/icons/icon-style.test.tsx +69 -0
- package/src/icons/icon-style.ts +104 -0
- package/src/icons/marks.tsx +72 -0
- package/src/icons/registry.test.ts +65 -0
- package/src/icons/registry.ts +1163 -0
- package/src/index.ts +22 -0
- package/src/lib/merge-config.test.ts +109 -0
- package/src/lib/merge-config.ts +22 -0
- package/src/lib/pagination.test.ts +55 -0
- package/src/lib/pagination.ts +42 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/scan-contract.test.ts +74 -0
- package/src/styles/theme.css +798 -0
- package/src/test/i18n.tsx +22 -0
- package/src/test/setup.ts +104 -0
- package/src/viewport.ts +69 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { AppDialog } from "@hoardodile/ui/components/app-dialog"
|
|
2
|
+
import { Button } from "@hoardodile/ui/components/button"
|
|
3
|
+
import { Input } from "@hoardodile/ui/components/input"
|
|
4
|
+
import { type ReactNode } from "react"
|
|
5
|
+
import { Trans, useTranslation } from "react-i18next"
|
|
6
|
+
|
|
7
|
+
export type ConfirmByTypingDialogProps = Readonly<{
|
|
8
|
+
open: boolean
|
|
9
|
+
onOpenChange(open: boolean): void
|
|
10
|
+
title: string
|
|
11
|
+
description: string
|
|
12
|
+
/** Exact string the user must type to enable the confirm button. */
|
|
13
|
+
expectedInput: string
|
|
14
|
+
/**
|
|
15
|
+
* The target name rendered in bold inside the one-line confirm prompt.
|
|
16
|
+
*/
|
|
17
|
+
targetName: string
|
|
18
|
+
confirmLabel: string
|
|
19
|
+
pendingLabel: string
|
|
20
|
+
pending: boolean
|
|
21
|
+
/** When true the dialog takes the danger register (destructive header
|
|
22
|
+
ink) and the confirm button uses the destructive variant. */
|
|
23
|
+
destructive?: boolean
|
|
24
|
+
typed: string
|
|
25
|
+
onTypedChange(value: string): void
|
|
26
|
+
onConfirm(): void
|
|
27
|
+
/**
|
|
28
|
+
* The one-line confirm prompt (rendered below the description) —
|
|
29
|
+
* defaults to the shared "Type <name>…</name> to confirm" copy with
|
|
30
|
+
* the target name bolded.
|
|
31
|
+
*/
|
|
32
|
+
prompt?: ReactNode
|
|
33
|
+
/** Cancel button label — defaults to the shared "Cancel" copy. */
|
|
34
|
+
cancelLabel?: ReactNode
|
|
35
|
+
inputTestId?: string
|
|
36
|
+
confirmTestId?: string
|
|
37
|
+
/** Forwarded to the dialog content (e.g. theme scope classes). */
|
|
38
|
+
contentClassName?: string
|
|
39
|
+
}>
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Shared "type the name to confirm" dialog for any operation that
|
|
43
|
+
* requires the user to re-read a target name and type it back. The
|
|
44
|
+
* type-to-confirm anatomy: the consequence reads in the header
|
|
45
|
+
* description, the target name sits in bold inside the one-line prompt
|
|
46
|
+
* (`Type “harbor” to confirm`), and the confirm stays gated until the
|
|
47
|
+
* input matches. The prompt is rendered uniformly; only the bold target
|
|
48
|
+
* name varies.
|
|
49
|
+
*/
|
|
50
|
+
export function ConfirmByTypingDialog(props: ConfirmByTypingDialogProps) {
|
|
51
|
+
const {
|
|
52
|
+
open,
|
|
53
|
+
onOpenChange,
|
|
54
|
+
title,
|
|
55
|
+
description,
|
|
56
|
+
expectedInput,
|
|
57
|
+
confirmLabel,
|
|
58
|
+
pendingLabel,
|
|
59
|
+
pending,
|
|
60
|
+
destructive = true,
|
|
61
|
+
typed,
|
|
62
|
+
onTypedChange,
|
|
63
|
+
onConfirm,
|
|
64
|
+
targetName,
|
|
65
|
+
prompt,
|
|
66
|
+
cancelLabel,
|
|
67
|
+
inputTestId,
|
|
68
|
+
confirmTestId,
|
|
69
|
+
contentClassName,
|
|
70
|
+
} = props
|
|
71
|
+
const { t } = useTranslation("ui", { useSuspense: false })
|
|
72
|
+
const canConfirm = !pending && typed === expectedInput
|
|
73
|
+
function handleOpenChange(next: boolean) {
|
|
74
|
+
if (pending && !next) return
|
|
75
|
+
onOpenChange(next)
|
|
76
|
+
}
|
|
77
|
+
return (
|
|
78
|
+
<AppDialog
|
|
79
|
+
open={open}
|
|
80
|
+
onOpenChange={handleOpenChange}
|
|
81
|
+
title={title}
|
|
82
|
+
description={description}
|
|
83
|
+
danger={destructive}
|
|
84
|
+
contentClassName={contentClassName}
|
|
85
|
+
footer={
|
|
86
|
+
<>
|
|
87
|
+
<Button
|
|
88
|
+
variant="secondary"
|
|
89
|
+
onClick={() => onOpenChange(false)}
|
|
90
|
+
disabled={pending}
|
|
91
|
+
>
|
|
92
|
+
{cancelLabel ?? t("dialog.cancel")}
|
|
93
|
+
</Button>
|
|
94
|
+
<Button
|
|
95
|
+
variant={destructive ? "destructive" : "default"}
|
|
96
|
+
disabled={!canConfirm}
|
|
97
|
+
onClick={onConfirm}
|
|
98
|
+
data-testid={confirmTestId}
|
|
99
|
+
>
|
|
100
|
+
{pending ? pendingLabel : confirmLabel}
|
|
101
|
+
</Button>
|
|
102
|
+
</>
|
|
103
|
+
}
|
|
104
|
+
>
|
|
105
|
+
<div className="flex flex-col gap-1.5">
|
|
106
|
+
<span className="text-xs text-muted-foreground">
|
|
107
|
+
{prompt ?? (
|
|
108
|
+
<Trans
|
|
109
|
+
ns="ui"
|
|
110
|
+
i18nKey="confirmByTyping.prompt"
|
|
111
|
+
values={{ name: targetName }}
|
|
112
|
+
components={{
|
|
113
|
+
name: (
|
|
114
|
+
<span className="font-semibold text-foreground" />
|
|
115
|
+
),
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
</span>
|
|
120
|
+
<Input
|
|
121
|
+
autoFocus
|
|
122
|
+
value={typed}
|
|
123
|
+
onChange={(e) => onTypedChange(e.target.value)}
|
|
124
|
+
autoComplete="off"
|
|
125
|
+
data-testid={inputTestId}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
</AppDialog>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { screen } from "@testing-library/react"
|
|
2
|
+
import userEvent from "@testing-library/user-event"
|
|
3
|
+
import { describe, expect, it, vi } from "vitest"
|
|
4
|
+
import { testI18n, renderWithI18n } from "../test/i18n"
|
|
5
|
+
import { ConfirmDialog } from "./confirm-dialog"
|
|
6
|
+
|
|
7
|
+
function renderDialog(
|
|
8
|
+
overrides: Partial<Parameters<typeof ConfirmDialog>[0]> = {},
|
|
9
|
+
) {
|
|
10
|
+
const onOpenChange = vi.fn()
|
|
11
|
+
const onConfirm = vi.fn()
|
|
12
|
+
renderWithI18n(
|
|
13
|
+
<ConfirmDialog
|
|
14
|
+
open
|
|
15
|
+
onOpenChange={onOpenChange}
|
|
16
|
+
title="Title"
|
|
17
|
+
confirmLabel="Confirm"
|
|
18
|
+
isPending={false}
|
|
19
|
+
onConfirm={onConfirm}
|
|
20
|
+
{...overrides}
|
|
21
|
+
/>,
|
|
22
|
+
)
|
|
23
|
+
return { onOpenChange, onConfirm }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("ConfirmDialog", () => {
|
|
27
|
+
it("defaults the cancel button to the shared cancel copy", async () => {
|
|
28
|
+
const { onOpenChange } = renderDialog()
|
|
29
|
+
await userEvent.click(screen.getByRole("button", { name: "Cancel" }))
|
|
30
|
+
expect(onOpenChange).toHaveBeenCalledWith(false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("uses the localized cancel copy in another language", async () => {
|
|
34
|
+
await testI18n.changeLanguage("zh")
|
|
35
|
+
renderDialog()
|
|
36
|
+
expect(screen.getByRole("button", { name: "取消" })).toBeInTheDocument()
|
|
37
|
+
await testI18n.changeLanguage("en")
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { AppDialog } from "@hoardodile/ui/components/app-dialog"
|
|
2
|
+
import { Button } from "@hoardodile/ui/components/button"
|
|
3
|
+
import { type ReactNode } from "react"
|
|
4
|
+
import { useTranslation } from "react-i18next"
|
|
5
|
+
|
|
6
|
+
export type ConfirmDialogProps = Readonly<{
|
|
7
|
+
open: boolean
|
|
8
|
+
onOpenChange: (open: boolean) => void
|
|
9
|
+
title: ReactNode
|
|
10
|
+
description?: ReactNode
|
|
11
|
+
body?: ReactNode
|
|
12
|
+
confirmLabel: ReactNode
|
|
13
|
+
pendingLabel?: ReactNode
|
|
14
|
+
/** Cancel button label; defaults to the shared "Cancel" copy. */
|
|
15
|
+
cancelLabel?: ReactNode
|
|
16
|
+
isPending: boolean
|
|
17
|
+
destructive?: boolean
|
|
18
|
+
confirmDisabled?: boolean
|
|
19
|
+
onConfirm: () => void
|
|
20
|
+
confirmTestId?: string
|
|
21
|
+
cancelTestId?: string
|
|
22
|
+
contentClassName?: string
|
|
23
|
+
overlayClassName?: string
|
|
24
|
+
contentMotion?: "default" | "minimal"
|
|
25
|
+
suppressAutoFocus?: boolean
|
|
26
|
+
/** When true, closing while pending is blocked. Defaults to true. */
|
|
27
|
+
lockWhilePending?: boolean
|
|
28
|
+
}>
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cancel + primary action dialog. Disables and (by default) blocks
|
|
32
|
+
* dismissal while `isPending` so a user does not double-submit a
|
|
33
|
+
* destructive action.
|
|
34
|
+
*/
|
|
35
|
+
export function ConfirmDialog(props: ConfirmDialogProps) {
|
|
36
|
+
const {
|
|
37
|
+
open,
|
|
38
|
+
onOpenChange,
|
|
39
|
+
title,
|
|
40
|
+
description,
|
|
41
|
+
body,
|
|
42
|
+
confirmLabel,
|
|
43
|
+
pendingLabel,
|
|
44
|
+
cancelLabel,
|
|
45
|
+
isPending,
|
|
46
|
+
destructive = false,
|
|
47
|
+
confirmDisabled = false,
|
|
48
|
+
onConfirm,
|
|
49
|
+
confirmTestId,
|
|
50
|
+
cancelTestId,
|
|
51
|
+
contentClassName,
|
|
52
|
+
overlayClassName,
|
|
53
|
+
contentMotion,
|
|
54
|
+
suppressAutoFocus,
|
|
55
|
+
lockWhilePending = true,
|
|
56
|
+
} = props
|
|
57
|
+
const { t } = useTranslation("ui", { useSuspense: false })
|
|
58
|
+
function handleOpenChange(next: boolean) {
|
|
59
|
+
if (lockWhilePending && isPending && !next) return
|
|
60
|
+
onOpenChange(next)
|
|
61
|
+
}
|
|
62
|
+
const footer = (
|
|
63
|
+
<>
|
|
64
|
+
<Button
|
|
65
|
+
variant="secondary"
|
|
66
|
+
onClick={() => handleOpenChange(false)}
|
|
67
|
+
disabled={isPending}
|
|
68
|
+
data-testid={cancelTestId}
|
|
69
|
+
>
|
|
70
|
+
{cancelLabel ?? t("dialog.cancel")}
|
|
71
|
+
</Button>
|
|
72
|
+
<Button
|
|
73
|
+
variant={destructive ? "destructive" : "default"}
|
|
74
|
+
onClick={onConfirm}
|
|
75
|
+
disabled={isPending || confirmDisabled}
|
|
76
|
+
data-testid={confirmTestId}
|
|
77
|
+
>
|
|
78
|
+
{isPending && pendingLabel !== undefined ? pendingLabel : confirmLabel}
|
|
79
|
+
</Button>
|
|
80
|
+
</>
|
|
81
|
+
)
|
|
82
|
+
return (
|
|
83
|
+
<AppDialog
|
|
84
|
+
open={open}
|
|
85
|
+
onOpenChange={handleOpenChange}
|
|
86
|
+
title={title}
|
|
87
|
+
description={description}
|
|
88
|
+
danger={destructive}
|
|
89
|
+
footer={footer}
|
|
90
|
+
contentClassName={contentClassName}
|
|
91
|
+
overlayClassName={overlayClassName}
|
|
92
|
+
contentMotion={contentMotion}
|
|
93
|
+
suppressAutoFocus={suppressAutoFocus}
|
|
94
|
+
>
|
|
95
|
+
{body}
|
|
96
|
+
</AppDialog>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cn } from "@hoardodile/ui/lib/utils"
|
|
2
|
+
|
|
3
|
+
export type CountBadgeProps = {
|
|
4
|
+
readonly count: number
|
|
5
|
+
readonly className?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Count badge — the inverted 16px pill worn by buttons to show how many
|
|
10
|
+
* filters are active, how many documents are pinned, etc.
|
|
11
|
+
*/
|
|
12
|
+
export function CountBadge(props: CountBadgeProps) {
|
|
13
|
+
return (
|
|
14
|
+
<span
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex size-4 items-center justify-center rounded-full bg-foreground text-tiny text-background",
|
|
17
|
+
props.className,
|
|
18
|
+
)}
|
|
19
|
+
>
|
|
20
|
+
{props.count}
|
|
21
|
+
</span>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
|
|
3
|
+
import { useTranslation } from "react-i18next"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@hoardodile/ui/lib/utils"
|
|
6
|
+
import { Button } from "@hoardodile/ui/components/button"
|
|
7
|
+
import { useMobileBackToClose } from "@hoardodile/ui/hooks/useMobileBackToClose"
|
|
8
|
+
import { Cross } from "@hoardodile/ui/icons/marks"
|
|
9
|
+
|
|
10
|
+
function Dialog({
|
|
11
|
+
open,
|
|
12
|
+
defaultOpen,
|
|
13
|
+
onOpenChange,
|
|
14
|
+
...props
|
|
15
|
+
}: Omit<DialogPrimitive.Root.Props, "onOpenChange"> & {
|
|
16
|
+
/**
|
|
17
|
+
* `eventDetails` is absent for closes triggered by the mobile back
|
|
18
|
+
* gesture (a synthetic close with no underlying Base UI event).
|
|
19
|
+
*/
|
|
20
|
+
onOpenChange?: (
|
|
21
|
+
open: boolean,
|
|
22
|
+
eventDetails?: DialogPrimitive.Root.ChangeEventDetails,
|
|
23
|
+
) => void
|
|
24
|
+
}) {
|
|
25
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(
|
|
26
|
+
defaultOpen ?? false,
|
|
27
|
+
)
|
|
28
|
+
const isControlled = open !== undefined
|
|
29
|
+
const currentOpen = isControlled ? open : uncontrolledOpen
|
|
30
|
+
function handleOpenChange(
|
|
31
|
+
next: boolean,
|
|
32
|
+
eventDetails?: DialogPrimitive.Root.ChangeEventDetails,
|
|
33
|
+
) {
|
|
34
|
+
if (!isControlled) setUncontrolledOpen(next)
|
|
35
|
+
onOpenChange?.(next, eventDetails)
|
|
36
|
+
}
|
|
37
|
+
useMobileBackToClose(currentOpen, handleOpenChange)
|
|
38
|
+
return (
|
|
39
|
+
<DialogPrimitive.Root
|
|
40
|
+
data-slot="dialog"
|
|
41
|
+
open={currentOpen}
|
|
42
|
+
onOpenChange={handleOpenChange}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
|
|
49
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
|
|
53
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
|
|
57
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function DialogOverlay({
|
|
61
|
+
className,
|
|
62
|
+
...props
|
|
63
|
+
}: DialogPrimitive.Backdrop.Props) {
|
|
64
|
+
return (
|
|
65
|
+
<DialogPrimitive.Backdrop
|
|
66
|
+
data-slot="dialog-overlay"
|
|
67
|
+
className={cn(
|
|
68
|
+
// The dialog layer: a static whisper-of-ink scrim plus
|
|
69
|
+
// backdrop blur — part of the surface definition, not motion.
|
|
70
|
+
"fixed inset-0 isolate z-50 bg-foreground/5 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-sm",
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function DialogContent({
|
|
79
|
+
className,
|
|
80
|
+
children,
|
|
81
|
+
showCloseButton = true,
|
|
82
|
+
overlayClassName,
|
|
83
|
+
contentMotion = "default",
|
|
84
|
+
...props
|
|
85
|
+
}: DialogPrimitive.Popup.Props &
|
|
86
|
+
React.RefAttributes<HTMLDivElement> & {
|
|
87
|
+
showCloseButton?: boolean
|
|
88
|
+
/** Merged into {@link DialogOverlay}. Use to drop backdrop blur over heavy GPU layers (e.g. WebGL). */
|
|
89
|
+
overlayClassName?: string
|
|
90
|
+
/**
|
|
91
|
+
* `minimal` uses fade-only enter/exit (no slide/zoom) to reduce jank
|
|
92
|
+
* when opening over canvas/video or other expensive surfaces.
|
|
93
|
+
*/
|
|
94
|
+
contentMotion?: "default" | "minimal"
|
|
95
|
+
}) {
|
|
96
|
+
const { t } = useTranslation("ui", { useSuspense: false })
|
|
97
|
+
// Auto-wrap "loose" body children (anything that is not a DialogHeader
|
|
98
|
+
// or DialogFooter) inside a DialogBody. This guarantees a single
|
|
99
|
+
// scroll container — historically callers relied on DialogContent
|
|
100
|
+
// itself scrolling and added their own nested `overflow-y-auto`
|
|
101
|
+
// wrappers, which produced a confusing two-scrollbar behaviour on
|
|
102
|
+
// touch devices. With a single inner scroller, header/footer stay
|
|
103
|
+
// pinned and only the middle region scrolls on every viewport.
|
|
104
|
+
const arranged = arrangeDialogChildren(children)
|
|
105
|
+
const motionClasses =
|
|
106
|
+
contentMotion === "minimal"
|
|
107
|
+
? "data-ending-style:opacity-0 data-starting-style:opacity-0"
|
|
108
|
+
: cn(
|
|
109
|
+
"data-ending-style:translate-y-10 data-ending-style:opacity-0 data-starting-style:translate-y-10 data-starting-style:opacity-0",
|
|
110
|
+
// ≥ sm the popup is centred with -translate-y-1/2, so the
|
|
111
|
+
// starting/ending styles must keep that offset (zoom + fade
|
|
112
|
+
// only, no slide) instead of clobbering it with translate-y-0.
|
|
113
|
+
"sm:data-ending-style:-translate-y-1/2 sm:data-ending-style:scale-95 sm:data-starting-style:-translate-y-1/2 sm:data-starting-style:scale-95"
|
|
114
|
+
)
|
|
115
|
+
return (
|
|
116
|
+
<DialogPortal>
|
|
117
|
+
<DialogOverlay className={overlayClassName} />
|
|
118
|
+
<DialogPrimitive.Popup
|
|
119
|
+
data-slot="dialog-content"
|
|
120
|
+
className={cn(
|
|
121
|
+
// The floating-card anatomy: `bg-card`
|
|
122
|
+
// hairline-bordered surface with the system's one deep shadow.
|
|
123
|
+
// Mobile: bottom sheet occupying most of the viewport so edit
|
|
124
|
+
// forms have room to breathe. The component never scrolls
|
|
125
|
+
// itself; only the inner DialogBody does. Header/body/footer
|
|
126
|
+
// parts are held apart by the card's section gap
|
|
127
|
+
// (`gap-4` between the header document, the body and the
|
|
128
|
+
// hairline footer).
|
|
129
|
+
"fixed z-50 flex flex-col gap-4 bg-card text-sm text-card-foreground shadow-dialog outline-hidden transition duration-200 ease-in-out",
|
|
130
|
+
"inset-x-0 bottom-0 max-h-[85svh] overflow-hidden rounded-t-2xl rounded-b-none border border-b-0 border-border",
|
|
131
|
+
motionClasses,
|
|
132
|
+
// ≥ sm: centred modal, allow up to 90vh.
|
|
133
|
+
"sm:inset-x-auto sm:bottom-auto sm:top-1/2 sm:left-1/2 sm:w-full sm:max-w-md sm:max-h-[90vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-2xl",
|
|
134
|
+
className
|
|
135
|
+
)}
|
|
136
|
+
{...props}
|
|
137
|
+
>
|
|
138
|
+
{arranged}
|
|
139
|
+
{showCloseButton && (
|
|
140
|
+
<DialogPrimitive.Close
|
|
141
|
+
data-slot="dialog-close"
|
|
142
|
+
className="absolute top-3 right-3 z-20 flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors duration-150 hover:bg-muted hover:text-foreground"
|
|
143
|
+
>
|
|
144
|
+
<Cross className="size-3" />
|
|
145
|
+
<span className="sr-only">{t("aria.close")}</span>
|
|
146
|
+
</DialogPrimitive.Close>
|
|
147
|
+
)}
|
|
148
|
+
</DialogPrimitive.Popup>
|
|
149
|
+
</DialogPortal>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
154
|
+
return (
|
|
155
|
+
<div
|
|
156
|
+
data-slot="dialog-header"
|
|
157
|
+
className={cn(
|
|
158
|
+
// The header document: the card's `p-5`
|
|
159
|
+
// rhythm — 20px top and sides, no bottom padding — with the
|
|
160
|
+
// eyebrow, title row and description stacked tight; the section
|
|
161
|
+
// gap below separates it from the body.
|
|
162
|
+
"flex shrink-0 flex-col bg-card px-5 pt-5",
|
|
163
|
+
className
|
|
164
|
+
)}
|
|
165
|
+
{...props}
|
|
166
|
+
/>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Scrolling body region for dialogs. The dialog itself never scrolls —
|
|
172
|
+
* only this element does — so touch users always interact with one
|
|
173
|
+
* obvious scroll surface. The card's `p-5` side rhythm lives here; the
|
|
174
|
+
* section gaps (`gap-4`) separate it from the header and footer.
|
|
175
|
+
*/
|
|
176
|
+
function DialogBody({ className, ...props }: React.ComponentProps<"div">) {
|
|
177
|
+
return (
|
|
178
|
+
<div
|
|
179
|
+
data-slot="dialog-body"
|
|
180
|
+
className={cn(
|
|
181
|
+
"flex-1 overflow-y-auto overscroll-contain px-5",
|
|
182
|
+
className
|
|
183
|
+
)}
|
|
184
|
+
{...props}
|
|
185
|
+
/>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function DialogFooter({
|
|
190
|
+
className,
|
|
191
|
+
flush = false,
|
|
192
|
+
showCloseButton = false,
|
|
193
|
+
children,
|
|
194
|
+
...props
|
|
195
|
+
}: React.ComponentProps<"div"> & {
|
|
196
|
+
flush?: boolean
|
|
197
|
+
showCloseButton?: boolean
|
|
198
|
+
}) {
|
|
199
|
+
return (
|
|
200
|
+
<div
|
|
201
|
+
data-slot="dialog-footer"
|
|
202
|
+
className={cn("flex shrink-0 flex-col bg-card", className)}
|
|
203
|
+
{...props}
|
|
204
|
+
>
|
|
205
|
+
{/* Inset hairline: the same side padding as the content below, so
|
|
206
|
+
the divider never runs edge to edge. */}
|
|
207
|
+
<div
|
|
208
|
+
className={cn(
|
|
209
|
+
"border-t border-border",
|
|
210
|
+
flush ? "mx-4" : "mx-5"
|
|
211
|
+
)}
|
|
212
|
+
/>
|
|
213
|
+
<div
|
|
214
|
+
className={cn(
|
|
215
|
+
// The hairline action bar: 16px above the
|
|
216
|
+
// buttons, the card's bottom padding (20px) below.
|
|
217
|
+
"flex flex-row justify-end flex-wrap gap-2",
|
|
218
|
+
flush ? "p-4" : "px-5 pt-4 pb-5"
|
|
219
|
+
)}
|
|
220
|
+
>
|
|
221
|
+
{children}
|
|
222
|
+
{showCloseButton && (
|
|
223
|
+
<DialogPrimitive.Close render={<Button variant="outline" />}>
|
|
224
|
+
Close
|
|
225
|
+
</DialogPrimitive.Close>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
|
233
|
+
return (
|
|
234
|
+
<DialogPrimitive.Title
|
|
235
|
+
data-slot="dialog-title"
|
|
236
|
+
className={cn("text-sm font-semibold", className)}
|
|
237
|
+
{...props}
|
|
238
|
+
/>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function DialogDescription({
|
|
243
|
+
className,
|
|
244
|
+
...props
|
|
245
|
+
}: DialogPrimitive.Description.Props) {
|
|
246
|
+
return (
|
|
247
|
+
<DialogPrimitive.Description
|
|
248
|
+
data-slot="dialog-description"
|
|
249
|
+
className={cn(
|
|
250
|
+
"text-xs leading-5 text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
|
251
|
+
className
|
|
252
|
+
)}
|
|
253
|
+
{...props}
|
|
254
|
+
/>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Splits dialog children into header/footer/body groups so loose body
|
|
260
|
+
* nodes (passed by callers that pre-date `<DialogBody>`) get wrapped in
|
|
261
|
+
* a single scrolling region. Children already wrapped in
|
|
262
|
+
* `<DialogBody>` are passed through unchanged.
|
|
263
|
+
*/
|
|
264
|
+
function arrangeDialogChildren(children: React.ReactNode): React.ReactNode {
|
|
265
|
+
const headers: React.ReactNode[] = []
|
|
266
|
+
const footers: React.ReactNode[] = []
|
|
267
|
+
const bodyNodes: React.ReactNode[] = []
|
|
268
|
+
let hasExplicitBody = false
|
|
269
|
+
React.Children.forEach(children, (child) => {
|
|
270
|
+
if (!React.isValidElement(child)) {
|
|
271
|
+
bodyNodes.push(child)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
if (child.type === DialogHeader) {
|
|
275
|
+
headers.push(child)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
if (child.type === DialogFooter) {
|
|
279
|
+
footers.push(child)
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
if (child.type === DialogBody) {
|
|
283
|
+
hasExplicitBody = true
|
|
284
|
+
bodyNodes.push(child)
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
bodyNodes.push(child)
|
|
288
|
+
})
|
|
289
|
+
const body = hasExplicitBody ? (
|
|
290
|
+
bodyNodes
|
|
291
|
+
) : (
|
|
292
|
+
<DialogBody>{bodyNodes}</DialogBody>
|
|
293
|
+
)
|
|
294
|
+
return (
|
|
295
|
+
<>
|
|
296
|
+
{headers}
|
|
297
|
+
{body}
|
|
298
|
+
{footers}
|
|
299
|
+
</>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export {
|
|
304
|
+
Dialog,
|
|
305
|
+
DialogBody,
|
|
306
|
+
DialogClose,
|
|
307
|
+
DialogContent,
|
|
308
|
+
DialogDescription,
|
|
309
|
+
DialogFooter,
|
|
310
|
+
DialogHeader,
|
|
311
|
+
DialogOverlay,
|
|
312
|
+
DialogPortal,
|
|
313
|
+
DialogTitle,
|
|
314
|
+
DialogTrigger,
|
|
315
|
+
}
|