@imjp/writenex-astro 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.
Files changed (141) hide show
  1. package/README.md +539 -0
  2. package/dist/chunk-5PM6EQE5.js +151 -0
  3. package/dist/chunk-5PM6EQE5.js.map +1 -0
  4. package/dist/chunk-7XU5X6CW.js +1331 -0
  5. package/dist/chunk-7XU5X6CW.js.map +1 -0
  6. package/dist/chunk-AAOQHQPU.js +574 -0
  7. package/dist/chunk-AAOQHQPU.js.map +1 -0
  8. package/dist/chunk-CF2XXJFF.js +1410 -0
  9. package/dist/chunk-CF2XXJFF.js.map +1 -0
  10. package/dist/chunk-CRPZUUDU.js +52 -0
  11. package/dist/chunk-CRPZUUDU.js.map +1 -0
  12. package/dist/chunk-CYLDJ3HZ.js +310 -0
  13. package/dist/chunk-CYLDJ3HZ.js.map +1 -0
  14. package/dist/chunk-KIKIPIFA.js +1 -0
  15. package/dist/chunk-KIKIPIFA.js.map +1 -0
  16. package/dist/chunk-XNTQTTJU.js +145 -0
  17. package/dist/chunk-XNTQTTJU.js.map +1 -0
  18. package/dist/client/index.css +2 -0
  19. package/dist/client/index.css.map +1 -0
  20. package/dist/client/index.js +375 -0
  21. package/dist/client/index.js.map +1 -0
  22. package/dist/client/styles.css +584 -0
  23. package/dist/client/variables.css +304 -0
  24. package/dist/config/index.d.ts +54 -0
  25. package/dist/config/index.js +38 -0
  26. package/dist/config/index.js.map +1 -0
  27. package/dist/config-BmEdBDo_.d.ts +220 -0
  28. package/dist/content-BWR52vD-.d.ts +64 -0
  29. package/dist/discovery/index.d.ts +310 -0
  30. package/dist/discovery/index.js +38 -0
  31. package/dist/discovery/index.js.map +1 -0
  32. package/dist/errors-C0iYiDTv.d.ts +107 -0
  33. package/dist/filesystem/index.d.ts +1292 -0
  34. package/dist/filesystem/index.js +203 -0
  35. package/dist/filesystem/index.js.map +1 -0
  36. package/dist/image-FP7w5ZIs.d.ts +47 -0
  37. package/dist/index.d.ts +64 -0
  38. package/dist/index.js +151 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/loader-55LWCXHA.js +12 -0
  41. package/dist/loader-55LWCXHA.js.map +1 -0
  42. package/dist/loader-CrdnaAWR.d.ts +327 -0
  43. package/dist/server/index.d.ts +357 -0
  44. package/dist/server/index.js +37 -0
  45. package/dist/server/index.js.map +1 -0
  46. package/package.json +94 -0
  47. package/src/client/App.tsx +900 -0
  48. package/src/client/components/ConfigPanel/ConfigPanel.css +553 -0
  49. package/src/client/components/ConfigPanel/ConfigPanel.tsx +396 -0
  50. package/src/client/components/ConfigPanel/index.ts +6 -0
  51. package/src/client/components/CreateContentModal/CreateContentModal.css +327 -0
  52. package/src/client/components/CreateContentModal/CreateContentModal.tsx +216 -0
  53. package/src/client/components/CreateContentModal/index.ts +7 -0
  54. package/src/client/components/Editor/Editor.css +885 -0
  55. package/src/client/components/Editor/Editor.tsx +484 -0
  56. package/src/client/components/Editor/ImageDialog.css +344 -0
  57. package/src/client/components/Editor/ImageDialog.tsx +367 -0
  58. package/src/client/components/Editor/LinkDialog.css +326 -0
  59. package/src/client/components/Editor/LinkDialog.tsx +332 -0
  60. package/src/client/components/Editor/index.ts +6 -0
  61. package/src/client/components/FrontmatterForm/FrontmatterForm.css +468 -0
  62. package/src/client/components/FrontmatterForm/FrontmatterForm.tsx +914 -0
  63. package/src/client/components/FrontmatterForm/index.ts +7 -0
  64. package/src/client/components/Header/Header.css +300 -0
  65. package/src/client/components/Header/Header.tsx +300 -0
  66. package/src/client/components/Header/index.ts +7 -0
  67. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.css +239 -0
  68. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.tsx +151 -0
  69. package/src/client/components/KeyboardShortcuts/index.ts +6 -0
  70. package/src/client/components/LazyEditor.tsx +75 -0
  71. package/src/client/components/LiveRegion/LiveRegion.css +19 -0
  72. package/src/client/components/LiveRegion/LiveRegion.tsx +60 -0
  73. package/src/client/components/LiveRegion/index.ts +7 -0
  74. package/src/client/components/SearchReplace/SearchReplacePanel.css +300 -0
  75. package/src/client/components/SearchReplace/SearchReplacePanel.tsx +332 -0
  76. package/src/client/components/SearchReplace/index.ts +7 -0
  77. package/src/client/components/SelectCollectionModal/SelectCollectionModal.css +308 -0
  78. package/src/client/components/SelectCollectionModal/SelectCollectionModal.tsx +223 -0
  79. package/src/client/components/SelectCollectionModal/index.ts +7 -0
  80. package/src/client/components/Sidebar/Sidebar.css +570 -0
  81. package/src/client/components/Sidebar/Sidebar.tsx +617 -0
  82. package/src/client/components/Sidebar/index.ts +7 -0
  83. package/src/client/components/SkipLink/SkipLink.css +51 -0
  84. package/src/client/components/SkipLink/SkipLink.tsx +67 -0
  85. package/src/client/components/SkipLink/index.ts +7 -0
  86. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.css +233 -0
  87. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.tsx +160 -0
  88. package/src/client/components/UnsavedChangesModal/index.ts +1 -0
  89. package/src/client/components/VersionHistory/DiffViewer.css +430 -0
  90. package/src/client/components/VersionHistory/DiffViewer.tsx +383 -0
  91. package/src/client/components/VersionHistory/VersionActions.css +318 -0
  92. package/src/client/components/VersionHistory/VersionActions.tsx +277 -0
  93. package/src/client/components/VersionHistory/VersionHistoryPanel.css +369 -0
  94. package/src/client/components/VersionHistory/VersionHistoryPanel.tsx +469 -0
  95. package/src/client/components/VersionHistory/index.ts +9 -0
  96. package/src/client/context/ApiContext.tsx +154 -0
  97. package/src/client/context/ThemeContext.tsx +172 -0
  98. package/src/client/hooks/useAnnounce.ts +201 -0
  99. package/src/client/hooks/useApi.ts +374 -0
  100. package/src/client/hooks/useArrowNavigation.ts +286 -0
  101. package/src/client/hooks/useAutosave.ts +241 -0
  102. package/src/client/hooks/useFocusTrap.ts +178 -0
  103. package/src/client/hooks/useKeyboardShortcuts.ts +203 -0
  104. package/src/client/hooks/useSearch.ts +206 -0
  105. package/src/client/hooks/useVersionHistory.ts +451 -0
  106. package/src/client/index.tsx +70 -0
  107. package/src/client/styles.css +584 -0
  108. package/src/client/utils/focus.ts +57 -0
  109. package/src/client/utils/openInEditor.ts +130 -0
  110. package/src/client/variables.css +304 -0
  111. package/src/config/defaults.ts +109 -0
  112. package/src/config/index.ts +32 -0
  113. package/src/config/loader.ts +174 -0
  114. package/src/config/schema.ts +161 -0
  115. package/src/core/constants.ts +39 -0
  116. package/src/core/errors.ts +739 -0
  117. package/src/core/index.ts +11 -0
  118. package/src/discovery/collections.ts +216 -0
  119. package/src/discovery/index.ts +33 -0
  120. package/src/discovery/patterns.ts +702 -0
  121. package/src/discovery/schema.ts +453 -0
  122. package/src/filesystem/images.ts +798 -0
  123. package/src/filesystem/index.ts +107 -0
  124. package/src/filesystem/reader.ts +452 -0
  125. package/src/filesystem/version-config.ts +390 -0
  126. package/src/filesystem/versions.ts +1339 -0
  127. package/src/filesystem/watcher.ts +226 -0
  128. package/src/filesystem/writer.ts +540 -0
  129. package/src/index.ts +61 -0
  130. package/src/integration.ts +228 -0
  131. package/src/server/assets.ts +254 -0
  132. package/src/server/cache.ts +355 -0
  133. package/src/server/index.ts +33 -0
  134. package/src/server/middleware.ts +209 -0
  135. package/src/server/routes.ts +1428 -0
  136. package/src/types/api.ts +61 -0
  137. package/src/types/config.ts +134 -0
  138. package/src/types/content.ts +64 -0
  139. package/src/types/image.ts +48 -0
  140. package/src/types/index.ts +58 -0
  141. package/src/types/version.ts +117 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @fileoverview Skip Link component for keyboard navigation
3
+ *
4
+ * A visually hidden link that becomes visible on focus, allowing keyboard
5
+ * users to skip repetitive navigation and jump directly to main content.
6
+ * This is a critical accessibility feature for WCAG 2.1 AA compliance.
7
+ *
8
+ * @module @writenex/astro/client/components/SkipLink
9
+ * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/bypass-blocks.html}
10
+ */
11
+
12
+ import { useCallback } from "react";
13
+ import "./SkipLink.css";
14
+
15
+ /**
16
+ * Props for the SkipLink component
17
+ */
18
+ export interface SkipLinkProps {
19
+ /** Target element ID to skip to (without the # prefix) */
20
+ targetId: string;
21
+ /** Link text for screen readers */
22
+ children: React.ReactNode;
23
+ }
24
+
25
+ /**
26
+ * Skip link component for keyboard accessibility
27
+ *
28
+ * Renders a visually hidden link that becomes visible when focused.
29
+ * When activated, it moves focus to the target element specified by targetId.
30
+ *
31
+ * @component
32
+ * @example
33
+ * ```tsx
34
+ * <SkipLink targetId="main-content">Skip to main content</SkipLink>
35
+ * ```
36
+ */
37
+ export function SkipLink({
38
+ targetId,
39
+ children,
40
+ }: SkipLinkProps): React.ReactElement {
41
+ /**
42
+ * Handle click to move focus to target element
43
+ */
44
+ const handleClick = useCallback(
45
+ (event: React.MouseEvent<HTMLAnchorElement>) => {
46
+ event.preventDefault();
47
+
48
+ const targetElement = document.getElementById(targetId);
49
+ if (targetElement) {
50
+ // Make the target focusable if it isn't already
51
+ if (!targetElement.hasAttribute("tabindex")) {
52
+ targetElement.setAttribute("tabindex", "-1");
53
+ }
54
+ targetElement.focus();
55
+ // Scroll the element into view
56
+ targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
57
+ }
58
+ },
59
+ [targetId]
60
+ );
61
+
62
+ return (
63
+ <a href={`#${targetId}`} className="wn-skip-link" onClick={handleClick}>
64
+ {children}
65
+ </a>
66
+ );
67
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview SkipLink component exports
3
+ * @module @writenex/astro/client/components/SkipLink
4
+ */
5
+
6
+ export { SkipLink } from "./SkipLink";
7
+ export type { SkipLinkProps } from "./SkipLink";
@@ -0,0 +1,233 @@
1
+ /**
2
+ * @fileoverview UnsavedChangesModal styles
3
+ *
4
+ * Modal styling for unsaved changes confirmation dialog.
5
+ */
6
+
7
+ /* ============================================================================
8
+ MODAL OVERLAY
9
+ ============================================================================ */
10
+
11
+ .wn-unsaved-modal-overlay {
12
+ position: fixed;
13
+ inset: 0;
14
+ z-index: var(--wn-z-modal);
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ background-color: var(--wn-backdrop);
19
+ animation: wn-unsaved-modal-fadeIn var(--wn-transition-fast) ease-out;
20
+ }
21
+
22
+ @keyframes wn-unsaved-modal-fadeIn {
23
+ from {
24
+ opacity: 0;
25
+ }
26
+ to {
27
+ opacity: 1;
28
+ }
29
+ }
30
+
31
+ /* ============================================================================
32
+ MODAL CONTAINER
33
+ ============================================================================ */
34
+
35
+ .wn-unsaved-modal {
36
+ width: 100%;
37
+ max-width: var(--wn-modal-sm);
38
+ overflow: hidden;
39
+ border-radius: var(--wn-radius-lg);
40
+ border: 1px solid var(--wn-zinc-700);
41
+ background-color: var(--wn-zinc-900);
42
+ animation: wn-unsaved-modal-zoomIn var(--wn-transition-fast) ease-out;
43
+ }
44
+
45
+ @keyframes wn-unsaved-modal-zoomIn {
46
+ from {
47
+ opacity: 0;
48
+ transform: scale(0.95) translateY(-10px);
49
+ }
50
+ to {
51
+ opacity: 1;
52
+ transform: scale(1) translateY(0);
53
+ }
54
+ }
55
+
56
+ /* ============================================================================
57
+ MODAL HEADER
58
+ ============================================================================ */
59
+
60
+ .wn-unsaved-modal-header {
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: space-between;
64
+ padding: var(--wn-space-5) var(--wn-space-6);
65
+ border-bottom: 1px solid var(--wn-zinc-700);
66
+ }
67
+
68
+ .wn-unsaved-modal-header-content {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: var(--wn-space-4);
72
+ }
73
+
74
+ .wn-unsaved-modal-icon {
75
+ color: var(--wn-warning-500);
76
+ }
77
+
78
+ .wn-unsaved-modal-title {
79
+ font-size: var(--wn-font-md);
80
+ font-weight: 600;
81
+ color: var(--wn-zinc-50);
82
+ margin: 0;
83
+ }
84
+
85
+ .wn-unsaved-modal-close {
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ width: var(--wn-icon-btn-md);
90
+ height: var(--wn-icon-btn-md);
91
+ padding: 0;
92
+ border: none;
93
+ border-radius: var(--wn-radius-md);
94
+ background-color: transparent;
95
+ color: var(--wn-zinc-400);
96
+ cursor: pointer;
97
+ transition:
98
+ background-color var(--wn-transition-fast),
99
+ color var(--wn-transition-fast);
100
+ }
101
+
102
+ .wn-unsaved-modal-close:hover:not(:disabled) {
103
+ background-color: var(--wn-overlay-10);
104
+ color: var(--wn-zinc-50);
105
+ }
106
+
107
+ .wn-unsaved-modal-close:disabled {
108
+ opacity: 0.5;
109
+ cursor: not-allowed;
110
+ }
111
+
112
+ /* ============================================================================
113
+ MODAL BODY
114
+ ============================================================================ */
115
+
116
+ .wn-unsaved-modal-body {
117
+ padding: var(--wn-space-6);
118
+ }
119
+
120
+ .wn-unsaved-modal-text {
121
+ font-size: var(--wn-font-base);
122
+ color: var(--wn-zinc-400);
123
+ margin: 0;
124
+ line-height: 1.5;
125
+ }
126
+
127
+ /* ============================================================================
128
+ MODAL FOOTER
129
+ ============================================================================ */
130
+
131
+ .wn-unsaved-modal-footer {
132
+ display: flex;
133
+ justify-content: flex-end;
134
+ gap: var(--wn-space-4);
135
+ padding: var(--wn-space-5) var(--wn-space-6);
136
+ border-top: 1px solid var(--wn-zinc-700);
137
+ background-color: var(--wn-overlay-light-10);
138
+ }
139
+
140
+ /* Buttons */
141
+ .wn-unsaved-modal-btn {
142
+ display: inline-flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ gap: var(--wn-space-3);
146
+ padding: var(--wn-space-3) var(--wn-space-5);
147
+ border-radius: var(--wn-radius-md);
148
+ font-size: var(--wn-font-base);
149
+ font-weight: 500;
150
+ cursor: pointer;
151
+ transition:
152
+ background-color var(--wn-transition-fast),
153
+ color var(--wn-transition-fast),
154
+ border-color var(--wn-transition-fast);
155
+ }
156
+
157
+ .wn-unsaved-modal-btn:disabled {
158
+ opacity: 0.5;
159
+ cursor: not-allowed;
160
+ }
161
+
162
+ /* Don't Save button - subtle danger styling */
163
+ .wn-unsaved-modal-btn--secondary {
164
+ background-color: transparent;
165
+ border: 1px solid var(--wn-zinc-700);
166
+ color: var(--wn-error-400);
167
+ }
168
+
169
+ .wn-unsaved-modal-btn--secondary:hover:not(:disabled) {
170
+ background-color: var(--wn-error-alpha-10);
171
+ color: #fca5a5;
172
+ }
173
+
174
+ /* Save button - primary action */
175
+ .wn-unsaved-modal-btn--primary {
176
+ background-color: var(--wn-brand-500);
177
+ border: 1px solid var(--wn-brand-500);
178
+ color: white;
179
+ }
180
+
181
+ .wn-unsaved-modal-btn--primary:hover:not(:disabled) {
182
+ background-color: var(--wn-brand-600);
183
+ border-color: var(--wn-brand-600);
184
+ }
185
+
186
+ /* ============================================================================
187
+ LIGHT MODE OVERRIDES
188
+ ============================================================================ */
189
+
190
+ .wn-light .wn-unsaved-modal-overlay {
191
+ background-color: var(--wn-backdrop-light);
192
+ }
193
+
194
+ .wn-light .wn-unsaved-modal {
195
+ border-color: var(--wn-zinc-200);
196
+ background-color: #fff;
197
+ }
198
+
199
+ .wn-light .wn-unsaved-modal-header {
200
+ border-bottom-color: var(--wn-zinc-200);
201
+ }
202
+
203
+ .wn-light .wn-unsaved-modal-title {
204
+ color: var(--wn-zinc-900);
205
+ }
206
+
207
+ .wn-light .wn-unsaved-modal-close {
208
+ color: var(--wn-zinc-500);
209
+ }
210
+
211
+ .wn-light .wn-unsaved-modal-close:hover:not(:disabled) {
212
+ background-color: var(--wn-overlay-light-5);
213
+ color: var(--wn-zinc-900);
214
+ }
215
+
216
+ .wn-light .wn-unsaved-modal-text {
217
+ color: var(--wn-zinc-600);
218
+ }
219
+
220
+ .wn-light .wn-unsaved-modal-footer {
221
+ border-top-color: var(--wn-zinc-200);
222
+ background-color: var(--wn-zinc-50);
223
+ }
224
+
225
+ .wn-light .wn-unsaved-modal-btn--secondary {
226
+ border-color: var(--wn-zinc-200);
227
+ color: var(--wn-error-600);
228
+ }
229
+
230
+ .wn-light .wn-unsaved-modal-btn--secondary:hover:not(:disabled) {
231
+ background-color: var(--wn-error-alpha-10);
232
+ color: #b91c1c;
233
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * @fileoverview Unsaved changes confirmation modal
3
+ *
4
+ * Modal dialog that prompts users when they attempt to navigate away
5
+ * from content with unsaved changes. Provides options to save or discard.
6
+ * Cancel is available via X button, clicking outside, or pressing Escape.
7
+ * Includes focus trap for accessibility compliance.
8
+ *
9
+ * @module @writenex/astro/client/components/UnsavedChangesModal
10
+ */
11
+
12
+ import { useCallback, useEffect, useRef } from "react";
13
+ import { X, AlertTriangle } from "lucide-react";
14
+ import { useFocusTrap } from "../../hooks/useFocusTrap";
15
+ import "./UnsavedChangesModal.css";
16
+
17
+ /**
18
+ * Props for UnsavedChangesModal component
19
+ */
20
+ interface UnsavedChangesModalProps {
21
+ /** Whether the modal is open */
22
+ isOpen: boolean;
23
+ /** Callback to close the modal (cancel action) */
24
+ onClose: () => void;
25
+ /** Callback when user chooses to discard changes and continue */
26
+ onDiscard: () => void;
27
+ /** Callback when user chooses to save and continue */
28
+ onSave: () => void;
29
+ /** Whether save is in progress */
30
+ isSaving?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Modal dialog for unsaved changes confirmation
35
+ *
36
+ * Features:
37
+ * - Two action buttons: Don't Save (discard), Save
38
+ * - Cancel via X button, click outside, or Escape key
39
+ * - Loading state during save
40
+ * - Consistent styling with design system
41
+ *
42
+ * @component
43
+ * @example
44
+ * ```tsx
45
+ * <UnsavedChangesModal
46
+ * isOpen={showUnsavedModal}
47
+ * onClose={() => setShowUnsavedModal(false)}
48
+ * onDiscard={handleDiscard}
49
+ * onSave={handleSave}
50
+ * />
51
+ * ```
52
+ */
53
+ export function UnsavedChangesModal({
54
+ isOpen,
55
+ onClose,
56
+ onDiscard,
57
+ onSave,
58
+ isSaving = false,
59
+ }: UnsavedChangesModalProps): React.ReactElement | null {
60
+ const triggerRef = useRef<HTMLElement | null>(null);
61
+ const discardButtonRef = useRef<HTMLButtonElement>(null);
62
+
63
+ // Store the trigger element when modal opens
64
+ useEffect(() => {
65
+ if (isOpen) {
66
+ triggerRef.current = document.activeElement as HTMLElement;
67
+ }
68
+ }, [isOpen]);
69
+
70
+ // Focus trap for accessibility
71
+ const { containerRef } = useFocusTrap({
72
+ enabled: isOpen,
73
+ onEscape: isSaving ? undefined : onClose,
74
+ returnFocusTo: triggerRef.current,
75
+ });
76
+
77
+ // Focus first button when modal opens
78
+ useEffect(() => {
79
+ if (isOpen) {
80
+ setTimeout(() => {
81
+ discardButtonRef.current?.focus();
82
+ }, 50);
83
+ }
84
+ }, [isOpen]);
85
+
86
+ const handleOverlayClick = useCallback(
87
+ (e: React.MouseEvent) => {
88
+ if (e.target === e.currentTarget && !isSaving) {
89
+ onClose();
90
+ }
91
+ },
92
+ [onClose, isSaving]
93
+ );
94
+
95
+ if (!isOpen) return null;
96
+
97
+ return (
98
+ <div
99
+ className="wn-unsaved-modal-overlay"
100
+ onClick={handleOverlayClick}
101
+ role="presentation"
102
+ >
103
+ <div
104
+ ref={containerRef}
105
+ className="wn-unsaved-modal"
106
+ role="alertdialog"
107
+ aria-modal="true"
108
+ aria-labelledby="unsaved-modal-title"
109
+ aria-describedby="unsaved-modal-description"
110
+ >
111
+ {/* Header */}
112
+ <div className="wn-unsaved-modal-header">
113
+ <div className="wn-unsaved-modal-header-content">
114
+ <AlertTriangle size={18} className="wn-unsaved-modal-icon" />
115
+ <h2 id="unsaved-modal-title" className="wn-unsaved-modal-title">
116
+ Unsaved Changes
117
+ </h2>
118
+ </div>
119
+ <button
120
+ className="wn-unsaved-modal-close"
121
+ onClick={onClose}
122
+ disabled={isSaving}
123
+ title="Close (Esc)"
124
+ aria-label="Close modal"
125
+ >
126
+ <X size={16} />
127
+ </button>
128
+ </div>
129
+
130
+ {/* Body */}
131
+ <div className="wn-unsaved-modal-body">
132
+ <p id="unsaved-modal-description" className="wn-unsaved-modal-text">
133
+ You have unsaved changes. Save them before continuing?
134
+ </p>
135
+ </div>
136
+
137
+ {/* Footer */}
138
+ <div className="wn-unsaved-modal-footer">
139
+ <button
140
+ ref={discardButtonRef}
141
+ type="button"
142
+ className="wn-unsaved-modal-btn wn-unsaved-modal-btn--secondary"
143
+ onClick={onDiscard}
144
+ disabled={isSaving}
145
+ >
146
+ Don't Save
147
+ </button>
148
+ <button
149
+ type="button"
150
+ className="wn-unsaved-modal-btn wn-unsaved-modal-btn--primary"
151
+ onClick={onSave}
152
+ disabled={isSaving}
153
+ >
154
+ {isSaving ? "Saving..." : "Save"}
155
+ </button>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ );
160
+ }
@@ -0,0 +1 @@
1
+ export { UnsavedChangesModal } from "./UnsavedChangesModal";