@nuasite/cms 0.1.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/README.md +237 -0
- package/dist/src/build-processor.d.ts +20 -0
- package/dist/src/build-processor.d.ts.map +1 -0
- package/dist/src/collection-scanner.d.ts +6 -0
- package/dist/src/collection-scanner.d.ts.map +1 -0
- package/dist/src/component-registry.d.ts +63 -0
- package/dist/src/component-registry.d.ts.map +1 -0
- package/dist/src/config.d.ts +24 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/dev-middleware.d.ts +20 -0
- package/dist/src/dev-middleware.d.ts.map +1 -0
- package/dist/src/editor/ai.d.ts +60 -0
- package/dist/src/editor/ai.d.ts.map +1 -0
- package/dist/src/editor/api.d.ts +140 -0
- package/dist/src/editor/api.d.ts.map +1 -0
- package/dist/src/editor/color-utils.d.ts +106 -0
- package/dist/src/editor/color-utils.d.ts.map +1 -0
- package/dist/src/editor/components/ai-chat.d.ts +11 -0
- package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
- package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
- package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
- package/dist/src/editor/components/attribute-editor.d.ts +5 -0
- package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
- package/dist/src/editor/components/block-editor.d.ts +12 -0
- package/dist/src/editor/components/block-editor.d.ts.map +1 -0
- package/dist/src/editor/components/collections-browser.d.ts +2 -0
- package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
- package/dist/src/editor/components/color-toolbar.d.ts +12 -0
- package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
- package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
- package/dist/src/editor/components/create-page-modal.d.ts +2 -0
- package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
- package/dist/src/editor/components/editable-highlights.d.ts +9 -0
- package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
- package/dist/src/editor/components/error-boundary.d.ts +32 -0
- package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
- package/dist/src/editor/components/fields.d.ts +75 -0
- package/dist/src/editor/components/fields.d.ts.map +1 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
- package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
- package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/image-overlay.d.ts +12 -0
- package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
- package/dist/src/editor/components/media-library.d.ts +2 -0
- package/dist/src/editor/components/media-library.d.ts.map +1 -0
- package/dist/src/editor/components/outline.d.ts +21 -0
- package/dist/src/editor/components/outline.d.ts.map +1 -0
- package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
- package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
- package/dist/src/editor/components/seo-editor.d.ts +2 -0
- package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
- package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast.d.ts +7 -0
- package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
- package/dist/src/editor/components/toast/types.d.ts +7 -0
- package/dist/src/editor/components/toast/types.d.ts.map +1 -0
- package/dist/src/editor/components/toolbar.d.ts +21 -0
- package/dist/src/editor/components/toolbar.d.ts.map +1 -0
- package/dist/src/editor/config.d.ts +4 -0
- package/dist/src/editor/config.d.ts.map +1 -0
- package/dist/src/editor/constants.d.ts +101 -0
- package/dist/src/editor/constants.d.ts.map +1 -0
- package/dist/src/editor/context.d.ts +14 -0
- package/dist/src/editor/context.d.ts.map +1 -0
- package/dist/src/editor/dom.d.ts +77 -0
- package/dist/src/editor/dom.d.ts.map +1 -0
- package/dist/src/editor/editor.d.ts +64 -0
- package/dist/src/editor/editor.d.ts.map +1 -0
- package/dist/src/editor/history.d.ts +20 -0
- package/dist/src/editor/history.d.ts.map +1 -0
- package/dist/src/editor/hooks/index.d.ts +14 -0
- package/dist/src/editor/hooks/index.d.ts.map +1 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
- package/dist/src/editor/hooks/utils.d.ts +32 -0
- package/dist/src/editor/hooks/utils.d.ts.map +1 -0
- package/dist/src/editor/index.d.ts +12 -0
- package/dist/src/editor/index.d.ts.map +1 -0
- package/dist/src/editor/lib/cn.d.ts +3 -0
- package/dist/src/editor/lib/cn.d.ts.map +1 -0
- package/dist/src/editor/manifest.d.ts +19 -0
- package/dist/src/editor/manifest.d.ts.map +1 -0
- package/dist/src/editor/markdown-api.d.ts +36 -0
- package/dist/src/editor/markdown-api.d.ts.map +1 -0
- package/dist/src/editor/signals.d.ts +242 -0
- package/dist/src/editor/signals.d.ts.map +1 -0
- package/dist/src/editor/storage.d.ts +27 -0
- package/dist/src/editor/storage.d.ts.map +1 -0
- package/dist/src/editor/text-styling.d.ts +350 -0
- package/dist/src/editor/text-styling.d.ts.map +1 -0
- package/dist/src/editor/themes.d.ts +38 -0
- package/dist/src/editor/themes.d.ts.map +1 -0
- package/dist/src/editor/types.d.ts +454 -0
- package/dist/src/editor/types.d.ts.map +1 -0
- package/dist/src/error-collector.d.ts +56 -0
- package/dist/src/error-collector.d.ts.map +1 -0
- package/dist/src/handlers/component-ops.d.ts +34 -0
- package/dist/src/handlers/component-ops.d.ts.map +1 -0
- package/dist/src/handlers/markdown-ops.d.ts +41 -0
- package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
- package/dist/src/handlers/request-utils.d.ts +20 -0
- package/dist/src/handlers/request-utils.d.ts.map +1 -0
- package/dist/src/handlers/source-writer.d.ts +51 -0
- package/dist/src/handlers/source-writer.d.ts.map +1 -0
- package/dist/src/html-processor.d.ts +63 -0
- package/dist/src/html-processor.d.ts.map +1 -0
- package/dist/src/index.d.ts +41 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/manifest-writer.d.ts +111 -0
- package/dist/src/manifest-writer.d.ts.map +1 -0
- package/dist/src/media/contember.d.ts +15 -0
- package/dist/src/media/contember.d.ts.map +1 -0
- package/dist/src/media/local.d.ts +9 -0
- package/dist/src/media/local.d.ts.map +1 -0
- package/dist/src/media/s3.d.ts +12 -0
- package/dist/src/media/s3.d.ts.map +1 -0
- package/dist/src/media/types.d.ts +40 -0
- package/dist/src/media/types.d.ts.map +1 -0
- package/dist/src/preview-generator.d.ts +19 -0
- package/dist/src/preview-generator.d.ts.map +1 -0
- package/dist/src/seo-processor.d.ts +23 -0
- package/dist/src/seo-processor.d.ts.map +1 -0
- package/dist/src/source-finder/ast-extractors.d.ts +35 -0
- package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
- package/dist/src/source-finder/ast-parser.d.ts +16 -0
- package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
- package/dist/src/source-finder/cache.d.ts +18 -0
- package/dist/src/source-finder/cache.d.ts.map +1 -0
- package/dist/src/source-finder/collection-finder.d.ts +29 -0
- package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
- package/dist/src/source-finder/element-finder.d.ts +42 -0
- package/dist/src/source-finder/element-finder.d.ts.map +1 -0
- package/dist/src/source-finder/image-finder.d.ts +24 -0
- package/dist/src/source-finder/image-finder.d.ts.map +1 -0
- package/dist/src/source-finder/index.d.ts +9 -0
- package/dist/src/source-finder/index.d.ts.map +1 -0
- package/dist/src/source-finder/search-index.d.ts +27 -0
- package/dist/src/source-finder/search-index.d.ts.map +1 -0
- package/dist/src/source-finder/snippet-utils.d.ts +90 -0
- package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
- package/dist/src/source-finder/source-lookup.d.ts +16 -0
- package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
- package/dist/src/source-finder/types.d.ts +167 -0
- package/dist/src/source-finder/types.d.ts.map +1 -0
- package/dist/src/source-finder/variable-extraction.d.ts +37 -0
- package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
- package/dist/src/tailwind-colors.d.ts +54 -0
- package/dist/src/tailwind-colors.d.ts.map +1 -0
- package/dist/src/tsconfig.tsbuildinfo +1 -0
- package/dist/src/types.d.ts +367 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +61 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/vite-plugin.d.ts +14 -0
- package/dist/src/vite-plugin.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +80 -0
- package/src/build-processor.ts +784 -0
- package/src/collection-scanner.ts +304 -0
- package/src/component-registry.ts +393 -0
- package/src/config.ts +74 -0
- package/src/dev-middleware.ts +525 -0
- package/src/dist/src/tsconfig.tsbuildinfo +1 -0
- package/src/editor/ai.ts +185 -0
- package/src/editor/api.ts +513 -0
- package/src/editor/color-utils.ts +556 -0
- package/src/editor/components/ai-chat.tsx +632 -0
- package/src/editor/components/ai-tooltip.tsx +179 -0
- package/src/editor/components/attribute-editor.tsx +596 -0
- package/src/editor/components/block-editor.tsx +546 -0
- package/src/editor/components/collections-browser.tsx +248 -0
- package/src/editor/components/color-toolbar.tsx +314 -0
- package/src/editor/components/confirm-dialog.tsx +69 -0
- package/src/editor/components/create-page-modal.tsx +163 -0
- package/src/editor/components/editable-highlights.tsx +260 -0
- package/src/editor/components/error-boundary.tsx +87 -0
- package/src/editor/components/fields.tsx +387 -0
- package/src/editor/components/frontmatter-fields.tsx +469 -0
- package/src/editor/components/highlight-overlay.ts +229 -0
- package/src/editor/components/image-overlay.tsx +230 -0
- package/src/editor/components/markdown-editor-overlay.tsx +505 -0
- package/src/editor/components/markdown-inline-editor.tsx +780 -0
- package/src/editor/components/media-library.tsx +297 -0
- package/src/editor/components/outline.tsx +402 -0
- package/src/editor/components/redirect-countdown.tsx +45 -0
- package/src/editor/components/seo-editor.tsx +498 -0
- package/src/editor/components/text-style-toolbar.tsx +362 -0
- package/src/editor/components/toast/toast-container.tsx +15 -0
- package/src/editor/components/toast/toast.tsx +49 -0
- package/src/editor/components/toast/types.ts +7 -0
- package/src/editor/components/toolbar.tsx +366 -0
- package/src/editor/config.ts +12 -0
- package/src/editor/constants.ts +106 -0
- package/src/editor/context.tsx +38 -0
- package/src/editor/dom.ts +357 -0
- package/src/editor/editor.ts +1510 -0
- package/src/editor/env.d.ts +4 -0
- package/src/editor/history.ts +355 -0
- package/src/editor/hooks/index.ts +19 -0
- package/src/editor/hooks/useAIHandlers.ts +345 -0
- package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
- package/src/editor/hooks/useElementDetection.ts +284 -0
- package/src/editor/hooks/useImageHoverDetection.ts +102 -0
- package/src/editor/hooks/useTextSelection.ts +187 -0
- package/src/editor/hooks/useTooltipState.ts +126 -0
- package/src/editor/hooks/utils.ts +101 -0
- package/src/editor/index.tsx +481 -0
- package/src/editor/lib/cn.ts +4 -0
- package/src/editor/manifest.ts +25 -0
- package/src/editor/markdown-api.ts +209 -0
- package/src/editor/signals.ts +1351 -0
- package/src/editor/storage.ts +266 -0
- package/src/editor/styles.css +465 -0
- package/src/editor/text-styling.ts +773 -0
- package/src/editor/themes.ts +210 -0
- package/src/editor/types.ts +591 -0
- package/src/error-collector.ts +106 -0
- package/src/handlers/component-ops.ts +463 -0
- package/src/handlers/markdown-ops.ts +202 -0
- package/src/handlers/request-utils.ts +151 -0
- package/src/handlers/source-writer.ts +649 -0
- package/src/html-processor.ts +1108 -0
- package/src/index.ts +284 -0
- package/src/manifest-writer.ts +371 -0
- package/src/media/contember.ts +84 -0
- package/src/media/local.ts +114 -0
- package/src/media/s3.ts +133 -0
- package/src/media/types.ts +33 -0
- package/src/preview-generator.ts +293 -0
- package/src/seo-processor.ts +567 -0
- package/src/source-finder/ast-extractors.ts +185 -0
- package/src/source-finder/ast-parser.ts +150 -0
- package/src/source-finder/cache.ts +76 -0
- package/src/source-finder/collection-finder.ts +335 -0
- package/src/source-finder/cross-file-tracker.ts +741 -0
- package/src/source-finder/element-finder.ts +387 -0
- package/src/source-finder/image-finder.ts +283 -0
- package/src/source-finder/index.ts +37 -0
- package/src/source-finder/search-index.ts +525 -0
- package/src/source-finder/snippet-utils.ts +668 -0
- package/src/source-finder/source-lookup.ts +200 -0
- package/src/source-finder/types.ts +210 -0
- package/src/source-finder/variable-extraction.ts +406 -0
- package/src/tailwind-colors.ts +874 -0
- package/src/tsconfig.json +25 -0
- package/src/types.ts +406 -0
- package/src/utils.ts +186 -0
- package/src/vite-plugin.ts +42 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { useMemo } from 'preact/hooks'
|
|
2
|
+
import {
|
|
3
|
+
closeCollectionsBrowser,
|
|
4
|
+
isCollectionsBrowserOpen,
|
|
5
|
+
manifest,
|
|
6
|
+
openMarkdownEditorForNewPage,
|
|
7
|
+
selectBrowserCollection,
|
|
8
|
+
selectedBrowserCollection,
|
|
9
|
+
} from '../signals'
|
|
10
|
+
import { savePendingEntryNavigation } from '../storage'
|
|
11
|
+
|
|
12
|
+
export function CollectionsBrowser() {
|
|
13
|
+
const visible = isCollectionsBrowserOpen.value
|
|
14
|
+
const selected = selectedBrowserCollection.value
|
|
15
|
+
|
|
16
|
+
const collectionDefinitions = manifest.value.collectionDefinitions ?? {}
|
|
17
|
+
|
|
18
|
+
const collections = useMemo(() => {
|
|
19
|
+
return Object.values(collectionDefinitions)
|
|
20
|
+
}, [collectionDefinitions])
|
|
21
|
+
|
|
22
|
+
if (!visible) return null
|
|
23
|
+
|
|
24
|
+
const handleClose = () => {
|
|
25
|
+
closeCollectionsBrowser()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const handleBackdropClick = (e: Event) => {
|
|
29
|
+
handleClose()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// View 2: Entry list for selected collection
|
|
33
|
+
if (selected) {
|
|
34
|
+
const def = collectionDefinitions[selected]
|
|
35
|
+
if (!def) return null
|
|
36
|
+
|
|
37
|
+
const entries = def.entries ?? []
|
|
38
|
+
|
|
39
|
+
const handleEntryClick = (slug: string, sourcePath: string, pathname?: string) => {
|
|
40
|
+
closeCollectionsBrowser()
|
|
41
|
+
// Navigate to the collection detail page to edit inline.
|
|
42
|
+
// Use known pathname or construct one from collection/slug.
|
|
43
|
+
const targetPath = pathname || `/${selected}/${slug}`
|
|
44
|
+
savePendingEntryNavigation({ collectionName: selected, slug, sourcePath, pathname: targetPath })
|
|
45
|
+
window.location.href = targetPath
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleAddNew = () => {
|
|
49
|
+
closeCollectionsBrowser()
|
|
50
|
+
openMarkdownEditorForNewPage(selected, def)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
class="fixed inset-0 z-2147483647 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
|
56
|
+
onClick={handleBackdropClick}
|
|
57
|
+
data-cms-ui
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
class="bg-cms-dark rounded-cms-xl shadow-[0_8px_32px_rgba(0,0,0,0.4)] max-w-md w-full border border-white/10 flex flex-col max-h-[80vh]"
|
|
61
|
+
onClick={(e) => e.stopPropagation()}
|
|
62
|
+
data-cms-ui
|
|
63
|
+
>
|
|
64
|
+
{/* Header */}
|
|
65
|
+
<div class="flex items-center justify-between p-5 border-b border-white/10 shrink-0">
|
|
66
|
+
<div class="flex items-center gap-3">
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={() => selectBrowserCollection(null)}
|
|
70
|
+
class="text-white/50 hover:text-white p-1 hover:bg-white/10 rounded-full transition-colors"
|
|
71
|
+
data-cms-ui
|
|
72
|
+
>
|
|
73
|
+
<BackArrowIcon />
|
|
74
|
+
</button>
|
|
75
|
+
<h2 class="text-lg font-semibold text-white">{def.label}</h2>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="flex items-center gap-2">
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
onClick={handleAddNew}
|
|
81
|
+
class="px-3 py-1.5 text-sm font-medium text-black bg-cms-primary hover:bg-cms-primary/80 rounded-cms-pill transition-colors"
|
|
82
|
+
data-cms-ui
|
|
83
|
+
>
|
|
84
|
+
+ Add New
|
|
85
|
+
</button>
|
|
86
|
+
<CloseButton onClick={handleClose} />
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{/* Entry list */}
|
|
91
|
+
<div class="p-5 space-y-1 overflow-y-auto flex-1 min-h-0">
|
|
92
|
+
{entries.length === 0 && (
|
|
93
|
+
<div class="text-white/50 text-sm text-center py-8">
|
|
94
|
+
No entries yet. Click "Add New" to create one.
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
{entries.map((entry) => (
|
|
98
|
+
<button
|
|
99
|
+
key={entry.slug}
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={() => handleEntryClick(entry.slug, entry.sourcePath, entry.pathname)}
|
|
102
|
+
class="w-full flex items-center gap-3 px-4 py-3 hover:bg-white/10 rounded-cms-lg transition-colors text-left group"
|
|
103
|
+
data-cms-ui
|
|
104
|
+
>
|
|
105
|
+
<div class="flex-1 min-w-0">
|
|
106
|
+
<div class={`font-medium truncate ${entry.draft ? 'text-white/40' : 'text-white'}`}>
|
|
107
|
+
{entry.title || entry.slug}
|
|
108
|
+
</div>
|
|
109
|
+
{entry.title && <div class="text-white/30 text-xs truncate">{entry.slug}</div>}
|
|
110
|
+
</div>
|
|
111
|
+
{entry.draft && (
|
|
112
|
+
<span class="shrink-0 px-2 py-0.5 text-xs font-medium text-amber-400/80 bg-amber-400/10 rounded-full border border-amber-400/20">
|
|
113
|
+
Draft
|
|
114
|
+
</span>
|
|
115
|
+
)}
|
|
116
|
+
<svg
|
|
117
|
+
class="w-4 h-4 text-white/20 group-hover:text-white/40 shrink-0 transition-colors"
|
|
118
|
+
fill="none"
|
|
119
|
+
stroke="currentColor"
|
|
120
|
+
viewBox="0 0 24 24"
|
|
121
|
+
>
|
|
122
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
123
|
+
</svg>
|
|
124
|
+
</button>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// View 1: Collection list
|
|
133
|
+
if (collections.length === 0) {
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
class="fixed inset-0 z-2147483647 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
|
137
|
+
onClick={handleBackdropClick}
|
|
138
|
+
data-cms-ui
|
|
139
|
+
>
|
|
140
|
+
<div
|
|
141
|
+
class="bg-cms-dark rounded-cms-xl shadow-[0_8px_32px_rgba(0,0,0,0.4)] max-w-md w-full border border-white/10"
|
|
142
|
+
onClick={(e) => e.stopPropagation()}
|
|
143
|
+
data-cms-ui
|
|
144
|
+
>
|
|
145
|
+
<div class="flex items-center justify-between p-5 border-b border-white/10">
|
|
146
|
+
<h2 class="text-lg font-semibold text-white">Collections</h2>
|
|
147
|
+
<CloseButton onClick={handleClose} />
|
|
148
|
+
</div>
|
|
149
|
+
<div class="p-8 text-center">
|
|
150
|
+
<div class="text-white/60 mb-4">No content collections found.</div>
|
|
151
|
+
<p class="text-white/40 text-sm">
|
|
152
|
+
Add markdown files to <code class="bg-white/10 px-1.5 py-0.5 rounded">src/content/</code> subdirectories to enable collections.
|
|
153
|
+
</p>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div
|
|
162
|
+
class="fixed inset-0 z-2147483647 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
|
163
|
+
onClick={handleBackdropClick}
|
|
164
|
+
data-cms-ui
|
|
165
|
+
>
|
|
166
|
+
<div
|
|
167
|
+
class="bg-cms-dark rounded-cms-xl shadow-[0_8px_32px_rgba(0,0,0,0.4)] max-w-md w-full border border-white/10 flex flex-col max-h-[80vh]"
|
|
168
|
+
onClick={(e) => e.stopPropagation()}
|
|
169
|
+
data-cms-ui
|
|
170
|
+
>
|
|
171
|
+
<div class="flex items-center justify-between p-5 border-b border-white/10 shrink-0">
|
|
172
|
+
<h2 class="text-lg font-semibold text-white">Collections</h2>
|
|
173
|
+
<CloseButton onClick={handleClose} />
|
|
174
|
+
</div>
|
|
175
|
+
<div class="p-5 space-y-2 overflow-y-auto flex-1 min-h-0">
|
|
176
|
+
{collections.map((col) => (
|
|
177
|
+
<button
|
|
178
|
+
key={col.name}
|
|
179
|
+
type="button"
|
|
180
|
+
onClick={() => selectBrowserCollection(col.name)}
|
|
181
|
+
class="w-full flex items-center gap-4 p-4 bg-white/5 hover:bg-white/10 rounded-cms-lg border border-white/10 hover:border-white/20 transition-colors text-left"
|
|
182
|
+
data-cms-ui
|
|
183
|
+
>
|
|
184
|
+
<div class="shrink-0 w-10 h-10 bg-cms-primary/20 rounded-cms-md flex items-center justify-center">
|
|
185
|
+
<CollectionIcon />
|
|
186
|
+
</div>
|
|
187
|
+
<div class="flex-1 min-w-0">
|
|
188
|
+
<div class="text-white font-medium">{col.label}</div>
|
|
189
|
+
<div class="text-white/50 text-sm">
|
|
190
|
+
{col.entryCount} {col.entryCount === 1 ? 'entry' : 'entries'}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
<ChevronRightIcon />
|
|
194
|
+
</button>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// Icons
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
function CloseButton({ onClick }: { onClick: () => void }) {
|
|
207
|
+
return (
|
|
208
|
+
<button
|
|
209
|
+
type="button"
|
|
210
|
+
onClick={onClick}
|
|
211
|
+
class="text-white/50 hover:text-white p-1.5 hover:bg-white/10 rounded-full transition-colors"
|
|
212
|
+
data-cms-ui
|
|
213
|
+
>
|
|
214
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
215
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
216
|
+
</svg>
|
|
217
|
+
</button>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function CollectionIcon() {
|
|
222
|
+
return (
|
|
223
|
+
<svg class="w-5 h-5 text-cms-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
224
|
+
<path
|
|
225
|
+
stroke-linecap="round"
|
|
226
|
+
stroke-linejoin="round"
|
|
227
|
+
stroke-width="2"
|
|
228
|
+
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
|
229
|
+
/>
|
|
230
|
+
</svg>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function ChevronRightIcon() {
|
|
235
|
+
return (
|
|
236
|
+
<svg class="w-5 h-5 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
237
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
238
|
+
</svg>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function BackArrowIcon() {
|
|
243
|
+
return (
|
|
244
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
245
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
246
|
+
</svg>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'preact/hooks'
|
|
2
|
+
import {
|
|
3
|
+
applyColorChange,
|
|
4
|
+
COLOR_PREVIEW_MAP,
|
|
5
|
+
DEFAULT_TAILWIND_COLORS,
|
|
6
|
+
getColorPreview,
|
|
7
|
+
parseColorClass,
|
|
8
|
+
SPECIAL_COLORS,
|
|
9
|
+
STANDARD_SHADES,
|
|
10
|
+
} from '../color-utils'
|
|
11
|
+
import { CSS, Z_INDEX } from '../constants'
|
|
12
|
+
import { cn } from '../lib/cn'
|
|
13
|
+
import * as signals from '../signals'
|
|
14
|
+
import type { Attribute, AvailableColors } from '../types'
|
|
15
|
+
|
|
16
|
+
export interface ColorToolbarProps {
|
|
17
|
+
visible: boolean
|
|
18
|
+
rect: DOMRect | null
|
|
19
|
+
element: HTMLElement | null
|
|
20
|
+
availableColors: AvailableColors | undefined
|
|
21
|
+
currentClasses: Record<string, Attribute> | undefined
|
|
22
|
+
onColorChange?: (type: 'bg' | 'text' | 'border' | 'hoverBg' | 'hoverText', oldClass: string, newClass: string, previousClassName: string, previousStyleCssText: string) => void
|
|
23
|
+
onClose?: () => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ColorSwatchProps {
|
|
27
|
+
colorName: string
|
|
28
|
+
shade?: string
|
|
29
|
+
isSelected: boolean
|
|
30
|
+
onClick: () => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ColorSwatch({ colorName, shade, isSelected, onClick }: ColorSwatchProps) {
|
|
34
|
+
const preview = getColorPreview(colorName, shade)
|
|
35
|
+
const isWhite = colorName === 'white' || (preview === '#ffffff')
|
|
36
|
+
const isTransparent = colorName === 'transparent'
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={onClick}
|
|
42
|
+
title={shade ? `${colorName}-${shade}` : colorName}
|
|
43
|
+
class={cn(
|
|
44
|
+
'w-7 h-7 rounded-full border-2 transition-all cursor-pointer hover:scale-110',
|
|
45
|
+
isSelected ? 'border-cms-primary ring-2 ring-cms-primary/30 scale-110' : 'border-transparent',
|
|
46
|
+
isWhite && !isSelected && 'border-white/30',
|
|
47
|
+
)}
|
|
48
|
+
style={{
|
|
49
|
+
backgroundColor: isTransparent ? 'transparent' : preview,
|
|
50
|
+
backgroundImage: isTransparent
|
|
51
|
+
? 'linear-gradient(45deg, #555 25%, transparent 25%, transparent 75%, #555 75%, #555), linear-gradient(45deg, #555 25%, transparent 25%, transparent 75%, #555 75%, #555)'
|
|
52
|
+
: undefined,
|
|
53
|
+
backgroundSize: isTransparent ? '8px 8px' : undefined,
|
|
54
|
+
backgroundPosition: isTransparent ? '0 0, 4px 4px' : undefined,
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface ColorSectionProps {
|
|
61
|
+
title: string
|
|
62
|
+
type: 'bg' | 'text' | 'border' | 'hoverBg' | 'hoverText'
|
|
63
|
+
currentClass: string | undefined
|
|
64
|
+
availableColors: AvailableColors | undefined
|
|
65
|
+
onSelect: (colorName: string, shade?: string) => void
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function ColorSection({ title, type, currentClass, availableColors, onSelect }: ColorSectionProps) {
|
|
69
|
+
const [showAllShades, setShowAllShades] = useState(false)
|
|
70
|
+
const [selectedColor, setSelectedColor] = useState<string | undefined>()
|
|
71
|
+
|
|
72
|
+
// Parse current class to get selected color
|
|
73
|
+
const parsedCurrent = currentClass ? parseColorClass(currentClass) : undefined
|
|
74
|
+
|
|
75
|
+
// Get popular colors for quick selection
|
|
76
|
+
const popularColors = [
|
|
77
|
+
{ name: 'transparent', shade: undefined },
|
|
78
|
+
{ name: 'white', shade: undefined },
|
|
79
|
+
{ name: 'black', shade: undefined },
|
|
80
|
+
{ name: 'slate', shade: '500' },
|
|
81
|
+
{ name: 'gray', shade: '500' },
|
|
82
|
+
{ name: 'red', shade: '500' },
|
|
83
|
+
{ name: 'orange', shade: '500' },
|
|
84
|
+
{ name: 'amber', shade: '500' },
|
|
85
|
+
{ name: 'yellow', shade: '500' },
|
|
86
|
+
{ name: 'green', shade: '500' },
|
|
87
|
+
{ name: 'blue', shade: '500' },
|
|
88
|
+
{ name: 'indigo', shade: '500' },
|
|
89
|
+
{ name: 'purple', shade: '500' },
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
// Check if a color is selected
|
|
93
|
+
const isColorSelected = (colorName: string, shade?: string) => {
|
|
94
|
+
if (!parsedCurrent) return false
|
|
95
|
+
if (parsedCurrent.colorName !== colorName) return false
|
|
96
|
+
if (shade && parsedCurrent.shade !== shade) return false
|
|
97
|
+
if (!shade && parsedCurrent.shade) return false
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle color selection
|
|
102
|
+
const handleSelect = (colorName: string, shade?: string) => {
|
|
103
|
+
onSelect(colorName, shade)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Get all colors with shades
|
|
107
|
+
const allColors = availableColors?.colors
|
|
108
|
+
? availableColors.colors.map(color => ({
|
|
109
|
+
name: color.name,
|
|
110
|
+
shades: Object.keys(color.values).filter(s => s !== '').sort((a, b) => Number(a) - Number(b)), // Extract and sort shade keys
|
|
111
|
+
isCustom: color.isCustom ?? false,
|
|
112
|
+
}))
|
|
113
|
+
: [
|
|
114
|
+
...SPECIAL_COLORS.filter(c => c === 'white' || c === 'black').map(name => ({
|
|
115
|
+
name,
|
|
116
|
+
shades: [] as string[],
|
|
117
|
+
isCustom: false,
|
|
118
|
+
})),
|
|
119
|
+
...DEFAULT_TAILWIND_COLORS.map(name => ({
|
|
120
|
+
name,
|
|
121
|
+
shades: [...STANDARD_SHADES],
|
|
122
|
+
isCustom: false,
|
|
123
|
+
})),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div class="flex flex-col gap-3">
|
|
128
|
+
<div class="text-xs font-medium text-white/50 uppercase tracking-wide">{title}</div>
|
|
129
|
+
|
|
130
|
+
{/* Popular colors grid */}
|
|
131
|
+
<div class="flex flex-wrap gap-2">
|
|
132
|
+
{popularColors.map(({ name, shade }) => (
|
|
133
|
+
<ColorSwatch
|
|
134
|
+
key={`${name}-${shade || 'base'}`}
|
|
135
|
+
colorName={name}
|
|
136
|
+
shade={shade}
|
|
137
|
+
isSelected={isColorSelected(name, shade)}
|
|
138
|
+
onClick={() => handleSelect(name, shade)}
|
|
139
|
+
/>
|
|
140
|
+
))}
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
{/* Show more button */}
|
|
144
|
+
<button
|
|
145
|
+
type="button"
|
|
146
|
+
onClick={() => setShowAllShades(!showAllShades)}
|
|
147
|
+
class="text-xs text-white/50 hover:text-white cursor-pointer text-left font-medium"
|
|
148
|
+
>
|
|
149
|
+
{showAllShades ? 'Show less' : 'More colors...'}
|
|
150
|
+
</button>
|
|
151
|
+
|
|
152
|
+
{/* All colors with shades */}
|
|
153
|
+
{showAllShades && (
|
|
154
|
+
<div class="flex flex-col gap-3 max-h-48 overflow-y-auto pr-1">
|
|
155
|
+
{allColors.filter(c => c.shades.length > 0).map(color => (
|
|
156
|
+
<div key={color.name} class="flex flex-col gap-1.5">
|
|
157
|
+
<div class="text-xs text-white/40 capitalize">{color.name}</div>
|
|
158
|
+
<div class="flex flex-wrap gap-1.5">
|
|
159
|
+
{color.shades.map(shade => (
|
|
160
|
+
<ColorSwatch
|
|
161
|
+
key={`${color.name}-${shade}`}
|
|
162
|
+
colorName={color.name}
|
|
163
|
+
shade={shade}
|
|
164
|
+
isSelected={isColorSelected(color.name, shade)}
|
|
165
|
+
onClick={() => handleSelect(color.name, shade)}
|
|
166
|
+
/>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function ColorToolbar({
|
|
178
|
+
visible,
|
|
179
|
+
rect,
|
|
180
|
+
element,
|
|
181
|
+
availableColors,
|
|
182
|
+
currentClasses,
|
|
183
|
+
onColorChange,
|
|
184
|
+
onClose,
|
|
185
|
+
}: ColorToolbarProps) {
|
|
186
|
+
// Handle color selection
|
|
187
|
+
const handleColorSelect = useCallback(
|
|
188
|
+
(type: 'bg' | 'text' | 'border' | 'hoverBg' | 'hoverText', colorName: string, shade?: string) => {
|
|
189
|
+
if (!element) return
|
|
190
|
+
|
|
191
|
+
// Capture className and inline styles before DOM mutation for undo support
|
|
192
|
+
const previousClassName = element.className
|
|
193
|
+
const previousStyleCssText = element.style.cssText
|
|
194
|
+
|
|
195
|
+
const result = applyColorChange(element, type, colorName, shade, availableColors)
|
|
196
|
+
if (result && onColorChange) {
|
|
197
|
+
onColorChange(type, result.oldClass, result.newClass, previousClassName, previousStyleCssText)
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
[element, onColorChange, availableColors],
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
// Close on click outside
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!visible) return
|
|
206
|
+
|
|
207
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
208
|
+
const target = e.target as HTMLElement
|
|
209
|
+
// Don't close if clicking on CMS UI elements
|
|
210
|
+
if (target.closest('[data-cms-ui]')) return
|
|
211
|
+
onClose?.()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Delay adding listener to avoid immediate close
|
|
215
|
+
const timeout = setTimeout(() => {
|
|
216
|
+
document.addEventListener('click', handleClickOutside)
|
|
217
|
+
}, 100)
|
|
218
|
+
|
|
219
|
+
return () => {
|
|
220
|
+
clearTimeout(timeout)
|
|
221
|
+
document.removeEventListener('click', handleClickOutside)
|
|
222
|
+
}
|
|
223
|
+
}, [visible, onClose])
|
|
224
|
+
|
|
225
|
+
if (!visible || !rect) {
|
|
226
|
+
return null
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<div
|
|
231
|
+
data-cms-ui
|
|
232
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
233
|
+
onClick={(e) => e.stopPropagation()}
|
|
234
|
+
class="right-8 top-8 bottom-8 fixed text-xs w-2xs"
|
|
235
|
+
style={{
|
|
236
|
+
zIndex: Z_INDEX.MODAL,
|
|
237
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
<div class="bg-cms-dark border border-white/10 shadow-[0_8px_32px_rgba(0,0,0,0.4)] rounded-cms-lg p-4 flex flex-col gap-4 h-full overflow-y-auto">
|
|
241
|
+
{/* Header */}
|
|
242
|
+
<div class="flex items-center justify-between">
|
|
243
|
+
<span class="font-medium text-white">Element Colors</span>
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
onClick={onClose}
|
|
247
|
+
class="text-white/50 hover:text-white cursor-pointer p-1.5 hover:bg-white/10 rounded-full transition-colors"
|
|
248
|
+
>
|
|
249
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
250
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
251
|
+
</svg>
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{/* Background color section */}
|
|
256
|
+
<ColorSection
|
|
257
|
+
title="Background"
|
|
258
|
+
type="bg"
|
|
259
|
+
currentClass={currentClasses?.bg?.value}
|
|
260
|
+
availableColors={availableColors}
|
|
261
|
+
onSelect={(colorName, shade) => handleColorSelect('bg', colorName, shade)}
|
|
262
|
+
/>
|
|
263
|
+
|
|
264
|
+
{/* Divider */}
|
|
265
|
+
<div class="h-px bg-white/10" />
|
|
266
|
+
|
|
267
|
+
{/* Text color section */}
|
|
268
|
+
<ColorSection
|
|
269
|
+
title="Text"
|
|
270
|
+
type="text"
|
|
271
|
+
currentClass={currentClasses?.text?.value}
|
|
272
|
+
availableColors={availableColors}
|
|
273
|
+
onSelect={(colorName, shade) => handleColorSelect('text', colorName, shade)}
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
{/* Divider */}
|
|
277
|
+
<div class="h-px bg-white/10" />
|
|
278
|
+
|
|
279
|
+
{/* Border color section */}
|
|
280
|
+
<ColorSection
|
|
281
|
+
title="Border"
|
|
282
|
+
type="border"
|
|
283
|
+
currentClass={currentClasses?.border?.value}
|
|
284
|
+
availableColors={availableColors}
|
|
285
|
+
onSelect={(colorName, shade) => handleColorSelect('border', colorName, shade)}
|
|
286
|
+
/>
|
|
287
|
+
|
|
288
|
+
{/* Divider */}
|
|
289
|
+
<div class="h-px bg-white/10" />
|
|
290
|
+
|
|
291
|
+
{/* Hover Background color section */}
|
|
292
|
+
<ColorSection
|
|
293
|
+
title="Hover Background"
|
|
294
|
+
type="hoverBg"
|
|
295
|
+
currentClass={currentClasses?.hoverBg?.value}
|
|
296
|
+
availableColors={availableColors}
|
|
297
|
+
onSelect={(colorName, shade) => handleColorSelect('hoverBg', colorName, shade)}
|
|
298
|
+
/>
|
|
299
|
+
|
|
300
|
+
{/* Divider */}
|
|
301
|
+
<div class="h-px bg-white/10" />
|
|
302
|
+
|
|
303
|
+
{/* Hover Text color section */}
|
|
304
|
+
<ColorSection
|
|
305
|
+
title="Hover Text"
|
|
306
|
+
type="hoverText"
|
|
307
|
+
currentClass={currentClasses?.hoverText?.value}
|
|
308
|
+
availableColors={availableColors}
|
|
309
|
+
onSelect={(colorName, shade) => handleColorSelect('hoverText', colorName, shade)}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { cn } from '../lib/cn'
|
|
2
|
+
import { confirmDialogState } from '../signals'
|
|
3
|
+
|
|
4
|
+
export function ConfirmDialog() {
|
|
5
|
+
const state = confirmDialogState.value
|
|
6
|
+
|
|
7
|
+
if (!state.isOpen) return null
|
|
8
|
+
|
|
9
|
+
const handleConfirm = () => {
|
|
10
|
+
state.onConfirm?.()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const handleCancel = () => {
|
|
14
|
+
state.onCancel?.()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const handleBackdropClick = () => {
|
|
18
|
+
handleCancel()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
class="fixed inset-0 z-[2147483647] flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
|
24
|
+
onClick={handleBackdropClick}
|
|
25
|
+
data-cms-ui
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
class="bg-cms-dark rounded-cms-xl shadow-[0_8px_32px_rgba(0,0,0,0.4)] max-w-sm w-full border border-white/10 mx-4"
|
|
29
|
+
onClick={(e) => e.stopPropagation()}
|
|
30
|
+
data-cms-ui
|
|
31
|
+
>
|
|
32
|
+
{/* Header */}
|
|
33
|
+
<div class="p-5 pb-3">
|
|
34
|
+
<h2 class="text-lg font-semibold text-white">{state.title}</h2>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
{/* Body */}
|
|
38
|
+
<div class="px-5 pb-5">
|
|
39
|
+
<p class="text-sm text-white/70 leading-relaxed">{state.message}</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{/* Footer */}
|
|
43
|
+
<div class="flex items-center justify-end gap-3 p-5 pt-4 border-t border-white/10 bg-white/5 rounded-b-cms-xl">
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
onClick={handleCancel}
|
|
47
|
+
class="px-4 py-2.5 text-sm text-white/80 font-medium rounded-cms-pill hover:bg-white/10 hover:text-white transition-colors cursor-pointer"
|
|
48
|
+
data-cms-ui
|
|
49
|
+
>
|
|
50
|
+
{state.cancelLabel}
|
|
51
|
+
</button>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
onClick={handleConfirm}
|
|
55
|
+
class={cn(
|
|
56
|
+
'px-5 py-2.5 rounded-cms-pill text-sm font-medium transition-colors cursor-pointer',
|
|
57
|
+
state.variant === 'danger' && 'bg-cms-error text-white hover:bg-red-600',
|
|
58
|
+
state.variant === 'warning' && 'bg-amber-500 text-white hover:bg-amber-600',
|
|
59
|
+
state.variant === 'info' && 'bg-cms-primary text-cms-primary-text hover:bg-cms-primary-hover',
|
|
60
|
+
)}
|
|
61
|
+
data-cms-ui
|
|
62
|
+
>
|
|
63
|
+
{state.confirmLabel}
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|