@actuate-media/cms-admin 0.12.0 → 0.12.1

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 (152) hide show
  1. package/dist/__tests__/components/comment-mark.test.d.ts +2 -0
  2. package/dist/__tests__/components/comment-mark.test.d.ts.map +1 -0
  3. package/dist/__tests__/components/comment-mark.test.js +89 -0
  4. package/dist/__tests__/components/comment-mark.test.js.map +1 -0
  5. package/dist/__tests__/components/comment-side-panel.test.d.ts +2 -0
  6. package/dist/__tests__/components/comment-side-panel.test.d.ts.map +1 -0
  7. package/dist/__tests__/components/comment-side-panel.test.js +291 -0
  8. package/dist/__tests__/components/comment-side-panel.test.js.map +1 -0
  9. package/dist/__tests__/components/notification-bell.test.d.ts +2 -0
  10. package/dist/__tests__/components/notification-bell.test.d.ts.map +1 -0
  11. package/dist/__tests__/components/notification-bell.test.js +200 -0
  12. package/dist/__tests__/components/notification-bell.test.js.map +1 -0
  13. package/dist/__tests__/components/offline-status.test.d.ts +2 -0
  14. package/dist/__tests__/components/offline-status.test.d.ts.map +1 -0
  15. package/dist/__tests__/components/offline-status.test.js +56 -0
  16. package/dist/__tests__/components/offline-status.test.js.map +1 -0
  17. package/dist/__tests__/components/presence-chips-helpers.test.d.ts +2 -0
  18. package/dist/__tests__/components/presence-chips-helpers.test.d.ts.map +1 -0
  19. package/dist/__tests__/components/presence-chips-helpers.test.js +35 -0
  20. package/dist/__tests__/components/presence-chips-helpers.test.js.map +1 -0
  21. package/dist/__tests__/lib/active-editor.test.d.ts +2 -0
  22. package/dist/__tests__/lib/active-editor.test.d.ts.map +1 -0
  23. package/dist/__tests__/lib/active-editor.test.js +57 -0
  24. package/dist/__tests__/lib/active-editor.test.js.map +1 -0
  25. package/dist/__tests__/lib/coauthor-client.test.d.ts +2 -0
  26. package/dist/__tests__/lib/coauthor-client.test.d.ts.map +1 -0
  27. package/dist/__tests__/lib/coauthor-client.test.js +173 -0
  28. package/dist/__tests__/lib/coauthor-client.test.js.map +1 -0
  29. package/dist/__tests__/lib/collaboration-provider.test.d.ts +2 -0
  30. package/dist/__tests__/lib/collaboration-provider.test.d.ts.map +1 -0
  31. package/dist/__tests__/lib/collaboration-provider.test.js +335 -0
  32. package/dist/__tests__/lib/collaboration-provider.test.js.map +1 -0
  33. package/dist/__tests__/lib/comment-anchor.test.d.ts +2 -0
  34. package/dist/__tests__/lib/comment-anchor.test.d.ts.map +1 -0
  35. package/dist/__tests__/lib/comment-anchor.test.js +173 -0
  36. package/dist/__tests__/lib/comment-anchor.test.js.map +1 -0
  37. package/dist/__tests__/lib/comments-client.test.d.ts +2 -0
  38. package/dist/__tests__/lib/comments-client.test.d.ts.map +1 -0
  39. package/dist/__tests__/lib/comments-client.test.js +202 -0
  40. package/dist/__tests__/lib/comments-client.test.js.map +1 -0
  41. package/dist/__tests__/lib/notifications-client.test.d.ts +2 -0
  42. package/dist/__tests__/lib/notifications-client.test.d.ts.map +1 -0
  43. package/dist/__tests__/lib/notifications-client.test.js +155 -0
  44. package/dist/__tests__/lib/notifications-client.test.js.map +1 -0
  45. package/dist/__tests__/lib/slash-command.test.d.ts +2 -0
  46. package/dist/__tests__/lib/slash-command.test.d.ts.map +1 -0
  47. package/dist/__tests__/lib/slash-command.test.js +123 -0
  48. package/dist/__tests__/lib/slash-command.test.js.map +1 -0
  49. package/dist/actuate-admin.css +1 -1
  50. package/dist/components/AICoauthorBubbleMenu.d.ts +18 -0
  51. package/dist/components/AICoauthorBubbleMenu.d.ts.map +1 -0
  52. package/dist/components/AICoauthorBubbleMenu.js +113 -0
  53. package/dist/components/AICoauthorBubbleMenu.js.map +1 -0
  54. package/dist/components/CoauthorResultPopover.d.ts +28 -0
  55. package/dist/components/CoauthorResultPopover.d.ts.map +1 -0
  56. package/dist/components/CoauthorResultPopover.js +42 -0
  57. package/dist/components/CoauthorResultPopover.js.map +1 -0
  58. package/dist/components/CommandPalette.d.ts +1 -1
  59. package/dist/components/CommandPalette.d.ts.map +1 -1
  60. package/dist/components/CommandPalette.js +138 -40
  61. package/dist/components/CommandPalette.js.map +1 -1
  62. package/dist/components/CommentMark.d.ts +54 -0
  63. package/dist/components/CommentMark.d.ts.map +1 -0
  64. package/dist/components/CommentMark.js +104 -0
  65. package/dist/components/CommentMark.js.map +1 -0
  66. package/dist/components/CommentSidePanel.d.ts +86 -0
  67. package/dist/components/CommentSidePanel.d.ts.map +1 -0
  68. package/dist/components/CommentSidePanel.js +165 -0
  69. package/dist/components/CommentSidePanel.js.map +1 -0
  70. package/dist/components/LivePreview.d.ts.map +1 -1
  71. package/dist/components/LivePreview.js +80 -16
  72. package/dist/components/LivePreview.js.map +1 -1
  73. package/dist/components/NotificationBell.d.ts +45 -0
  74. package/dist/components/NotificationBell.d.ts.map +1 -0
  75. package/dist/components/NotificationBell.js +90 -0
  76. package/dist/components/NotificationBell.js.map +1 -0
  77. package/dist/components/OfflineStatus.d.ts +39 -0
  78. package/dist/components/OfflineStatus.d.ts.map +1 -0
  79. package/dist/components/OfflineStatus.js +47 -0
  80. package/dist/components/OfflineStatus.js.map +1 -0
  81. package/dist/components/PresenceChips.d.ts +31 -0
  82. package/dist/components/PresenceChips.d.ts.map +1 -0
  83. package/dist/components/PresenceChips.js +58 -0
  84. package/dist/components/PresenceChips.js.map +1 -0
  85. package/dist/components/TipTapEditor.d.ts +61 -1
  86. package/dist/components/TipTapEditor.d.ts.map +1 -1
  87. package/dist/components/TipTapEditor.js +211 -8
  88. package/dist/components/TipTapEditor.js.map +1 -1
  89. package/dist/components/ui/Badge.d.ts +1 -1
  90. package/dist/index.d.ts +8 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +4 -0
  93. package/dist/index.js.map +1 -1
  94. package/dist/lib/active-editor.d.ts +6 -0
  95. package/dist/lib/active-editor.d.ts.map +1 -0
  96. package/dist/lib/active-editor.js +35 -0
  97. package/dist/lib/active-editor.js.map +1 -0
  98. package/dist/lib/coauthor-client.d.ts +72 -0
  99. package/dist/lib/coauthor-client.d.ts.map +1 -0
  100. package/dist/lib/coauthor-client.js +98 -0
  101. package/dist/lib/coauthor-client.js.map +1 -0
  102. package/dist/lib/collaboration-provider.d.ts +142 -0
  103. package/dist/lib/collaboration-provider.d.ts.map +1 -0
  104. package/dist/lib/collaboration-provider.js +168 -0
  105. package/dist/lib/collaboration-provider.js.map +1 -0
  106. package/dist/lib/comment-anchor.d.ts +86 -0
  107. package/dist/lib/comment-anchor.d.ts.map +1 -0
  108. package/dist/lib/comment-anchor.js +139 -0
  109. package/dist/lib/comment-anchor.js.map +1 -0
  110. package/dist/lib/comments-client.d.ts +106 -0
  111. package/dist/lib/comments-client.d.ts.map +1 -0
  112. package/dist/lib/comments-client.js +138 -0
  113. package/dist/lib/comments-client.js.map +1 -0
  114. package/dist/lib/notifications-client.d.ts +68 -0
  115. package/dist/lib/notifications-client.d.ts.map +1 -0
  116. package/dist/lib/notifications-client.js +85 -0
  117. package/dist/lib/notifications-client.js.map +1 -0
  118. package/dist/lib/slash-command.d.ts +39 -0
  119. package/dist/lib/slash-command.d.ts.map +1 -0
  120. package/dist/lib/slash-command.js +50 -0
  121. package/dist/lib/slash-command.js.map +1 -0
  122. package/package.json +12 -3
  123. package/src/__tests__/components/comment-mark.test.ts +99 -0
  124. package/src/__tests__/components/comment-side-panel.test.tsx +343 -0
  125. package/src/__tests__/components/notification-bell.test.tsx +213 -0
  126. package/src/__tests__/components/offline-status.test.tsx +66 -0
  127. package/src/__tests__/components/presence-chips-helpers.test.ts +44 -0
  128. package/src/__tests__/lib/active-editor.test.ts +70 -0
  129. package/src/__tests__/lib/coauthor-client.test.ts +213 -0
  130. package/src/__tests__/lib/collaboration-provider.test.ts +425 -0
  131. package/src/__tests__/lib/comment-anchor.test.ts +188 -0
  132. package/src/__tests__/lib/comments-client.test.ts +239 -0
  133. package/src/__tests__/lib/notifications-client.test.ts +195 -0
  134. package/src/__tests__/lib/slash-command.test.ts +146 -0
  135. package/src/components/AICoauthorBubbleMenu.tsx +187 -0
  136. package/src/components/CoauthorResultPopover.tsx +193 -0
  137. package/src/components/CommandPalette.tsx +397 -209
  138. package/src/components/CommentMark.ts +138 -0
  139. package/src/components/CommentSidePanel.tsx +521 -0
  140. package/src/components/LivePreview.tsx +117 -11
  141. package/src/components/NotificationBell.tsx +242 -0
  142. package/src/components/OfflineStatus.tsx +90 -0
  143. package/src/components/PresenceChips.tsx +150 -0
  144. package/src/components/TipTapEditor.tsx +310 -12
  145. package/src/index.ts +20 -0
  146. package/src/lib/active-editor.ts +39 -0
  147. package/src/lib/coauthor-client.ts +160 -0
  148. package/src/lib/collaboration-provider.ts +306 -0
  149. package/src/lib/comment-anchor.ts +179 -0
  150. package/src/lib/comments-client.ts +208 -0
  151. package/src/lib/notifications-client.ts +146 -0
  152. package/src/lib/slash-command.ts +67 -0
@@ -0,0 +1,187 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useState } from 'react'
4
+ import { BubbleMenu, type Editor } from '@tiptap/react'
5
+ import { Loader2, Maximize2, Minimize2, Sparkles, SpellCheck, Wand2 } from 'lucide-react'
6
+ import { toast } from 'sonner'
7
+ import {
8
+ COAUTHOR_ACTIONS,
9
+ coauthorActionLabel,
10
+ coauthorText,
11
+ type CoauthorAction,
12
+ type ProofreadChange,
13
+ } from '../lib/coauthor-client.js'
14
+ import { CoauthorResultPopover } from './CoauthorResultPopover.js'
15
+
16
+ export interface AICoauthorBubbleMenuProps {
17
+ editor: Editor | null
18
+ }
19
+
20
+ const ACTION_ICONS: Record<CoauthorAction, typeof Sparkles> = {
21
+ rewrite: Wand2,
22
+ expand: Maximize2,
23
+ compress: Minimize2,
24
+ proofread: SpellCheck,
25
+ }
26
+
27
+ interface ResultState {
28
+ open: boolean
29
+ loading: boolean
30
+ action: CoauthorAction
31
+ before: string
32
+ after: string
33
+ error?: string
34
+ changes?: ProofreadChange[]
35
+ from: number
36
+ to: number
37
+ }
38
+
39
+ const INITIAL: ResultState = {
40
+ open: false,
41
+ loading: false,
42
+ action: 'rewrite',
43
+ before: '',
44
+ after: '',
45
+ from: 0,
46
+ to: 0,
47
+ }
48
+
49
+ /**
50
+ * Floating menu that appears above the current text selection inside the
51
+ * TipTap editor. Renders four AI co-author actions
52
+ * (rewrite / expand / compress / proofread) and hands the selected text to
53
+ * `/ai/coauthor`. When the server returns, opens the shared
54
+ * `CoauthorResultPopover` with a before/after diff and Apply / Copy /
55
+ * Discard actions.
56
+ *
57
+ * The popover, not the bubble itself, holds the result — by design — so
58
+ * the user can scroll the editor, see the source of truth, and explicitly
59
+ * choose what to do with the AI output.
60
+ */
61
+ export function AICoauthorBubbleMenu({ editor }: AICoauthorBubbleMenuProps) {
62
+ const [state, setState] = useState<ResultState>(INITIAL)
63
+ const [pendingAction, setPendingAction] = useState<CoauthorAction | null>(null)
64
+
65
+ const runAction = useCallback(
66
+ async (action: CoauthorAction) => {
67
+ if (!editor) return
68
+ const { from, to, empty } = editor.state.selection
69
+ if (empty) return
70
+ const before = editor.state.doc.textBetween(from, to, '\n', '\n')
71
+ if (!before.trim()) {
72
+ toast.error('Select text to use the AI co-author.')
73
+ return
74
+ }
75
+
76
+ setPendingAction(action)
77
+ setState({
78
+ open: true,
79
+ loading: true,
80
+ action,
81
+ before,
82
+ after: '',
83
+ from,
84
+ to,
85
+ })
86
+
87
+ const outcome = await coauthorText(action, before)
88
+ if (!outcome.ok) {
89
+ setState((prev) => ({ ...prev, loading: false, error: outcome.error }))
90
+ toast.error(outcome.error)
91
+ setPendingAction(null)
92
+ return
93
+ }
94
+
95
+ setState((prev) => ({
96
+ ...prev,
97
+ loading: false,
98
+ after: outcome.result.text,
99
+ changes: outcome.result.changes,
100
+ error: undefined,
101
+ }))
102
+ setPendingAction(null)
103
+ },
104
+ [editor],
105
+ )
106
+
107
+ const handleApply = useCallback(() => {
108
+ if (!editor) return
109
+ editor.chain().focus().insertContentAt({ from: state.from, to: state.to }, state.after).run()
110
+ toast.success(`${coauthorActionLabel(state.action)} applied.`)
111
+ setState(INITIAL)
112
+ }, [editor, state.action, state.from, state.to, state.after])
113
+
114
+ const handleCopy = useCallback(async () => {
115
+ try {
116
+ await navigator.clipboard.writeText(state.after)
117
+ toast.success('Copied to clipboard.')
118
+ } catch {
119
+ toast.error('Clipboard unavailable — copy the text manually.')
120
+ }
121
+ }, [state.after])
122
+
123
+ const handleDiscard = useCallback(() => {
124
+ setState(INITIAL)
125
+ setPendingAction(null)
126
+ }, [])
127
+
128
+ if (!editor) return null
129
+
130
+ return (
131
+ <>
132
+ <BubbleMenu
133
+ editor={editor}
134
+ shouldShow={({ editor: ed, from, to }) => {
135
+ if (state.open) return false
136
+ if (from === to) return false
137
+ if (!ed.isEditable) return false
138
+ return true
139
+ }}
140
+ tippyOptions={{ duration: 100, placement: 'top' }}
141
+ className="flex items-center gap-0.5 rounded-md border border-gray-200 bg-white p-1 shadow-md"
142
+ >
143
+ <span className="flex items-center gap-1 px-1.5 text-xs font-medium text-gray-500">
144
+ <Sparkles className="h-3 w-3" aria-hidden />
145
+ AI
146
+ </span>
147
+ <span className="mx-0.5 h-4 w-px bg-gray-200" />
148
+ {COAUTHOR_ACTIONS.map((id) => {
149
+ const Icon = ACTION_ICONS[id]
150
+ const isPending = pendingAction === id
151
+ const anyPending = pendingAction !== null
152
+ return (
153
+ <button
154
+ key={id}
155
+ type="button"
156
+ onClick={() => runAction(id)}
157
+ disabled={anyPending}
158
+ className="flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-700 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50"
159
+ title={`AI ${coauthorActionLabel(id)}`}
160
+ >
161
+ {isPending ? (
162
+ <Loader2 className="h-3 w-3 animate-spin" aria-hidden />
163
+ ) : (
164
+ <Icon className="h-3 w-3" aria-hidden />
165
+ )}
166
+ {coauthorActionLabel(id)}
167
+ </button>
168
+ )
169
+ })}
170
+ </BubbleMenu>
171
+
172
+ <CoauthorResultPopover
173
+ open={state.open}
174
+ loading={state.loading}
175
+ action={state.action}
176
+ before={state.before}
177
+ after={state.after}
178
+ error={state.error}
179
+ changes={state.changes}
180
+ applyLabel="Replace selection"
181
+ onApply={handleApply}
182
+ onCopy={handleCopy}
183
+ onDiscard={handleDiscard}
184
+ />
185
+ </>
186
+ )
187
+ }
@@ -0,0 +1,193 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useRef } from 'react'
4
+ import { Check, Copy, Loader2, Sparkles, X } from 'lucide-react'
5
+ import type { CoauthorAction, ProofreadChange } from '../lib/coauthor-client.js'
6
+ import { coauthorActionLabel } from '../lib/coauthor-client.js'
7
+
8
+ export interface CoauthorResultPopoverProps {
9
+ open: boolean
10
+ loading: boolean
11
+ action: CoauthorAction
12
+ before: string
13
+ after: string
14
+ error?: string
15
+ changes?: ProofreadChange[]
16
+ /** Label for the apply target, e.g. "Apply to editor" or "Replace selection". */
17
+ applyLabel?: string
18
+ onApply: () => void
19
+ onCopy: () => void
20
+ onDiscard: () => void
21
+ }
22
+
23
+ /**
24
+ * Universal AI-result panel rendered by both the TipTap bubble menu and the
25
+ * Cmd+K slash-command palette. Shows a before/after diff plus three actions:
26
+ *
27
+ * - Apply (write the result back to the active target)
28
+ * - Copy (copy the result to the clipboard)
29
+ * - Discard (close without changing anything)
30
+ *
31
+ * The popover takes the same `{ open, loading, action, before, after, error }`
32
+ * state shape regardless of caller so the UX stays identical across surfaces.
33
+ */
34
+ export function CoauthorResultPopover({
35
+ open,
36
+ loading,
37
+ action,
38
+ before,
39
+ after,
40
+ error,
41
+ changes,
42
+ applyLabel,
43
+ onApply,
44
+ onCopy,
45
+ onDiscard,
46
+ }: CoauthorResultPopoverProps) {
47
+ const dialogRef = useRef<HTMLDivElement>(null)
48
+
49
+ useEffect(() => {
50
+ if (!open) return
51
+ const onKey = (e: KeyboardEvent) => {
52
+ if (e.key === 'Escape') {
53
+ e.preventDefault()
54
+ onDiscard()
55
+ }
56
+ }
57
+ document.addEventListener('keydown', onKey)
58
+ return () => document.removeEventListener('keydown', onKey)
59
+ }, [open, onDiscard])
60
+
61
+ useEffect(() => {
62
+ if (!open) return
63
+ dialogRef.current?.focus()
64
+ }, [open])
65
+
66
+ if (!open) return null
67
+
68
+ const applyDisabled = loading || !!error || after.length === 0
69
+ const copyDisabled = loading || !!error || after.length === 0
70
+
71
+ return (
72
+ <div
73
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4"
74
+ role="presentation"
75
+ onClick={onDiscard}
76
+ >
77
+ <div
78
+ ref={dialogRef}
79
+ role="dialog"
80
+ aria-modal="true"
81
+ aria-label={`AI ${coauthorActionLabel(action)} result`}
82
+ tabIndex={-1}
83
+ onClick={(e) => e.stopPropagation()}
84
+ className="w-full max-w-2xl rounded-lg bg-white shadow-2xl outline-none"
85
+ >
86
+ <div className="flex items-center justify-between border-b border-gray-200 px-4 py-3">
87
+ <div className="flex items-center gap-2 text-sm font-semibold text-gray-900">
88
+ <Sparkles className="h-4 w-4 text-blue-600" aria-hidden />
89
+ <span>AI {coauthorActionLabel(action)}</span>
90
+ {loading && (
91
+ <Loader2 className="ml-1 h-4 w-4 animate-spin text-gray-400" aria-label="Loading" />
92
+ )}
93
+ </div>
94
+ <button
95
+ type="button"
96
+ onClick={onDiscard}
97
+ className="rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
98
+ aria-label="Close"
99
+ >
100
+ <X className="h-4 w-4" />
101
+ </button>
102
+ </div>
103
+
104
+ <div className="grid grid-cols-2 gap-4 p-4">
105
+ <section aria-labelledby="coauthor-before">
106
+ <h3
107
+ id="coauthor-before"
108
+ className="mb-2 text-xs font-medium tracking-wide text-gray-500 uppercase"
109
+ >
110
+ Before
111
+ </h3>
112
+ <pre className="max-h-72 overflow-auto rounded border border-gray-200 bg-gray-50 p-3 text-sm whitespace-pre-wrap text-gray-700">
113
+ {before}
114
+ </pre>
115
+ </section>
116
+ <section aria-labelledby="coauthor-after">
117
+ <h3
118
+ id="coauthor-after"
119
+ className="mb-2 text-xs font-medium tracking-wide text-gray-500 uppercase"
120
+ >
121
+ After
122
+ </h3>
123
+ {loading ? (
124
+ <div
125
+ className="flex h-72 items-center justify-center rounded border border-dashed border-gray-200 bg-gray-50 text-sm text-gray-400"
126
+ role="status"
127
+ >
128
+ Thinking…
129
+ </div>
130
+ ) : error ? (
131
+ <div className="flex h-72 items-center justify-center rounded border border-dashed border-red-200 bg-red-50 p-3 text-sm text-red-600">
132
+ {error}
133
+ </div>
134
+ ) : (
135
+ <pre className="max-h-72 overflow-auto rounded border border-blue-200 bg-blue-50 p-3 text-sm whitespace-pre-wrap text-gray-900">
136
+ {after}
137
+ </pre>
138
+ )}
139
+ </section>
140
+ </div>
141
+
142
+ {changes && changes.length > 0 && !loading && !error && (
143
+ <div className="border-t border-gray-200 px-4 py-3">
144
+ <h3 className="mb-2 text-xs font-medium tracking-wide text-gray-500 uppercase">
145
+ Changes ({changes.length})
146
+ </h3>
147
+ <ul className="max-h-32 space-y-1 overflow-auto text-xs">
148
+ {changes.map((change, i) => (
149
+ <li key={i} className="flex items-start gap-2 rounded bg-gray-50 px-2 py-1">
150
+ <span className="text-red-600 line-through">{change.original}</span>
151
+ <span className="text-gray-400">→</span>
152
+ <span className="text-green-700">{change.corrected}</span>
153
+ {change.reason && (
154
+ <span className="ml-auto text-gray-500 italic">{change.reason}</span>
155
+ )}
156
+ </li>
157
+ ))}
158
+ </ul>
159
+ </div>
160
+ )}
161
+
162
+ <div className="flex items-center justify-end gap-2 border-t border-gray-200 bg-gray-50 px-4 py-3">
163
+ <button
164
+ type="button"
165
+ onClick={onDiscard}
166
+ className="rounded-md border border-gray-200 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50"
167
+ >
168
+ <X className="mr-1.5 inline h-3.5 w-3.5" aria-hidden />
169
+ Discard
170
+ </button>
171
+ <button
172
+ type="button"
173
+ onClick={onCopy}
174
+ disabled={copyDisabled}
175
+ className="rounded-md border border-gray-200 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50"
176
+ >
177
+ <Copy className="mr-1.5 inline h-3.5 w-3.5" aria-hidden />
178
+ Copy
179
+ </button>
180
+ <button
181
+ type="button"
182
+ onClick={onApply}
183
+ disabled={applyDisabled}
184
+ className="rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
185
+ >
186
+ <Check className="mr-1.5 inline h-3.5 w-3.5" aria-hidden />
187
+ {applyLabel ?? 'Apply'}
188
+ </button>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ )
193
+ }