@dfosco/storyboard-react 3.0.0 → 3.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.
@@ -13,3 +13,61 @@
13
13
  .loading p {
14
14
  margin: 0;
15
15
  }
16
+
17
+ .canvasScroll {
18
+ width: 100vw;
19
+ height: 100vh;
20
+ overflow: auto;
21
+ background-color: var(--bgColor-muted, #f6f8fa);
22
+ }
23
+
24
+ @media (prefers-color-scheme: dark) {
25
+ .canvasScroll {
26
+ background-color: var(--bgColor-muted, #161b22);
27
+ }
28
+ }
29
+
30
+ .canvasZoom {
31
+ min-width: 100%;
32
+ min-height: 100%;
33
+ }
34
+
35
+ .selected {
36
+ outline: 2px solid var(--bgColor-accent-emphasis, #2f81f7);
37
+ outline-offset: 2px;
38
+ border-radius: 4px;
39
+ }
40
+
41
+ .canvasTitle {
42
+ position: fixed;
43
+ top: 12px;
44
+ left: 16px;
45
+ z-index: 10;
46
+ }
47
+
48
+ .canvasTitleInput {
49
+ font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
50
+ font-size: 14px;
51
+ font-weight: 600;
52
+ color: var(--fgColor-muted, #656d76);
53
+ background: transparent;
54
+ border: 1px solid transparent;
55
+ border-radius: 6px;
56
+ padding: 4px 8px;
57
+ outline: none;
58
+ min-width: 80px;
59
+ max-width: 300px;
60
+ transition: border-color 150ms, background-color 150ms, color 150ms;
61
+ }
62
+
63
+ .canvasTitleInput:hover {
64
+ color: var(--fgColor-default, #1f2328);
65
+ border-color: var(--borderColor-default, #d1d9e0);
66
+ background: var(--bgColor-default, #ffffff);
67
+ }
68
+
69
+ .canvasTitleInput:focus {
70
+ color: var(--fgColor-default, #1f2328);
71
+ border-color: var(--bgColor-accent-emphasis, #2f81f7);
72
+ background: var(--bgColor-default, #ffffff);
73
+ }
@@ -0,0 +1,34 @@
1
+ import WidgetWrapper from './WidgetWrapper.jsx'
2
+ import { readProp, linkPreviewSchema } from './widgetProps.js'
3
+ import styles from './LinkPreview.module.css'
4
+
5
+ export default function LinkPreview({ props }) {
6
+ const url = readProp(props, 'url', linkPreviewSchema)
7
+ const title = readProp(props, 'title', linkPreviewSchema)
8
+
9
+ let hostname = ''
10
+ try {
11
+ hostname = new URL(url).hostname
12
+ } catch { /* invalid URL */ }
13
+
14
+ return (
15
+ <WidgetWrapper>
16
+ <div className={styles.card}>
17
+ <span className={styles.icon}>🔗</span>
18
+ <div className={styles.text}>
19
+ {title && <p className={styles.title}>{title}</p>}
20
+ <a
21
+ href={url}
22
+ target="_blank"
23
+ rel="noopener noreferrer"
24
+ className={styles.url}
25
+ onMouseDown={(e) => e.stopPropagation()}
26
+ onPointerDown={(e) => e.stopPropagation()}
27
+ >
28
+ {hostname || url}
29
+ </a>
30
+ </div>
31
+ </div>
32
+ </WidgetWrapper>
33
+ )
34
+ }
@@ -0,0 +1,51 @@
1
+ .card {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 12px;
5
+ padding: 14px 16px;
6
+ text-decoration: none;
7
+ color: inherit;
8
+ min-width: 240px;
9
+ transition: background 150ms;
10
+ }
11
+
12
+ .card:hover {
13
+ background: var(--bgColor-muted, #f6f8fa);
14
+ }
15
+
16
+ .icon {
17
+ font-size: 20px;
18
+ flex-shrink: 0;
19
+ }
20
+
21
+ .text {
22
+ overflow: hidden;
23
+ min-width: 0;
24
+ }
25
+
26
+ .title {
27
+ margin: 0;
28
+ font-size: 14px;
29
+ font-weight: 600;
30
+ line-height: 1.4;
31
+ color: var(--fgColor-default, #1f2328);
32
+ white-space: nowrap;
33
+ overflow: hidden;
34
+ text-overflow: ellipsis;
35
+ }
36
+
37
+ .url {
38
+ margin: 0;
39
+ font-size: 12px;
40
+ color: var(--fgColor-muted, #656d76);
41
+ white-space: nowrap;
42
+ overflow: hidden;
43
+ text-overflow: ellipsis;
44
+ text-decoration: none;
45
+ display: block;
46
+ }
47
+
48
+ .url:hover {
49
+ text-decoration: underline;
50
+ color: var(--fgColor-accent, #0969da);
51
+ }
@@ -1,4 +1,4 @@
1
- import { useState, useRef, useEffect } from 'react'
1
+ import { useState, useRef, useEffect, useCallback } from 'react'
2
2
  import WidgetWrapper from './WidgetWrapper.jsx'
3
3
  import { readProp, markdownSchema } from './widgetProps.js'
4
4
  import styles from './MarkdownBlock.module.css'
@@ -25,7 +25,7 @@ function renderMarkdown(text) {
25
25
  })
26
26
  }
27
27
 
28
- export default function MarkdownBlock({ props, onUpdate, onRemove }) {
28
+ export default function MarkdownBlock({ props, onUpdate }) {
29
29
  const content = readProp(props, 'content', markdownSchema)
30
30
  const width = readProp(props, 'width', markdownSchema)
31
31
  const [editing, setEditing] = useState(false)
@@ -33,6 +33,10 @@ export default function MarkdownBlock({ props, onUpdate, onRemove }) {
33
33
  const blockRef = useRef(null)
34
34
  const [editHeight, setEditHeight] = useState(null)
35
35
 
36
+ const handleContentChange = useCallback((e) => {
37
+ onUpdate?.({ content: e.target.value })
38
+ }, [onUpdate])
39
+
36
40
  useEffect(() => {
37
41
  if (editing) {
38
42
  // Capture the preview height before switching to editor
@@ -48,7 +52,7 @@ export default function MarkdownBlock({ props, onUpdate, onRemove }) {
48
52
  }, [editing, editHeight])
49
53
 
50
54
  return (
51
- <WidgetWrapper onRemove={onRemove}>
55
+ <WidgetWrapper>
52
56
  <div
53
57
  ref={blockRef}
54
58
  className={styles.block}
@@ -60,7 +64,7 @@ export default function MarkdownBlock({ props, onUpdate, onRemove }) {
60
64
  className={styles.editor}
61
65
  style={{ minHeight: editHeight ? editHeight - 2 : undefined }}
62
66
  value={content}
63
- onChange={(e) => onUpdate?.({ content: e.target.value })}
67
+ onChange={handleContentChange}
64
68
  onBlur={() => setEditing(false)}
65
69
  onMouseDown={(e) => e.stopPropagation()}
66
70
  onPointerDown={(e) => e.stopPropagation()}
@@ -1,33 +1,179 @@
1
+ import { useState, useRef, useEffect, useCallback } from 'react'
1
2
  import WidgetWrapper from './WidgetWrapper.jsx'
2
3
  import { readProp, prototypeEmbedSchema } from './widgetProps.js'
3
4
  import styles from './PrototypeEmbed.module.css'
4
5
 
5
- export default function PrototypeEmbed({ props, onRemove }) {
6
+ export default function PrototypeEmbed({ props, onUpdate }) {
6
7
  const src = readProp(props, 'src', prototypeEmbedSchema)
7
8
  const width = readProp(props, 'width', prototypeEmbedSchema)
8
9
  const height = readProp(props, 'height', prototypeEmbedSchema)
10
+ const zoom = readProp(props, 'zoom', prototypeEmbedSchema)
9
11
  const label = readProp(props, 'label', prototypeEmbedSchema) || src
10
12
 
11
- // Build the full iframe URL using the app's base path
12
13
  const basePath = (import.meta.env.BASE_URL || '/').replace(/\/$/, '')
13
- const iframeSrc = src ? `${basePath}${src}` : ''
14
+ const rawSrc = src ? `${basePath}${src}` : ''
15
+ const iframeSrc = rawSrc ? `${rawSrc}${rawSrc.includes('?') ? '&' : '?'}_sb_embed` : ''
16
+
17
+ const scale = zoom / 100
18
+
19
+ const [editing, setEditing] = useState(false)
20
+ const [interactive, setInteractive] = useState(false)
21
+ const inputRef = useRef(null)
22
+ const embedRef = useRef(null)
23
+
24
+ useEffect(() => {
25
+ if (editing && inputRef.current) {
26
+ inputRef.current.focus()
27
+ inputRef.current.select()
28
+ }
29
+ }, [editing])
30
+
31
+ // Exit interactive mode when clicking outside the embed
32
+ useEffect(() => {
33
+ if (!interactive) return
34
+ function handlePointerDown(e) {
35
+ if (embedRef.current && !embedRef.current.contains(e.target)) {
36
+ setInteractive(false)
37
+ }
38
+ }
39
+ document.addEventListener('pointerdown', handlePointerDown)
40
+ return () => document.removeEventListener('pointerdown', handlePointerDown)
41
+ }, [interactive])
42
+
43
+ const enterInteractive = useCallback(() => setInteractive(true), [])
44
+
45
+ function handleSubmit(e) {
46
+ e.preventDefault()
47
+ const value = inputRef.current?.value?.trim() || ''
48
+ onUpdate?.({ src: value })
49
+ setEditing(false)
50
+ }
14
51
 
15
52
  return (
16
- <WidgetWrapper onRemove={onRemove}>
17
- <div className={styles.embed} style={{ width, height }}>
18
- {iframeSrc ? (
19
- <iframe
20
- src={iframeSrc}
21
- className={styles.iframe}
22
- title={label || 'Prototype embed'}
23
- sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
24
- />
53
+ <WidgetWrapper>
54
+ <div
55
+ ref={embedRef}
56
+ className={styles.embed}
57
+ style={{ width, height }}
58
+ >
59
+ {editing ? (
60
+ <form
61
+ className={styles.urlForm}
62
+ onSubmit={handleSubmit}
63
+ onMouseDown={(e) => e.stopPropagation()}
64
+ onPointerDown={(e) => e.stopPropagation()}
65
+ >
66
+ <label className={styles.urlLabel}>Prototype URL path</label>
67
+ <input
68
+ ref={inputRef}
69
+ className={styles.urlInput}
70
+ type="text"
71
+ defaultValue={src}
72
+ placeholder="/MyPrototype/page"
73
+ onKeyDown={(e) => { if (e.key === 'Escape') setEditing(false) }}
74
+ />
75
+ <div className={styles.urlActions}>
76
+ <button type="submit" className={styles.urlSave}>Save</button>
77
+ <button type="button" className={styles.urlCancel} onClick={() => setEditing(false)}>Cancel</button>
78
+ </div>
79
+ </form>
80
+ ) : iframeSrc ? (
81
+ <>
82
+ <div className={styles.iframeContainer}>
83
+ <iframe
84
+ src={iframeSrc}
85
+ className={styles.iframe}
86
+ style={{
87
+ width: width / scale,
88
+ height: height / scale,
89
+ transform: `scale(${scale})`,
90
+ transformOrigin: '0 0',
91
+ }}
92
+ title={label || 'Prototype embed'}
93
+ sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
94
+ />
95
+ </div>
96
+ {!interactive && (
97
+ <div
98
+ className={styles.dragOverlay}
99
+ onDoubleClick={enterInteractive}
100
+ />
101
+ )}
102
+ </>
25
103
  ) : (
26
- <div className={styles.empty}>
27
- <p>No prototype URL set</p>
104
+ <div
105
+ className={styles.empty}
106
+ onDoubleClick={() => setEditing(true)}
107
+ role="button"
108
+ tabIndex={0}
109
+ onKeyDown={(e) => { if (e.key === 'Enter') setEditing(true) }}
110
+ >
111
+ <p>Double-click to set prototype URL</p>
112
+ </div>
113
+ )}
114
+ {iframeSrc && !editing && (
115
+ <button
116
+ className={styles.editBtn}
117
+ onClick={(e) => { e.stopPropagation(); setEditing(true) }}
118
+ onMouseDown={(e) => e.stopPropagation()}
119
+ onPointerDown={(e) => e.stopPropagation()}
120
+ title="Edit URL"
121
+ aria-label="Edit prototype URL"
122
+ >
123
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z"/></svg>
124
+ </button>
125
+ )}
126
+ {iframeSrc && !editing && (
127
+ <div
128
+ className={styles.zoomBar}
129
+ onMouseDown={(e) => e.stopPropagation()}
130
+ onPointerDown={(e) => e.stopPropagation()}
131
+ >
132
+ <button
133
+ className={styles.zoomBtn}
134
+ onClick={() => {
135
+ const step = zoom <= 75 ? 5 : 25
136
+ onUpdate?.({ zoom: Math.max(25, zoom - step) })
137
+ }}
138
+ disabled={zoom <= 25}
139
+ aria-label="Zoom out"
140
+ >−</button>
141
+ <span className={styles.zoomLabel}>{zoom}%</span>
142
+ <button
143
+ className={styles.zoomBtn}
144
+ onClick={() => {
145
+ const step = zoom < 75 ? 5 : 25
146
+ onUpdate?.({ zoom: Math.min(200, zoom + step) })
147
+ }}
148
+ disabled={zoom >= 200}
149
+ aria-label="Zoom in"
150
+ >+</button>
28
151
  </div>
29
152
  )}
30
153
  </div>
154
+ <div
155
+ className={styles.resizeHandle}
156
+ onMouseDown={(e) => {
157
+ e.stopPropagation()
158
+ e.preventDefault()
159
+ const startX = e.clientX
160
+ const startY = e.clientY
161
+ const startW = width
162
+ const startH = height
163
+ function onMove(ev) {
164
+ const newW = Math.max(200, startW + ev.clientX - startX)
165
+ const newH = Math.max(150, startH + ev.clientY - startY)
166
+ onUpdate?.({ width: newW, height: newH })
167
+ }
168
+ function onUp() {
169
+ document.removeEventListener('mousemove', onMove)
170
+ document.removeEventListener('mouseup', onUp)
171
+ }
172
+ document.addEventListener('mousemove', onMove)
173
+ document.addEventListener('mouseup', onUp)
174
+ }}
175
+ onPointerDown={(e) => e.stopPropagation()}
176
+ />
31
177
  </WidgetWrapper>
32
178
  )
33
179
  }
@@ -4,13 +4,24 @@
4
4
  background: var(--bgColor-default, #ffffff);
5
5
  }
6
6
 
7
- .iframe {
7
+ .iframeContainer {
8
8
  width: 100%;
9
9
  height: 100%;
10
+ overflow: hidden;
11
+ }
12
+
13
+ .iframe {
10
14
  border: none;
11
15
  display: block;
12
16
  }
13
17
 
18
+ .dragOverlay {
19
+ position: absolute;
20
+ inset: 0;
21
+ z-index: 1;
22
+ cursor: grab;
23
+ }
24
+
14
25
  .empty {
15
26
  display: flex;
16
27
  align-items: center;
@@ -19,8 +30,213 @@
19
30
  color: var(--fgColor-muted, #656d76);
20
31
  font-size: 14px;
21
32
  font-style: italic;
33
+ cursor: pointer;
22
34
  }
23
35
 
24
36
  .empty p {
25
37
  margin: 0;
26
38
  }
39
+
40
+ .editBtn {
41
+ all: unset;
42
+ cursor: pointer;
43
+ position: absolute;
44
+ top: 8px;
45
+ right: 8px;
46
+ width: 28px;
47
+ height: 28px;
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ border-radius: 6px;
52
+ background: rgba(255, 255, 255, 0.92);
53
+ backdrop-filter: blur(12px);
54
+ -webkit-backdrop-filter: blur(12px);
55
+ border: 1px solid rgba(0, 0, 0, 0.12);
56
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
57
+ font-size: 14px;
58
+ opacity: 0;
59
+ transition: opacity 150ms;
60
+ z-index: 2;
61
+ }
62
+
63
+ .embed:hover .editBtn {
64
+ opacity: 1;
65
+ }
66
+
67
+ .editBtn:hover {
68
+ background: rgba(255, 255, 255, 0.98);
69
+ }
70
+
71
+ @media (prefers-color-scheme: dark) {
72
+ .editBtn {
73
+ background: rgba(22, 27, 34, 0.88);
74
+ border-color: rgba(255, 255, 255, 0.1);
75
+ }
76
+
77
+ .editBtn:hover {
78
+ background: rgba(30, 37, 46, 0.95);
79
+ }
80
+ }
81
+
82
+ .urlForm {
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: 8px;
86
+ padding: 24px;
87
+ height: 100%;
88
+ box-sizing: border-box;
89
+ justify-content: center;
90
+ }
91
+
92
+ .urlLabel {
93
+ font-size: 12px;
94
+ font-weight: 600;
95
+ color: var(--fgColor-muted, #656d76);
96
+ text-transform: uppercase;
97
+ letter-spacing: 0.5px;
98
+ }
99
+
100
+ .urlInput {
101
+ all: unset;
102
+ padding: 8px 10px;
103
+ font-size: 14px;
104
+ font-family: ui-monospace, SFMono-Regular, monospace;
105
+ border: 1px solid var(--borderColor-default, #d0d7de);
106
+ border-radius: 6px;
107
+ background: var(--bgColor-default, #ffffff);
108
+ color: var(--fgColor-default, #1f2328);
109
+ }
110
+
111
+ .urlInput:focus {
112
+ border-color: var(--bgColor-accent-emphasis, #2f81f7);
113
+ box-shadow: 0 0 0 2px rgba(47, 129, 247, 0.3);
114
+ }
115
+
116
+ .urlActions {
117
+ display: flex;
118
+ gap: 8px;
119
+ }
120
+
121
+ .urlSave,
122
+ .urlCancel {
123
+ all: unset;
124
+ cursor: pointer;
125
+ padding: 6px 14px;
126
+ font-size: 13px;
127
+ font-weight: 500;
128
+ border-radius: 6px;
129
+ text-align: center;
130
+ }
131
+
132
+ .urlSave {
133
+ background: var(--bgColor-accent-emphasis, #2f81f7);
134
+ color: var(--fgColor-onEmphasis, #ffffff);
135
+ }
136
+
137
+ .urlSave:hover {
138
+ background: var(--bgColor-accent-emphasis, #388bfd);
139
+ }
140
+
141
+ .urlCancel {
142
+ color: var(--fgColor-muted, #656d76);
143
+ }
144
+
145
+ .urlCancel:hover {
146
+ color: var(--fgColor-default, #1f2328);
147
+ background: var(--bgColor-muted, #f6f8fa);
148
+ }
149
+
150
+ .resizeHandle {
151
+ position: absolute;
152
+ bottom: 0;
153
+ right: 0;
154
+ width: 16px;
155
+ height: 16px;
156
+ cursor: nwse-resize;
157
+ background: linear-gradient(
158
+ 135deg,
159
+ transparent 40%,
160
+ var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 40%,
161
+ var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 50%,
162
+ transparent 50%,
163
+ transparent 65%,
164
+ var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 65%,
165
+ var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 75%,
166
+ transparent 75%
167
+ );
168
+ opacity: 0;
169
+ transition: opacity 150ms;
170
+ z-index: 2;
171
+ }
172
+
173
+ .embed:hover ~ .resizeHandle,
174
+ .resizeHandle:hover {
175
+ opacity: 1;
176
+ }
177
+
178
+ .zoomBar {
179
+ position: absolute;
180
+ bottom: 8px;
181
+ left: 50%;
182
+ transform: translateX(-50%);
183
+ display: flex;
184
+ align-items: center;
185
+ border-radius: 10px;
186
+ border: 1.5px solid var(--trigger-border, var(--borderColor-muted, #d0d7de));
187
+ background: var(--trigger-bg, var(--bgColor-muted, #f6f8fa));
188
+ opacity: 0;
189
+ transition: opacity 150ms;
190
+ z-index: 3;
191
+ }
192
+
193
+ .embed:hover .zoomBar {
194
+ opacity: 1;
195
+ }
196
+
197
+ .zoomBtn {
198
+ all: unset;
199
+ cursor: pointer;
200
+ width: 36px;
201
+ height: 32px;
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ font-size: 16px;
206
+ font-weight: 600;
207
+ color: var(--trigger-text, var(--fgColor-default, #1f2328));
208
+ transition: background 120ms;
209
+ }
210
+
211
+ .zoomBtn:first-child {
212
+ border-radius: 7px 0 0 7px;
213
+ }
214
+
215
+ .zoomBtn:last-child {
216
+ border-radius: 0 7px 7px 0;
217
+ }
218
+
219
+ .zoomBtn:hover:not(:disabled) {
220
+ background: var(--trigger-bg-hover, var(--bgColor-neutral-muted, #eaeef2));
221
+ }
222
+
223
+ .zoomBtn:disabled {
224
+ opacity: 0.3;
225
+ cursor: default;
226
+ }
227
+
228
+ .zoomLabel {
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ min-width: 48px;
233
+ height: 32px;
234
+ padding: 0 4px;
235
+ font-size: 11px;
236
+ font-weight: 600;
237
+ font-variant-numeric: tabular-nums;
238
+ color: var(--trigger-text, var(--fgColor-default, #1f2328));
239
+ border-left: 1.5px solid var(--trigger-border, var(--borderColor-muted, #d0d7de));
240
+ border-right: 1.5px solid var(--trigger-border, var(--borderColor-muted, #d0d7de));
241
+ user-select: none;
242
+ }
@@ -11,7 +11,7 @@ const COLORS = {
11
11
  orange: { bg: '#fff1e5', border: '#d18616', dot: '#e8a844' },
12
12
  }
13
13
 
14
- export default function StickyNote({ props, onUpdate, onRemove }) {
14
+ export default function StickyNote({ props, onUpdate }) {
15
15
  const text = readProp(props, 'text', stickyNoteSchema)
16
16
  const color = readProp(props, 'color', stickyNoteSchema)
17
17
  const palette = COLORS[color] ?? COLORS.yellow
@@ -39,15 +39,17 @@ export default function StickyNote({ props, onUpdate, onRemove }) {
39
39
  className={styles.sticky}
40
40
  style={{ '--sticky-bg': palette.bg, '--sticky-border': palette.border }}
41
41
  >
42
- {onRemove && (
43
- <button
44
- className={styles.removeBtn}
45
- onClick={(e) => { e.stopPropagation(); onRemove() }}
46
- title="Remove"
47
- aria-label="Remove sticky note"
48
- >×</button>
49
- )}
50
- {editing ? (
42
+ <p
43
+ className={styles.text}
44
+ style={editing ? { visibility: 'hidden' } : undefined}
45
+ onDoubleClick={() => setEditing(true)}
46
+ role="button"
47
+ tabIndex={0}
48
+ onKeyDown={(e) => { if (e.key === 'Enter') setEditing(true) }}
49
+ >
50
+ {text || 'Double-click to edit…'}
51
+ </p>
52
+ {editing && (
51
53
  <textarea
52
54
  ref={textareaRef}
53
55
  className={styles.textarea}
@@ -61,16 +63,6 @@ export default function StickyNote({ props, onUpdate, onRemove }) {
61
63
  }}
62
64
  placeholder="Type here…"
63
65
  />
64
- ) : (
65
- <p
66
- className={styles.text}
67
- onDoubleClick={() => setEditing(true)}
68
- role="button"
69
- tabIndex={0}
70
- onKeyDown={(e) => { if (e.key === 'Enter') setEditing(true) }}
71
- >
72
- {text || 'Double-click to edit…'}
73
- </p>
74
66
  )}
75
67
  </article>
76
68