@plannotator/ui 0.29.1 → 0.31.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 +1 -0
- package/components/ActionMenu.tsx +6 -1
- package/components/AgentsTab.tsx +8 -9
- package/components/AnalysisLayerToggle.tsx +48 -0
- package/components/AnnotationPanel.tsx +68 -35
- package/components/AnnotationToolbar.tsx +28 -8
- package/components/AnnotationToolstrip.tsx +9 -0
- package/components/CommentPopover.tsx +212 -46
- package/components/ConfirmDialog.tsx +42 -28
- package/components/InlineMarkdown.tsx +3 -2
- package/components/KeyboardShortcuts.tsx +9 -0
- package/components/Landing.tsx +1 -1
- package/components/LookAndFeelAnnouncementDialog.tsx +147 -178
- package/components/MarkdownEditor/embedPicker.ts +349 -0
- package/components/MarkdownEditor.tsx +12 -0
- package/components/ModeToggle.tsx +2 -1
- package/components/PermissionModeSetup.tsx +24 -5
- package/components/PinpointOverlay.tsx +11 -6
- package/components/PlanHeaderMenu.tsx +140 -1
- package/components/SearchableSelect.tsx +2 -0
- package/components/Settings.tsx +136 -10
- package/components/SkillReferenceMenu.tsx +9 -0
- package/components/StickyHeaderLane.tsx +9 -2
- package/components/TableOfContents.tsx +9 -4
- package/components/TextShimmer.tsx +8 -5
- package/components/ThemeProvider.tsx +43 -1
- package/components/ThemeTab.tsx +52 -1
- package/components/Tooltip.tsx +3 -1
- package/components/Viewer.tsx +19 -5
- package/components/VimTargetReticle.tsx +12 -4
- package/components/ai/DocumentAIChatPanel.tsx +1 -0
- package/components/core/button.tsx +14 -6
- package/components/html-viewer/HtmlViewer.tsx +215 -39
- package/components/html-viewer/bridge-script.ts +384 -66
- package/components/html-viewer/composerYield.ts +1 -51
- package/components/html-viewer/useHtmlAnnotation.ts +179 -58
- package/components/plan-diff/PlanCleanDiffView.tsx +1 -0
- package/components/sidebar/FileBrowser.tsx +17 -5
- package/components/sidebar/SidebarContainer.tsx +124 -28
- package/components/ui/button.tsx +10 -8
- package/components/ui/dialog.tsx +35 -25
- package/config/index.ts +6 -1
- package/config/reviewView.ts +42 -9
- package/config/settings.ts +141 -0
- package/hooks/useAIProviderConfig.ts +8 -7
- package/hooks/useActiveSection.ts +6 -4
- package/hooks/useAgentJobs.ts +3 -0
- package/hooks/useAnnotationHighlighter.ts +20 -0
- package/hooks/useIsMobile.ts +37 -0
- package/hooks/useLinkedDoc.ts +7 -0
- package/hooks/useScrollViewport.ts +74 -0
- package/hooks/useViewportEnvironment.ts +350 -0
- package/package.json +3 -2
- package/shortcuts/index.ts +3 -0
- package/shortcuts/plan-review/annotationMode.shortcuts.ts +91 -0
- package/shortcuts/plan-review/documentView.shortcuts.ts +26 -0
- package/shortcuts/plan-review/htmlAnnotate.shortcuts.ts +24 -0
- package/styles.css +1 -1
- package/theme.css +229 -0
- package/types.ts +34 -0
- package/utils/annotateAgentTerminal.ts +36 -5
- package/utils/blockTargeting.ts +6 -3
- package/utils/composerYield.ts +45 -0
- package/utils/htmlChrome.ts +20 -16
- package/utils/lookAndFeelAnnouncement.ts +12 -8
- package/utils/markdownExtensions.ts +57 -0
- package/utils/parser.ts +37 -2
- package/utils/vimNavigation.ts +4 -1
- package/utils/vimScroll.ts +9 -4
- package/utils/wideMode.ts +20 -0
- package/components/PlanAIAnnouncementDialog.tsx +0 -187
- package/components/VimModeAnnouncementDialog.tsx +0 -557
- package/utils/planAIAnnouncement.ts +0 -17
- package/utils/vimModeAnnouncement.ts +0 -23
|
@@ -84,9 +84,17 @@ const inputMethodShortcuts: ShortcutSection = {
|
|
|
84
84
|
],
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
const documentViewShortcuts: ShortcutSection = {
|
|
88
|
+
title: 'View',
|
|
89
|
+
shortcuts: [
|
|
90
|
+
{ keys: [modKey, '.'], desc: 'Toggle focus mode', hint: 'Collapses the Contents sidebar and the right-hand panel together; press again to restore whatever was open before. Markdown documents only; HTML pages keep their own layout.' },
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
|
|
87
94
|
const annotationShortcuts: ShortcutSection = {
|
|
88
95
|
title: 'Annotations',
|
|
89
96
|
shortcuts: [
|
|
97
|
+
{ keys: [shiftKey, '1-4'], desc: 'Switch annotation mode', hint: '1 Markup, 2 Comment, 3 Redline, 4 Label — matches the toolstrip order' },
|
|
90
98
|
{ keys: ['a-z'], desc: 'Start typing comment', hint: 'When the annotation toolbar is open, any letter key opens the comment editor with that character' },
|
|
91
99
|
{ keys: [altKey, '1-0'], desc: 'Apply quick label', hint: 'Instantly applies the Nth preset label (0 = 10th). When the label picker is open, bare digits also work.' },
|
|
92
100
|
{ keys: [modKey, enter], desc: 'Submit comment' },
|
|
@@ -108,6 +116,7 @@ const imageAnnotatorShortcuts: ShortcutSection = {
|
|
|
108
116
|
};
|
|
109
117
|
|
|
110
118
|
const sharedPlanEditorShortcuts: ShortcutSection[] = [
|
|
119
|
+
documentViewShortcuts,
|
|
111
120
|
inputMethodShortcuts,
|
|
112
121
|
annotationShortcuts,
|
|
113
122
|
imageAnnotatorShortcuts,
|
package/components/Landing.tsx
CHANGED
|
@@ -50,7 +50,7 @@ export const Landing: React.FC<LandingProps> = ({ onEnter, shareBaseUrl }) => {
|
|
|
50
50
|
<div className="max-w-2xl">
|
|
51
51
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 text-primary text-xs font-medium mb-6">
|
|
52
52
|
<span className="w-1.5 h-1.5 rounded-full bg-primary animate-pulse" />
|
|
53
|
-
For Claude Code & OpenCode
|
|
53
|
+
For Claude Code & OpenCode
|
|
54
54
|
</div>
|
|
55
55
|
|
|
56
56
|
<div className="flex items-center gap-6 mb-4">
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/// <reference path="../globals.d.ts" />
|
|
2
|
-
import React, {
|
|
2
|
+
import React, { useEffect, useRef } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import { TextShimmer } from './TextShimmer';
|
|
5
4
|
import lookGridImg from '../assets/look-grid.png';
|
|
6
5
|
import lookFlatImg from '../assets/look-flat.png';
|
|
7
|
-
import workspacesImg from '../assets/workspaces.webp';
|
|
8
6
|
|
|
9
7
|
interface LookAndFeelAnnouncementDialogProps {
|
|
10
8
|
isOpen: boolean;
|
|
@@ -12,33 +10,16 @@ interface LookAndFeelAnnouncementDialogProps {
|
|
|
12
10
|
gridEnabled: boolean;
|
|
13
11
|
/** App owns this: it calls configStore.set('gridEnabled', value). */
|
|
14
12
|
onToggleGrid: (value: boolean) => void;
|
|
15
|
-
/**
|
|
13
|
+
/** Persists the current choice and closes the dialog. */
|
|
16
14
|
onDismiss: () => void;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
const WAITLIST_URL = 'https://plannotator.ai/workspaces';
|
|
20
|
-
|
|
21
|
-
const FEATURES: { title: string; desc: string }[] = [
|
|
22
|
-
{
|
|
23
|
-
title: 'Leaner install',
|
|
24
|
-
desc: 'Only the core skills ship by default. Extra skills install separately.',
|
|
25
|
-
},
|
|
26
|
-
{ title: 'A fresh new look', desc: 'Refreshed UI 2.0 with new Simple and Neutral themes.' },
|
|
27
|
-
{ title: 'Semantic code review', desc: 'Diffs grouped by what changed, not just which lines.' },
|
|
28
|
-
{ title: 'Multi-repo reviews', desc: 'Review nested repositories together in one pass.' },
|
|
29
|
-
{
|
|
30
|
-
title: 'Full-page HTML',
|
|
31
|
-
desc: 'Render HTML reports and explainers full-screen, then annotate them in place.',
|
|
32
|
-
},
|
|
33
|
-
];
|
|
34
|
-
|
|
35
17
|
const LOOK_OPTIONS: {
|
|
36
18
|
key: string;
|
|
37
19
|
/** gridEnabled value this option selects. */
|
|
38
20
|
value: boolean;
|
|
39
21
|
img: string;
|
|
40
22
|
title: string;
|
|
41
|
-
tag: string;
|
|
42
23
|
desc: string;
|
|
43
24
|
}[] = [
|
|
44
25
|
{
|
|
@@ -46,185 +27,173 @@ const LOOK_OPTIONS: {
|
|
|
46
27
|
value: true,
|
|
47
28
|
img: lookGridImg,
|
|
48
29
|
title: 'Grid',
|
|
49
|
-
|
|
50
|
-
desc: 'Your plan as a floating card on grid paper.',
|
|
30
|
+
desc: 'A floating plan card on grid paper.',
|
|
51
31
|
},
|
|
52
32
|
{
|
|
53
|
-
key: '
|
|
33
|
+
key: 'clean',
|
|
54
34
|
value: false,
|
|
55
35
|
img: lookFlatImg,
|
|
56
36
|
title: 'Clean',
|
|
57
|
-
|
|
58
|
-
desc: 'A simpler, edge-to-edge flat card.',
|
|
37
|
+
desc: 'A simple, edge-to-edge document.',
|
|
59
38
|
},
|
|
60
39
|
];
|
|
61
40
|
|
|
41
|
+
/**
|
|
42
|
+
* First-use plan appearance choice. The version announcement that previously
|
|
43
|
+
* wrapped this decision is intentionally gone: startup asks only for the
|
|
44
|
+
* preference Plannotator cannot infer, and Settings remains the long-term home.
|
|
45
|
+
*/
|
|
62
46
|
export const LookAndFeelAnnouncementDialog: React.FC<LookAndFeelAnnouncementDialogProps> = ({
|
|
63
47
|
isOpen,
|
|
64
48
|
gridEnabled,
|
|
65
49
|
onToggleGrid,
|
|
66
50
|
onDismiss,
|
|
67
51
|
}) => {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
52
|
+
const dialogRef = useRef<HTMLDivElement>(null);
|
|
53
|
+
const continueRef = useRef<HTMLButtonElement>(null);
|
|
54
|
+
const previousFocusRef = useRef<HTMLElement | null>(null);
|
|
55
|
+
const onDismissRef = useRef(onDismiss);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
onDismissRef.current = onDismiss;
|
|
59
|
+
}, [onDismiss]);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!isOpen) return;
|
|
63
|
+
|
|
64
|
+
previousFocusRef.current = document.activeElement instanceof HTMLElement
|
|
65
|
+
? document.activeElement
|
|
66
|
+
: null;
|
|
67
|
+
continueRef.current?.focus();
|
|
68
|
+
|
|
69
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
70
|
+
if (event.key === 'Escape') {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
onDismissRef.current();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (event.key !== 'Tab') return;
|
|
76
|
+
|
|
77
|
+
const focusable = Array.from(
|
|
78
|
+
dialogRef.current?.querySelectorAll<HTMLElement>(
|
|
79
|
+
'button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])',
|
|
80
|
+
) ?? [],
|
|
81
|
+
);
|
|
82
|
+
if (focusable.length === 0) return;
|
|
83
|
+
|
|
84
|
+
const first = focusable[0];
|
|
85
|
+
const last = focusable[focusable.length - 1];
|
|
86
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
last.focus();
|
|
89
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
first.focus();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
96
|
+
return () => {
|
|
97
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
98
|
+
previousFocusRef.current?.focus();
|
|
99
|
+
previousFocusRef.current = null;
|
|
100
|
+
};
|
|
101
|
+
}, [isOpen]);
|
|
102
|
+
|
|
70
103
|
if (!isOpen) return null;
|
|
71
104
|
|
|
72
105
|
return createPortal(
|
|
73
|
-
<div
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
<div
|
|
107
|
+
className="fixed inset-0 z-[100] flex items-center justify-center overflow-y-auto bg-background/90 backdrop-blur-sm"
|
|
108
|
+
style={{
|
|
109
|
+
paddingTop: 'max(0.75rem, var(--pn-safe-top, 0px))',
|
|
110
|
+
paddingRight: 'max(0.75rem, var(--pn-safe-right, 0px))',
|
|
111
|
+
paddingBottom: 'max(0.75rem, var(--pn-safe-bottom, 0px))',
|
|
112
|
+
paddingLeft: 'max(0.75rem, var(--pn-safe-left, 0px))',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<div
|
|
116
|
+
ref={dialogRef}
|
|
117
|
+
role="dialog"
|
|
118
|
+
aria-modal="true"
|
|
119
|
+
aria-labelledby="plan-look-choice-title"
|
|
120
|
+
aria-describedby="plan-look-choice-description"
|
|
121
|
+
className="flex w-full max-w-2xl flex-col overflow-hidden rounded-xl border border-border bg-card shadow-2xl"
|
|
122
|
+
style={{
|
|
123
|
+
maxHeight:
|
|
124
|
+
'calc(var(--pn-viewport-height, 100vh) - max(0.75rem, var(--pn-safe-top, 0px)) - max(0.75rem, var(--pn-safe-bottom, 0px)))',
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<header className="border-b border-border px-4 py-4 sm:px-6 sm:py-5">
|
|
128
|
+
<h2
|
|
129
|
+
id="plan-look-choice-title"
|
|
130
|
+
className="text-balance text-xl font-semibold tracking-tight sm:text-2xl"
|
|
131
|
+
>
|
|
132
|
+
Choose how plans look
|
|
133
|
+
</h2>
|
|
134
|
+
<p
|
|
135
|
+
id="plan-look-choice-description"
|
|
136
|
+
className="mt-1 text-sm leading-relaxed text-muted-foreground"
|
|
137
|
+
>
|
|
138
|
+
Pick a starting view. You can change it anytime in Settings.
|
|
139
|
+
</p>
|
|
140
|
+
</header>
|
|
105
141
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
<div className="min-h-0 overflow-y-auto p-3 sm:p-5">
|
|
143
|
+
<div className="grid grid-cols-2 gap-2.5 sm:gap-4" role="group" aria-label="Plan appearance">
|
|
144
|
+
{LOOK_OPTIONS.map((option) => {
|
|
145
|
+
const selected = gridEnabled === option.value;
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
key={option.key}
|
|
149
|
+
type="button"
|
|
150
|
+
onClick={() => onToggleGrid(option.value)}
|
|
151
|
+
aria-pressed={selected}
|
|
152
|
+
className={`plan-look-choice-option min-h-11 min-w-0 rounded-xl border p-2 text-left outline-none transition-[background-color,border-color,box-shadow] motion-reduce:transition-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-card active:bg-muted/35 sm:p-3 ${
|
|
153
|
+
selected
|
|
154
|
+
? 'border-primary bg-primary/[0.06] shadow-sm'
|
|
155
|
+
: 'border-border bg-muted/20'
|
|
156
|
+
}`}
|
|
157
|
+
>
|
|
158
|
+
<img
|
|
159
|
+
src={option.img}
|
|
160
|
+
alt=""
|
|
161
|
+
aria-hidden="true"
|
|
162
|
+
className="aspect-[1000/626] w-full rounded-lg border border-border/70 object-cover object-top select-none"
|
|
163
|
+
draggable={false}
|
|
164
|
+
/>
|
|
165
|
+
<span className="mt-2.5 flex min-w-0 items-center justify-between gap-2 px-0.5">
|
|
166
|
+
<span className="truncate text-sm font-semibold sm:text-base">{option.title}</span>
|
|
167
|
+
<span
|
|
168
|
+
aria-hidden="true"
|
|
169
|
+
className={`h-4 w-4 shrink-0 rounded-full border-2 p-[3px] ${
|
|
170
|
+
selected ? 'border-primary' : 'border-muted-foreground/35'
|
|
125
171
|
}`}
|
|
126
172
|
>
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}`,
|
|
139
|
-
transform: isHovered ? 'scale(1.22)' : 'scale(1)',
|
|
140
|
-
transformOrigin: 'center',
|
|
141
|
-
zIndex: isHovered ? 50 : 0,
|
|
142
|
-
position: 'relative',
|
|
143
|
-
boxShadow: isHovered ? '0 14px 36px rgba(0,0,0,0.4)' : 'none',
|
|
144
|
-
transition:
|
|
145
|
-
'transform 0.25s cubic-bezier(0.34,1.56,0.64,1), border-color 0.2s ease, box-shadow 0.2s ease',
|
|
146
|
-
}}
|
|
147
|
-
/>
|
|
148
|
-
</div>
|
|
149
|
-
<div className="flex items-center justify-between gap-2 px-0.5 mt-1">
|
|
150
|
-
<span className="text-base font-semibold">{opt.title}</span>
|
|
151
|
-
<span
|
|
152
|
-
className={`text-[11px] leading-none px-2 py-0.5 rounded-full ${
|
|
153
|
-
selected ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'
|
|
154
|
-
}`}
|
|
155
|
-
>
|
|
156
|
-
{selected ? 'Selected' : opt.tag}
|
|
157
|
-
</span>
|
|
158
|
-
</div>
|
|
159
|
-
<p className="text-sm text-muted-foreground px-0.5 leading-snug">{opt.desc}</p>
|
|
160
|
-
</button>
|
|
161
|
-
);
|
|
162
|
-
})}
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
|
|
166
|
-
{/* Footer */}
|
|
167
|
-
<div className="px-7 py-5 border-t border-border flex justify-end items-center gap-4">
|
|
168
|
-
<button
|
|
169
|
-
type="button"
|
|
170
|
-
onClick={() => setPage(2)}
|
|
171
|
-
className="px-4 py-2 rounded-lg border border-primary/35 hover:opacity-80 transition-opacity"
|
|
172
|
-
>
|
|
173
|
-
<TextShimmer className="text-sm font-medium" duration={2.5} spread={1.5}>
|
|
174
|
-
{'✨ Workspaces are coming 🎉 →'}
|
|
175
|
-
</TextShimmer>
|
|
176
|
-
</button>
|
|
177
|
-
<button
|
|
178
|
-
onClick={onDismiss}
|
|
179
|
-
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:opacity-90 transition-opacity"
|
|
180
|
-
>
|
|
181
|
-
Got it
|
|
182
|
-
</button>
|
|
183
|
-
</div>
|
|
184
|
-
</>
|
|
185
|
-
) : (
|
|
186
|
-
<>
|
|
187
|
-
{/* Header */}
|
|
188
|
-
<div className="p-7 border-b border-border">
|
|
189
|
-
<h3 className="font-semibold text-2xl mb-1.5">Workspaces are coming 🎉</h3>
|
|
190
|
-
<p className="text-sm text-muted-foreground">
|
|
191
|
-
A shared context workspace for specs, reviews, and decisions your agents can build
|
|
192
|
-
on. Join the waitlist.
|
|
193
|
-
</p>
|
|
194
|
-
</div>
|
|
195
|
-
|
|
196
|
-
{/* The landscape teaser image, scaled to fit the fixed dialog. */}
|
|
197
|
-
<div className="flex-1 min-h-0 p-6 flex items-center justify-center">
|
|
198
|
-
<img
|
|
199
|
-
src={workspacesImg}
|
|
200
|
-
alt="Plannotator Workspaces, a shared context workspace across your agents"
|
|
201
|
-
className="max-h-full max-w-full w-auto object-contain rounded-lg border border-border select-none"
|
|
202
|
-
draggable={false}
|
|
203
|
-
/>
|
|
204
|
-
</div>
|
|
173
|
+
{selected && <span className="block h-full w-full rounded-full bg-primary" />}
|
|
174
|
+
</span>
|
|
175
|
+
</span>
|
|
176
|
+
<span className="mt-0.5 block px-0.5 text-xs leading-snug text-muted-foreground sm:text-sm">
|
|
177
|
+
{option.desc}
|
|
178
|
+
</span>
|
|
179
|
+
</button>
|
|
180
|
+
);
|
|
181
|
+
})}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
205
184
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
href={WAITLIST_URL}
|
|
217
|
-
target="_blank"
|
|
218
|
-
rel="noopener noreferrer"
|
|
219
|
-
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:opacity-90 transition-opacity"
|
|
220
|
-
>
|
|
221
|
-
Join the waitlist
|
|
222
|
-
</a>
|
|
223
|
-
</div>
|
|
224
|
-
</>
|
|
225
|
-
)}
|
|
185
|
+
<footer className="border-t border-border px-4 py-3 sm:flex sm:justify-end sm:px-6 sm:py-4">
|
|
186
|
+
<button
|
|
187
|
+
ref={continueRef}
|
|
188
|
+
type="button"
|
|
189
|
+
onClick={onDismiss}
|
|
190
|
+
className="min-h-11 w-full rounded-lg bg-primary px-5 text-sm font-medium text-primary-foreground outline-none transition-opacity motion-reduce:transition-none hover:opacity-90 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-card sm:w-auto"
|
|
191
|
+
>
|
|
192
|
+
Continue
|
|
193
|
+
</button>
|
|
194
|
+
</footer>
|
|
226
195
|
</div>
|
|
227
196
|
</div>,
|
|
228
|
-
document.body
|
|
197
|
+
document.body,
|
|
229
198
|
);
|
|
230
199
|
};
|