@puredesktop/platform-editor 1.0.0-beta.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 (45) hide show
  1. package/package.json +63 -0
  2. package/src/CollapsePanel.tsx +35 -0
  3. package/src/DocumentDiffView.tsx +130 -0
  4. package/src/DocumentEditor.test.ts +118 -0
  5. package/src/DocumentEditor.tsx +2631 -0
  6. package/src/alignedLineDiff.test.ts +52 -0
  7. package/src/alignedLineDiff.ts +67 -0
  8. package/src/collectionAssetSrc.ts +2 -0
  9. package/src/commentUtils.test.ts +350 -0
  10. package/src/commentUtils.ts +546 -0
  11. package/src/constants/toolKeys.ts +14 -0
  12. package/src/contentFormat.test.ts +27 -0
  13. package/src/contentFormat.ts +18 -0
  14. package/src/editorExtensions.ts +155 -0
  15. package/src/extensions/appliedChangeMark.ts +115 -0
  16. package/src/extensions/autoReviewPrompts.test.ts +529 -0
  17. package/src/extensions/autoReviewPrompts.ts +1442 -0
  18. package/src/extensions/collectionImage.ts +26 -0
  19. package/src/extensions/collectionImagePaste.ts +215 -0
  20. package/src/extensions/commentMark.ts +223 -0
  21. package/src/extensions/documentAssetIdentity.ts +54 -0
  22. package/src/extensions/figure.ts +92 -0
  23. package/src/extensions/footnote.ts +186 -0
  24. package/src/extensions/indexMarker.ts +88 -0
  25. package/src/extensions/mathEditing.ts +239 -0
  26. package/src/extensions/mermaidBlock.tsx +297 -0
  27. package/src/extensions/slashCommands.test.ts +170 -0
  28. package/src/extensions/slashCommands.ts +746 -0
  29. package/src/extensions/smartTypography.test.ts +74 -0
  30. package/src/extensions/smartTypography.ts +120 -0
  31. package/src/index.ts +137 -0
  32. package/src/insertCollectionImage.test.ts +60 -0
  33. package/src/insertCollectionImage.ts +63 -0
  34. package/src/insertInlineAssetKind.test.ts +67 -0
  35. package/src/insertInlineAssetKind.ts +49 -0
  36. package/src/mermaidPreview.test.ts +54 -0
  37. package/src/mermaidPreview.ts +115 -0
  38. package/src/styled.ts +676 -0
  39. package/src/toolbar/ToolBtn.tsx +63 -0
  40. package/src/toolbar/Toolbar.test.tsx +325 -0
  41. package/src/toolbar/Toolbar.tsx +624 -0
  42. package/src/toolbar/groups/HeadingGroup.tsx +153 -0
  43. package/src/toolbar/overlayTypes.ts +28 -0
  44. package/src/useEditorExtensions.ts +22 -0
  45. package/src/utils/markdownUtils.ts +331 -0
@@ -0,0 +1,624 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { useCurrentEditor } from '@tiptap/react'
3
+ import { styled } from 'styled-components'
4
+ import {
5
+ BlockOutlined,
6
+ BoldOutlined,
7
+ CheckSquareOutlined,
8
+ CodeOutlined,
9
+ FileTextOutlined,
10
+ ItalicOutlined,
11
+ LinkOutlined,
12
+ MoreOutlined,
13
+ OrderedListOutlined,
14
+ PartitionOutlined,
15
+ RedoOutlined,
16
+ TableOutlined,
17
+ TagOutlined,
18
+ UndoOutlined,
19
+ UnorderedListOutlined,
20
+ } from '@ant-design/icons'
21
+ import { Divider, ToolbarRow, ToolBtn } from './ToolBtn.js'
22
+ import type { EditorToolbarOverlays } from './overlayTypes.js'
23
+ import { HeadingGroup } from './groups/HeadingGroup.js'
24
+ import { CollapsePanel } from '../CollapsePanel.js'
25
+ import { TOOL_KEYS } from '../constants/toolKeys.js'
26
+ import {
27
+ COMMENT_TYPES,
28
+ COMMENT_TYPE_LABELS,
29
+ type CommentType,
30
+ } from '../commentUtils.js'
31
+ import { copyAutoReviewPromptsToClipboard } from '../extensions/autoReviewPrompts.js'
32
+
33
+ export function Toolbar({
34
+ onAddComment,
35
+ overlays,
36
+ advancedTools = 'full',
37
+ }: {
38
+ onAddComment?: (payload: {
39
+ commentText: string
40
+ commentType: CommentType
41
+ from: number
42
+ to: number
43
+ }) => void
44
+ overlays?: EditorToolbarOverlays
45
+ advancedTools?: 'full' | 'formatting'
46
+ }): React.ReactElement | null {
47
+ const { editor } = useCurrentEditor()
48
+
49
+ const [showLinkInput, setShowLinkInput] = useState(false)
50
+ const [linkUrl, setLinkUrl] = useState('')
51
+ const [showCommentInput, setShowCommentInput] = useState(false)
52
+ const [commentText, setCommentText] = useState('')
53
+ const [commentType, setCommentType] = useState<CommentType>('general')
54
+ const [commentSelection, setCommentSelection] = useState<{
55
+ from: number
56
+ to: number
57
+ } | null>(null)
58
+ const [advancedOpen, setAdvancedOpen] = useState(false)
59
+ const [hasSelection, setHasSelection] = useState(false)
60
+ const advancedRef = useRef<HTMLDivElement>(null)
61
+ const lastSelectionRef = useRef<{ from: number; to: number } | null>(null)
62
+
63
+ const isLink = !!editor?.isActive(TOOL_KEYS.LINK)
64
+ const isBullet = !!editor?.isActive(TOOL_KEYS.BULLET_LIST)
65
+ const isOrdered = !!editor?.isActive(TOOL_KEYS.ORDERED_LIST)
66
+ const isCode = !!editor?.isActive(TOOL_KEYS.CODE)
67
+ const isBold = !!editor?.isActive(TOOL_KEYS.BOLD)
68
+ const isItalic = !!editor?.isActive(TOOL_KEYS.ITALIC)
69
+ const isUnderline = !!editor?.isActive(TOOL_KEYS.UNDERLINE)
70
+ const canUndo = !!editor?.can().undo()
71
+ const canRedo = !!editor?.can().redo()
72
+ const isTask = !!editor?.isActive(TOOL_KEYS.TASK_LIST)
73
+ const isBlockquote = !!editor?.isActive(TOOL_KEYS.BLOCKQUOTE)
74
+ const isCodeBlock = !!editor?.isActive(TOOL_KEYS.CODE_BLOCK)
75
+ const isSubscript = !!editor?.isActive('subscript')
76
+ const isSuperscript = !!editor?.isActive('superscript')
77
+ const indexActive = !!editor?.isActive(TOOL_KEYS.INDEX_MARKER)
78
+ const inTable = !!editor?.isActive(TOOL_KEYS.TABLE)
79
+ const hasAutoReviewPrompts = Boolean(
80
+ editor?.extensionManager.extensions.some(
81
+ extension => extension.name === 'autoReviewPrompts',
82
+ ),
83
+ )
84
+
85
+ useEffect(() => {
86
+ if (!editor) return
87
+ const syncSelection = (): void => {
88
+ const { from, to } = editor.state.selection
89
+ const nextHasSelection = from !== to
90
+ lastSelectionRef.current = nextHasSelection ? { from, to } : null
91
+ setHasSelection(nextHasSelection)
92
+ }
93
+ syncSelection()
94
+ editor.on('selectionUpdate', syncSelection)
95
+ editor.on('update', syncSelection)
96
+ return () => {
97
+ editor.off('selectionUpdate', syncSelection)
98
+ editor.off('update', syncSelection)
99
+ }
100
+ }, [editor])
101
+
102
+ useEffect(() => {
103
+ if (!advancedOpen) return
104
+ const onDown = (e: MouseEvent) => {
105
+ if (!advancedRef.current?.contains(e.target as Node))
106
+ setAdvancedOpen(false)
107
+ }
108
+ document.addEventListener('mousedown', onDown)
109
+ return () => document.removeEventListener('mousedown', onDown)
110
+ }, [advancedOpen])
111
+
112
+ const handleLinkClick = () => {
113
+ if (!editor) return
114
+ if (isLink) {
115
+ editor.chain().focus().unsetLink().run()
116
+ return
117
+ }
118
+ const { href = '' } = editor.getAttributes('link')
119
+ setLinkUrl(href)
120
+ setShowLinkInput(true)
121
+ }
122
+
123
+ const confirmLink = () => {
124
+ if (!editor) return
125
+ const trimmed = linkUrl.trim()
126
+ if (trimmed) editor.chain().focus().setLink({ href: trimmed }).run()
127
+ else editor.chain().focus().unsetLink().run()
128
+ setShowLinkInput(false)
129
+ setLinkUrl('')
130
+ }
131
+
132
+ const cancelLink = () => {
133
+ setShowLinkInput(false)
134
+ setLinkUrl('')
135
+ }
136
+
137
+ const openCommentInput = () => {
138
+ const selection = lastSelectionRef.current
139
+ if (!selection) return
140
+ setCommentSelection(selection)
141
+ setShowCommentInput(true)
142
+ }
143
+
144
+ const cancelComment = () => {
145
+ setShowCommentInput(false)
146
+ setCommentText('')
147
+ setCommentSelection(null)
148
+ }
149
+
150
+ const confirmComment = () => {
151
+ const selection = commentSelection ?? lastSelectionRef.current
152
+ const trimmed = commentText.trim()
153
+ if (!selection || !trimmed || !onAddComment) return
154
+ onAddComment({
155
+ commentText: trimmed,
156
+ commentType,
157
+ from: selection.from,
158
+ to: selection.to,
159
+ })
160
+ setShowCommentInput(false)
161
+ setCommentText('')
162
+ setCommentSelection(null)
163
+ }
164
+
165
+ const closeAdvanced = (fn: () => unknown) => {
166
+ setAdvancedOpen(false)
167
+ void fn()
168
+ }
169
+
170
+ const insertInlineMath = () => {
171
+ if (!editor) return
172
+ const latex = window.prompt('Inline LaTeX', 'E = mc^2')?.trim()
173
+ if (!latex) return
174
+ const commands = editor.chain().focus() as unknown as {
175
+ insertInlineMath?: (options: { latex: string }) => { run: () => void }
176
+ }
177
+ commands.insertInlineMath?.({ latex })?.run()
178
+ }
179
+
180
+ const insertBlockMath = () => {
181
+ if (!editor) return
182
+ const latex = window.prompt('Block LaTeX', '\\\\frac{a}{b}')?.trim()
183
+ if (!latex) return
184
+ const commands = editor.chain().focus() as unknown as {
185
+ insertBlockMath?: (options: { latex: string }) => { run: () => void }
186
+ }
187
+ commands.insertBlockMath?.({ latex })?.run()
188
+ }
189
+
190
+ const insertMermaidBlock = () => {
191
+ if (!editor) return
192
+ editor.chain().focus().insertMermaidBlock().run()
193
+ }
194
+
195
+ const addComment = () => {
196
+ openCommentInput()
197
+ }
198
+
199
+ const prepareAutoReviewPrompts = async () => {
200
+ if (!editor) return
201
+ try {
202
+ const result = await copyAutoReviewPromptsToClipboard(editor)
203
+ if (!result.paragraphCount) {
204
+ window.alert('No paragraph text found for editorial review.')
205
+ return
206
+ }
207
+ window.alert(
208
+ `Copied editorial review prompts for ${
209
+ result.paragraphCount
210
+ } paragraph${result.paragraphCount === 1 ? '' : 's'}.`,
211
+ )
212
+ } catch {
213
+ window.alert('Could not copy auto-review prompts to the clipboard.')
214
+ }
215
+ }
216
+
217
+ const indentList = () => {
218
+ const commands = editor?.chain().focus() as unknown as {
219
+ sinkListItem?: (type: string) => { run: () => void }
220
+ }
221
+ const action = commands.sinkListItem?.('listItem')
222
+ action?.run()
223
+ }
224
+
225
+ if (!editor) return null
226
+
227
+ const LinkPrompt = overlays?.LinkPrompt
228
+ const CommentPrompt = overlays?.CommentPrompt
229
+ const showFullAdvancedTools = advancedTools === 'full'
230
+
231
+ return (
232
+ <>
233
+ <ToolbarRow>
234
+ <ToolBtn
235
+ disabled={!canUndo}
236
+ onClick={() => editor.chain().focus().undo().run()}
237
+ title="Undo"
238
+ >
239
+ <UndoOutlined />
240
+ </ToolBtn>
241
+ <ToolBtn
242
+ disabled={!canRedo}
243
+ onClick={() => editor.chain().focus().redo().run()}
244
+ title="Redo"
245
+ >
246
+ <RedoOutlined />
247
+ </ToolBtn>
248
+ <Divider />
249
+ <HeadingGroup />
250
+ <Divider />
251
+ <ToolBtn
252
+ $active={!!isBlockquote}
253
+ onClick={() => editor.chain().focus().toggleBlockquote().run()}
254
+ title="Blockquote"
255
+ >
256
+ <span aria-hidden="true">“</span>
257
+ </ToolBtn>
258
+ <ToolBtn
259
+ $active={!!isOrdered}
260
+ onClick={() => editor.chain().focus().toggleOrderedList().run()}
261
+ title="Ordered list"
262
+ >
263
+ <OrderedListOutlined />
264
+ </ToolBtn>
265
+ <ToolBtn
266
+ $active={!!isBullet}
267
+ onClick={() => editor.chain().focus().toggleBulletList().run()}
268
+ title="Bullet list"
269
+ >
270
+ <UnorderedListOutlined />
271
+ </ToolBtn>
272
+ <ToolBtn
273
+ onClick={indentList}
274
+ title="Indent list"
275
+ disabled={!isBullet && !isOrdered}
276
+ >
277
+ <span aria-hidden="true">⇥</span>
278
+ </ToolBtn>
279
+ <Divider />
280
+ {onAddComment ? (
281
+ <ToolBtn
282
+ onClick={addComment}
283
+ title="Add comment to selection"
284
+ disabled={!hasSelection}
285
+ >
286
+ <TagOutlined />
287
+ </ToolBtn>
288
+ ) : null}
289
+ <ToolBtn
290
+ $active={!!isBold}
291
+ onClick={() => editor.chain().focus().toggleBold().run()}
292
+ title="Bold"
293
+ >
294
+ <BoldOutlined />
295
+ </ToolBtn>
296
+ <ToolBtn
297
+ $active={!!isItalic}
298
+ onClick={() => editor.chain().focus().toggleItalic().run()}
299
+ title="Italic"
300
+ >
301
+ <ItalicOutlined />
302
+ </ToolBtn>
303
+ <ToolBtn
304
+ $active={!!isCode}
305
+ onClick={() => editor.chain().focus().toggleCode().run()}
306
+ title="Inline code"
307
+ >
308
+ <CodeOutlined />
309
+ </ToolBtn>
310
+ <ToolBtn $active={!!isLink} onClick={handleLinkClick} title="Link">
311
+ <LinkOutlined />
312
+ </ToolBtn>
313
+ <ToolBtn
314
+ $active={!!isUnderline}
315
+ onClick={() => editor.chain().focus().toggleUnderline().run()}
316
+ title="Underline"
317
+ >
318
+ <UnderlineGlyph aria-hidden="true">U</UnderlineGlyph>
319
+ </ToolBtn>
320
+ <Divider />
321
+ <AdvancedWrapper ref={advancedRef}>
322
+ <ToolBtn
323
+ $active={!!advancedOpen}
324
+ onClick={() => setAdvancedOpen(open => !open)}
325
+ title="More tools"
326
+ >
327
+ <MoreOutlined />
328
+ </ToolBtn>
329
+ <AdvancedPanel open={advancedOpen}>
330
+ <AdvancedMenu>
331
+ <AdvancedItem
332
+ type="button"
333
+ onClick={() =>
334
+ closeAdvanced(() =>
335
+ editor.chain().focus().toggleSubscript().run(),
336
+ )
337
+ }
338
+ >
339
+ <span aria-hidden="true">x₂</span>
340
+ <span>{isSubscript ? 'Remove subscript' : 'Subscript'}</span>
341
+ </AdvancedItem>
342
+ <AdvancedItem
343
+ type="button"
344
+ onClick={() =>
345
+ closeAdvanced(() =>
346
+ editor.chain().focus().toggleSuperscript().run(),
347
+ )
348
+ }
349
+ >
350
+ <span aria-hidden="true">x²</span>
351
+ <span>
352
+ {isSuperscript ? 'Remove superscript' : 'Superscript'}
353
+ </span>
354
+ </AdvancedItem>
355
+ {showFullAdvancedTools ? (
356
+ <>
357
+ <AdvancedItem
358
+ type="button"
359
+ onClick={() => closeAdvanced(insertInlineMath)}
360
+ >
361
+ <span aria-hidden="true">ƒx</span>
362
+ <span>Insert inline equation</span>
363
+ </AdvancedItem>
364
+ <AdvancedItem
365
+ type="button"
366
+ onClick={() => closeAdvanced(insertBlockMath)}
367
+ >
368
+ <span aria-hidden="true">∑</span>
369
+ <span>Insert display equation</span>
370
+ </AdvancedItem>
371
+ <AdvancedItem
372
+ type="button"
373
+ onClick={() => closeAdvanced(insertMermaidBlock)}
374
+ >
375
+ <PartitionOutlined />
376
+ <span>Insert Mermaid diagram</span>
377
+ </AdvancedItem>
378
+ <AdvancedItem
379
+ type="button"
380
+ onClick={() =>
381
+ closeAdvanced(() =>
382
+ editor.chain().focus().toggleTaskList().run(),
383
+ )
384
+ }
385
+ >
386
+ <CheckSquareOutlined />
387
+ <span>{isTask ? 'Remove task list' : 'Task list'}</span>
388
+ </AdvancedItem>
389
+ </>
390
+ ) : null}
391
+ {onAddComment ? (
392
+ <AdvancedItem
393
+ type="button"
394
+ onClick={() => closeAdvanced(addComment)}
395
+ disabled={!hasSelection}
396
+ >
397
+ <TagOutlined />
398
+ <span>Add comment to selection</span>
399
+ </AdvancedItem>
400
+ ) : null}
401
+ {showFullAdvancedTools && hasAutoReviewPrompts ? (
402
+ <AdvancedItem
403
+ type="button"
404
+ onClick={() => closeAdvanced(prepareAutoReviewPrompts)}
405
+ >
406
+ <FileTextOutlined />
407
+ <span>Copy editorial review prompts</span>
408
+ </AdvancedItem>
409
+ ) : null}
410
+ {showFullAdvancedTools ? (
411
+ <>
412
+ <AdvancedItem
413
+ type="button"
414
+ onClick={() =>
415
+ closeAdvanced(() =>
416
+ editor.chain().focus().toggleBlockquote().run(),
417
+ )
418
+ }
419
+ >
420
+ <span>“</span>
421
+ <span>
422
+ {isBlockquote ? 'Remove blockquote' : 'Blockquote'}
423
+ </span>
424
+ </AdvancedItem>
425
+ <AdvancedItem
426
+ type="button"
427
+ onClick={() =>
428
+ closeAdvanced(() =>
429
+ editor.chain().focus().toggleCodeBlock().run(),
430
+ )
431
+ }
432
+ >
433
+ <CodeOutlined />
434
+ <span>
435
+ {isCodeBlock ? 'Remove code block' : 'Code block'}
436
+ </span>
437
+ </AdvancedItem>
438
+ <AdvancedItem
439
+ type="button"
440
+ onClick={() =>
441
+ closeAdvanced(() =>
442
+ editor
443
+ .chain()
444
+ .focus()
445
+ .insertTable({
446
+ rows: 3,
447
+ cols: 3,
448
+ withHeaderRow: true,
449
+ })
450
+ .run(),
451
+ )
452
+ }
453
+ >
454
+ <TableOutlined />
455
+ <span>
456
+ {inTable ? 'Insert another table' : 'Insert table'}
457
+ </span>
458
+ </AdvancedItem>
459
+ <AdvancedItem
460
+ type="button"
461
+ onClick={() =>
462
+ closeAdvanced(() =>
463
+ editor.chain().focus().insertFootnote().run(),
464
+ )
465
+ }
466
+ >
467
+ <FileTextOutlined />
468
+ <span>Insert footnote</span>
469
+ </AdvancedItem>
470
+ </>
471
+ ) : null}
472
+ <AdvancedItem
473
+ type="button"
474
+ disabled={!indexActive && !hasSelection}
475
+ onClick={() =>
476
+ closeAdvanced(() =>
477
+ editor.chain().focus().toggleIndexMarker().run(),
478
+ )
479
+ }
480
+ >
481
+ <TagOutlined />
482
+ <span>
483
+ {indexActive ? 'Remove index marker' : 'Mark for index'}
484
+ </span>
485
+ </AdvancedItem>
486
+ {inTable ? (
487
+ <>
488
+ <AdvancedItem
489
+ type="button"
490
+ onClick={() =>
491
+ closeAdvanced(() =>
492
+ editor.chain().focus().addRowAfter().run(),
493
+ )
494
+ }
495
+ >
496
+ <TableOutlined />
497
+ <span>Add table row</span>
498
+ </AdvancedItem>
499
+ <AdvancedItem
500
+ type="button"
501
+ onClick={() =>
502
+ closeAdvanced(() =>
503
+ editor.chain().focus().addColumnAfter().run(),
504
+ )
505
+ }
506
+ >
507
+ <TableOutlined />
508
+ <span>Add table column</span>
509
+ </AdvancedItem>
510
+ <AdvancedItem
511
+ type="button"
512
+ onClick={() =>
513
+ closeAdvanced(() =>
514
+ editor.chain().focus().deleteTable().run(),
515
+ )
516
+ }
517
+ >
518
+ <BlockOutlined />
519
+ <span>Delete table</span>
520
+ </AdvancedItem>
521
+ </>
522
+ ) : null}
523
+ </AdvancedMenu>
524
+ </AdvancedPanel>
525
+ </AdvancedWrapper>
526
+ </ToolbarRow>
527
+
528
+ {LinkPrompt ? (
529
+ <LinkPrompt
530
+ open={showLinkInput}
531
+ value={linkUrl}
532
+ onChange={setLinkUrl}
533
+ onConfirm={confirmLink}
534
+ onCancel={cancelLink}
535
+ />
536
+ ) : null}
537
+ {CommentPrompt ? (
538
+ <CommentPrompt
539
+ open={showCommentInput}
540
+ value={commentText}
541
+ commentType={commentType}
542
+ commentTypeOptions={COMMENT_TYPES.map(value => ({
543
+ value,
544
+ label: COMMENT_TYPE_LABELS[value],
545
+ }))}
546
+ onChange={setCommentText}
547
+ onCommentTypeChange={setCommentType}
548
+ onConfirm={confirmComment}
549
+ onCancel={cancelComment}
550
+ confirmDisabled={!commentText.trim() || !commentSelection}
551
+ />
552
+ ) : null}
553
+ </>
554
+ )
555
+ }
556
+
557
+ const AdvancedWrapper = styled.div`
558
+ position: relative;
559
+ `
560
+
561
+ const AdvancedPanel = styled(CollapsePanel)`
562
+ position: absolute;
563
+ top: calc(100% + 8px);
564
+ right: 0;
565
+ z-index: 20;
566
+ min-width: 220px;
567
+ background: var(--platform-colors-surface);
568
+ border: 1px solid rgb(from var(--platform-colors-border) r g b / 0.92);
569
+ box-shadow: 0 18px 36px rgb(from var(--platform-colors-text) r g b / 0.1);
570
+
571
+ &[data-open='false'] {
572
+ border-color: transparent;
573
+ box-shadow: none;
574
+ pointer-events: none;
575
+ }
576
+ `
577
+
578
+ const AdvancedMenu = styled.div`
579
+ display: flex;
580
+ flex-direction: column;
581
+ gap: 2px;
582
+ padding: 8px;
583
+ `
584
+
585
+ const AdvancedItem = styled.button.attrs({
586
+ onMouseDown: (event: React.MouseEvent<HTMLButtonElement>) => {
587
+ event.preventDefault()
588
+ },
589
+ })`
590
+ display: flex;
591
+ align-items: center;
592
+ gap: 10px;
593
+ width: 100%;
594
+ padding: 8px 10px;
595
+ border: none;
596
+ background: transparent;
597
+ color: var(--platform-colors-text);
598
+ text-align: left;
599
+ font-size: 12px;
600
+ transition: background 0.12s ease, color 0.12s ease;
601
+
602
+ &:hover:not(:disabled) {
603
+ background: rgb(from var(--platform-colors-surface-hover) r g b / 0.9);
604
+ }
605
+
606
+ &:disabled {
607
+ opacity: 0.45;
608
+ cursor: not-allowed;
609
+ }
610
+ `
611
+
612
+ const UnderlineGlyph = styled.span`
613
+ display: inline-flex;
614
+ align-items: center;
615
+ justify-content: center;
616
+ width: 1em;
617
+ color: currentColor;
618
+ font-size: 16px;
619
+ font-weight: 500;
620
+ line-height: 1;
621
+ text-decoration-line: underline;
622
+ text-decoration-thickness: 1.5px;
623
+ text-underline-offset: 3px;
624
+ `