@betterstart/cli 0.1.3 → 0.1.4
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 +133 -0
- package/dist/cli.d.ts +1 -9
- package/dist/cli.js +13484 -367
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +24 -266
- package/dist/index.js +4 -11378
- package/dist/index.js.map +1 -1
- package/package.json +29 -42
- package/templates/schema.json +959 -0
- package/templates/tiptap/hooks/use-composed-ref.ts +43 -0
- package/templates/tiptap/hooks/use-cursor-visibility.ts +68 -0
- package/templates/tiptap/hooks/use-element-rect.ts +166 -0
- package/templates/tiptap/hooks/use-is-breakpoint.ts +32 -0
- package/templates/tiptap/hooks/use-menu-navigation.ts +182 -0
- package/templates/tiptap/hooks/use-scrolling.ts +64 -0
- package/templates/tiptap/hooks/use-throttled-callback.ts +146 -0
- package/templates/tiptap/hooks/use-tiptap-editor.ts +46 -0
- package/templates/tiptap/hooks/use-unmount.ts +21 -0
- package/templates/tiptap/hooks/use-window-size.ts +87 -0
- package/templates/tiptap/lib/tiptap-utils.ts +587 -0
- package/templates/tiptap/styles/_keyframe-animations.scss +91 -0
- package/templates/tiptap/styles/_variables.scss +296 -0
- package/templates/tiptap/tiptap-extension/node-background-extension.ts +138 -0
- package/templates/tiptap/tiptap-icons/align-center-icon.tsx +38 -0
- package/templates/tiptap/tiptap-icons/align-justify-icon.tsx +38 -0
- package/templates/tiptap/tiptap-icons/align-left-icon.tsx +38 -0
- package/templates/tiptap/tiptap-icons/align-right-icon.tsx +38 -0
- package/templates/tiptap/tiptap-icons/arrow-left-icon.tsx +24 -0
- package/templates/tiptap/tiptap-icons/ban-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/blockquote-icon.tsx +44 -0
- package/templates/tiptap/tiptap-icons/bold-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/chevron-down-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/close-icon.tsx +24 -0
- package/templates/tiptap/tiptap-icons/code-block-icon.tsx +38 -0
- package/templates/tiptap/tiptap-icons/code2-icon.tsx +32 -0
- package/templates/tiptap/tiptap-icons/corner-down-left-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/external-link-icon.tsx +28 -0
- package/templates/tiptap/tiptap-icons/heading-five-icon.tsx +28 -0
- package/templates/tiptap/tiptap-icons/heading-four-icon.tsx +28 -0
- package/templates/tiptap/tiptap-icons/heading-icon.tsx +24 -0
- package/templates/tiptap/tiptap-icons/heading-one-icon.tsx +28 -0
- package/templates/tiptap/tiptap-icons/heading-six-icon.tsx +30 -0
- package/templates/tiptap/tiptap-icons/heading-three-icon.tsx +36 -0
- package/templates/tiptap/tiptap-icons/heading-two-icon.tsx +28 -0
- package/templates/tiptap/tiptap-icons/highlighter-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/image-plus-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/italic-icon.tsx +24 -0
- package/templates/tiptap/tiptap-icons/link-icon.tsx +28 -0
- package/templates/tiptap/tiptap-icons/list-icon.tsx +56 -0
- package/templates/tiptap/tiptap-icons/list-ordered-icon.tsx +56 -0
- package/templates/tiptap/tiptap-icons/list-todo-icon.tsx +50 -0
- package/templates/tiptap/tiptap-icons/moon-star-icon.tsx +30 -0
- package/templates/tiptap/tiptap-icons/redo2-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/strike-icon.tsx +28 -0
- package/templates/tiptap/tiptap-icons/subscript-icon.tsx +38 -0
- package/templates/tiptap/tiptap-icons/sun-icon.tsx +58 -0
- package/templates/tiptap/tiptap-icons/superscript-icon.tsx +38 -0
- package/templates/tiptap/tiptap-icons/trash-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/underline-icon.tsx +26 -0
- package/templates/tiptap/tiptap-icons/undo2-icon.tsx +26 -0
- package/templates/tiptap/tiptap-node/blockquote-node/blockquote-node.scss +37 -0
- package/templates/tiptap/tiptap-node/code-block-node/code-block-node.scss +54 -0
- package/templates/tiptap/tiptap-node/heading-node/heading-node.scss +45 -0
- package/templates/tiptap/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +10 -0
- package/templates/tiptap/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss +25 -0
- package/templates/tiptap/tiptap-node/image-node/image-node.scss +35 -0
- package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node-extension.ts +154 -0
- package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node.scss +249 -0
- package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node.tsx +522 -0
- package/templates/tiptap/tiptap-node/image-upload-node/index.tsx +1 -0
- package/templates/tiptap/tiptap-node/list-node/list-node.scss +208 -0
- package/templates/tiptap/tiptap-node/paragraph-node/paragraph-node.scss +273 -0
- package/templates/tiptap/tiptap-ui/blockquote-button/blockquote-button.tsx +104 -0
- package/templates/tiptap/tiptap-ui/blockquote-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/blockquote-button/use-blockquote.ts +252 -0
- package/templates/tiptap/tiptap-ui/code-block-button/code-block-button.tsx +106 -0
- package/templates/tiptap/tiptap-ui/code-block-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/code-block-button/use-code-block.ts +261 -0
- package/templates/tiptap/tiptap-ui/color-highlight-button/color-highlight-button.scss +49 -0
- package/templates/tiptap/tiptap-ui/color-highlight-button/color-highlight-button.tsx +153 -0
- package/templates/tiptap/tiptap-ui/color-highlight-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/color-highlight-button/use-color-highlight.ts +345 -0
- package/templates/tiptap/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +207 -0
- package/templates/tiptap/tiptap-ui/color-highlight-popover/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui/heading-button/heading-button.tsx +107 -0
- package/templates/tiptap/tiptap-ui/heading-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/heading-button/use-heading.ts +314 -0
- package/templates/tiptap/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +131 -0
- package/templates/tiptap/tiptap-ui/heading-dropdown-menu/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts +130 -0
- package/templates/tiptap/tiptap-ui/image-upload-button/image-upload-button.tsx +114 -0
- package/templates/tiptap/tiptap-ui/image-upload-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/image-upload-button/use-image-upload.ts +192 -0
- package/templates/tiptap/tiptap-ui/link-popover/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/link-popover/link-popover.tsx +285 -0
- package/templates/tiptap/tiptap-ui/link-popover/use-link-popover.ts +286 -0
- package/templates/tiptap/tiptap-ui/list-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/list-button/list-button.tsx +108 -0
- package/templates/tiptap/tiptap-ui/list-button/use-list.ts +329 -0
- package/templates/tiptap/tiptap-ui/list-dropdown-menu/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +123 -0
- package/templates/tiptap/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts +203 -0
- package/templates/tiptap/tiptap-ui/mark-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/mark-button/mark-button.tsx +107 -0
- package/templates/tiptap/tiptap-ui/mark-button/use-mark.ts +206 -0
- package/templates/tiptap/tiptap-ui/text-align-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/text-align-button/text-align-button.tsx +118 -0
- package/templates/tiptap/tiptap-ui/text-align-button/use-text-align.ts +212 -0
- package/templates/tiptap/tiptap-ui/undo-redo-button/index.tsx +2 -0
- package/templates/tiptap/tiptap-ui/undo-redo-button/undo-redo-button.tsx +105 -0
- package/templates/tiptap/tiptap-ui/undo-redo-button/use-undo-redo.ts +173 -0
- package/templates/tiptap/tiptap-ui-primitive/badge/badge-colors.scss +395 -0
- package/templates/tiptap/tiptap-ui-primitive/badge/badge-group.scss +16 -0
- package/templates/tiptap/tiptap-ui-primitive/badge/badge.scss +99 -0
- package/templates/tiptap/tiptap-ui-primitive/badge/badge.tsx +46 -0
- package/templates/tiptap/tiptap-ui-primitive/badge/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/button/button-colors.scss +429 -0
- package/templates/tiptap/tiptap-ui-primitive/button/button-group.scss +22 -0
- package/templates/tiptap/tiptap-ui-primitive/button/button.scss +314 -0
- package/templates/tiptap/tiptap-ui-primitive/button/button.tsx +102 -0
- package/templates/tiptap/tiptap-ui-primitive/button/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/card/card.scss +77 -0
- package/templates/tiptap/tiptap-ui-primitive/card/card.tsx +59 -0
- package/templates/tiptap/tiptap-ui-primitive/card/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss +63 -0
- package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx +95 -0
- package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/input/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/input/input.scss +45 -0
- package/templates/tiptap/tiptap-ui-primitive/input/input.tsx +18 -0
- package/templates/tiptap/tiptap-ui-primitive/popover/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/popover/popover.scss +63 -0
- package/templates/tiptap/tiptap-ui-primitive/popover/popover.tsx +33 -0
- package/templates/tiptap/tiptap-ui-primitive/separator/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/separator/separator.scss +23 -0
- package/templates/tiptap/tiptap-ui-primitive/separator/separator.tsx +33 -0
- package/templates/tiptap/tiptap-ui-primitive/spacer/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/spacer/spacer.tsx +21 -0
- package/templates/tiptap/tiptap-ui-primitive/toolbar/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/toolbar/toolbar.scss +98 -0
- package/templates/tiptap/tiptap-ui-primitive/toolbar/toolbar.tsx +113 -0
- package/templates/tiptap/tiptap-ui-primitive/tooltip/index.tsx +1 -0
- package/templates/tiptap/tiptap-ui-primitive/tooltip/tooltip.scss +43 -0
- package/templates/tiptap/tiptap-ui-primitive/tooltip/tooltip.tsx +223 -0
- package/templates/ui/accordion.tsx +52 -0
- package/templates/ui/alert-dialog.tsx +116 -0
- package/templates/ui/alert.tsx +48 -0
- package/templates/ui/aspect-ratio.tsx +7 -0
- package/templates/ui/avatar.tsx +46 -0
- package/templates/ui/badge.tsx +32 -0
- package/templates/ui/breadcrumb.tsx +98 -0
- package/templates/ui/button-group.tsx +77 -0
- package/templates/ui/button.tsx +48 -0
- package/templates/ui/calendar.tsx +176 -0
- package/templates/ui/card.tsx +54 -0
- package/templates/ui/carousel.tsx +234 -0
- package/templates/ui/chart.tsx +349 -0
- package/templates/ui/checkbox.tsx +27 -0
- package/templates/ui/collapsible.tsx +11 -0
- package/templates/ui/command.tsx +142 -0
- package/templates/ui/context-menu.tsx +188 -0
- package/templates/ui/curriculum-editor.tsx +601 -0
- package/templates/ui/date-picker.tsx +70 -0
- package/templates/ui/dialog.tsx +103 -0
- package/templates/ui/drawer.tsx +99 -0
- package/templates/ui/dropdown-menu.tsx +185 -0
- package/templates/ui/dynamic-list-field.tsx +95 -0
- package/templates/ui/empty.tsx +90 -0
- package/templates/ui/field.tsx +231 -0
- package/templates/ui/file-upload-example.tsx +113 -0
- package/templates/ui/form.tsx +172 -0
- package/templates/ui/hover-card.tsx +28 -0
- package/templates/ui/icon-picker.tsx +435 -0
- package/templates/ui/icons-data.ts +6 -0
- package/templates/ui/image-upload-field.tsx +360 -0
- package/templates/ui/input-group.tsx +160 -0
- package/templates/ui/input-otp.tsx +70 -0
- package/templates/ui/input.tsx +21 -0
- package/templates/ui/item.tsx +171 -0
- package/templates/ui/kbd.tsx +28 -0
- package/templates/ui/label.tsx +20 -0
- package/templates/ui/logo.tsx +113 -0
- package/templates/ui/markdown-editor.tsx +303 -0
- package/templates/ui/markdown-utils.ts +128 -0
- package/templates/ui/media-upload-field.tsx +255 -0
- package/templates/ui/menubar.tsx +230 -0
- package/templates/ui/navigation-menu.tsx +119 -0
- package/templates/ui/pagination.tsx +96 -0
- package/templates/ui/placeholder.tsx +25 -0
- package/templates/ui/popover.tsx +32 -0
- package/templates/ui/progress.tsx +24 -0
- package/templates/ui/radio-group.tsx +37 -0
- package/templates/ui/resizable.tsx +41 -0
- package/templates/ui/rich-text-editor.tsx +374 -0
- package/templates/ui/scroll-area.tsx +45 -0
- package/templates/ui/select.tsx +151 -0
- package/templates/ui/separator.tsx +25 -0
- package/templates/ui/sheet.tsx +120 -0
- package/templates/ui/sidebar.tsx +684 -0
- package/templates/ui/skeleton.tsx +7 -0
- package/templates/ui/slider.tsx +24 -0
- package/templates/ui/sonner.tsx +29 -0
- package/templates/ui/spinner.tsx +15 -0
- package/templates/ui/switch.tsx +28 -0
- package/templates/ui/table.tsx +93 -0
- package/templates/ui/tabs.tsx +54 -0
- package/templates/ui/textarea.tsx +20 -0
- package/templates/ui/toast.tsx +127 -0
- package/templates/ui/toggle-group.tsx +56 -0
- package/templates/ui/toggle.tsx +43 -0
- package/templates/ui/tooltip.tsx +31 -0
- package/templates/ui/use-mobile.tsx +19 -0
- package/templates/ui/video-upload-field.tsx +368 -0
- package/dist/chunk-EIH4RRIJ.js +0 -183
- package/dist/chunk-EIH4RRIJ.js.map +0 -1
- package/dist/chunk-NKRQYAS6.js +0 -260
- package/dist/chunk-NKRQYAS6.js.map +0 -1
- package/dist/chunk-QLVSHP7X.js +0 -235
- package/dist/chunk-QLVSHP7X.js.map +0 -1
- package/dist/chunk-WY6BC55D.js +0 -357
- package/dist/chunk-WY6BC55D.js.map +0 -1
- package/dist/config/index.d.ts +0 -93
- package/dist/config/index.js +0 -58
- package/dist/config/index.js.map +0 -1
- package/dist/core/index.d.ts +0 -415
- package/dist/core/index.js +0 -906
- package/dist/core/index.js.map +0 -1
- package/dist/import-resolver-BaZ-rzkH.d.ts +0 -123
- package/dist/logger-awLb347n.d.ts +0 -81
- package/dist/plugins/index.d.ts +0 -213
- package/dist/plugins/index.js +0 -365
- package/dist/plugins/index.js.map +0 -1
- package/dist/types-ByX_gl6y.d.ts +0 -232
- package/dist/types-eI549DEG.d.ts +0 -331
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { forwardRef, useCallback, useMemo } from 'react'
|
|
4
|
+
// --- Hooks ---
|
|
5
|
+
import { useTiptapEditor } from '../../hooks/use-tiptap-editor'
|
|
6
|
+
// --- Lib ---
|
|
7
|
+
import { parseShortcutKeys } from '../../lib/tiptap-utils'
|
|
8
|
+
import { Badge } from '../../tiptap-ui-primitive/badge'
|
|
9
|
+
// --- UI Primitives ---
|
|
10
|
+
import type { ButtonProps } from '../../tiptap-ui-primitive/button'
|
|
11
|
+
import { Button } from '../../tiptap-ui-primitive/button'
|
|
12
|
+
// --- Tiptap UI ---
|
|
13
|
+
import type { UseColorHighlightConfig } from '.'
|
|
14
|
+
import { COLOR_HIGHLIGHT_SHORTCUT_KEY, useColorHighlight } from '.'
|
|
15
|
+
|
|
16
|
+
// --- Styles ---
|
|
17
|
+
import './color-highlight-button.scss'
|
|
18
|
+
|
|
19
|
+
export interface ColorHighlightButtonProps
|
|
20
|
+
extends Omit<ButtonProps, 'type'>,
|
|
21
|
+
UseColorHighlightConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Optional text to display alongside the icon.
|
|
24
|
+
*/
|
|
25
|
+
text?: string
|
|
26
|
+
/**
|
|
27
|
+
* Optional show shortcut keys in the button.
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
showShortcut?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ColorHighlightShortcutBadge({
|
|
34
|
+
shortcutKeys = COLOR_HIGHLIGHT_SHORTCUT_KEY
|
|
35
|
+
}: {
|
|
36
|
+
shortcutKeys?: string
|
|
37
|
+
}) {
|
|
38
|
+
return <Badge>{parseShortcutKeys({ shortcutKeys })}</Badge>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Button component for applying color highlights in a Tiptap editor.
|
|
43
|
+
*
|
|
44
|
+
* Supports two highlighting modes:
|
|
45
|
+
* - "mark": Uses the highlight mark extension (default)
|
|
46
|
+
* - "node": Uses the node background extension
|
|
47
|
+
*
|
|
48
|
+
* For custom button implementations, use the `useColorHighlight` hook instead.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* // Mark-based highlighting (default)
|
|
53
|
+
* <ColorHighlightButton highlightColor="yellow" />
|
|
54
|
+
*
|
|
55
|
+
* // Node-based background coloring
|
|
56
|
+
* <ColorHighlightButton
|
|
57
|
+
* highlightColor="var(--tt-color-highlight-blue)"
|
|
58
|
+
* mode="node"
|
|
59
|
+
* />
|
|
60
|
+
*
|
|
61
|
+
* // With custom callback
|
|
62
|
+
* <ColorHighlightButton
|
|
63
|
+
* highlightColor="red"
|
|
64
|
+
* mode="mark"
|
|
65
|
+
* onApplied={({ color, mode }) => console.log(`Applied ${color} in ${mode} mode`)}
|
|
66
|
+
* />
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export const ColorHighlightButton = forwardRef<HTMLButtonElement, ColorHighlightButtonProps>(
|
|
70
|
+
(
|
|
71
|
+
{
|
|
72
|
+
editor: providedEditor,
|
|
73
|
+
highlightColor,
|
|
74
|
+
text,
|
|
75
|
+
hideWhenUnavailable = false,
|
|
76
|
+
mode = 'mark',
|
|
77
|
+
onApplied,
|
|
78
|
+
showShortcut = false,
|
|
79
|
+
onClick,
|
|
80
|
+
children,
|
|
81
|
+
style,
|
|
82
|
+
useColorValue = false,
|
|
83
|
+
...buttonProps
|
|
84
|
+
},
|
|
85
|
+
ref
|
|
86
|
+
) => {
|
|
87
|
+
const { editor } = useTiptapEditor(providedEditor)
|
|
88
|
+
const { isVisible, canColorHighlight, isActive, handleColorHighlight, label, shortcutKeys } =
|
|
89
|
+
useColorHighlight({
|
|
90
|
+
editor,
|
|
91
|
+
highlightColor,
|
|
92
|
+
useColorValue,
|
|
93
|
+
label: text || `Toggle highlight (${highlightColor})`,
|
|
94
|
+
hideWhenUnavailable,
|
|
95
|
+
mode,
|
|
96
|
+
onApplied
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const handleClick = useCallback(
|
|
100
|
+
(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
101
|
+
onClick?.(event)
|
|
102
|
+
if (event.defaultPrevented) return
|
|
103
|
+
handleColorHighlight()
|
|
104
|
+
},
|
|
105
|
+
[handleColorHighlight, onClick]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const buttonStyle = useMemo(
|
|
109
|
+
() =>
|
|
110
|
+
({
|
|
111
|
+
...style,
|
|
112
|
+
'--highlight-color': highlightColor
|
|
113
|
+
}) as React.CSSProperties,
|
|
114
|
+
[highlightColor, style]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if (!isVisible) {
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Button
|
|
123
|
+
type="button"
|
|
124
|
+
variant="ghost"
|
|
125
|
+
data-active-state={isActive ? 'on' : 'off'}
|
|
126
|
+
role="button"
|
|
127
|
+
tabIndex={-1}
|
|
128
|
+
disabled={!canColorHighlight}
|
|
129
|
+
data-disabled={!canColorHighlight}
|
|
130
|
+
aria-label={label}
|
|
131
|
+
aria-pressed={isActive}
|
|
132
|
+
tooltip={label}
|
|
133
|
+
onClick={handleClick}
|
|
134
|
+
style={buttonStyle}
|
|
135
|
+
{...buttonProps}
|
|
136
|
+
ref={ref}
|
|
137
|
+
>
|
|
138
|
+
{children ?? (
|
|
139
|
+
<>
|
|
140
|
+
<span
|
|
141
|
+
className="tiptap-button-highlight"
|
|
142
|
+
style={{ '--highlight-color': highlightColor } as React.CSSProperties}
|
|
143
|
+
/>
|
|
144
|
+
{text && <span className="tiptap-button-text">{text}</span>}
|
|
145
|
+
{showShortcut && <ColorHighlightShortcutBadge shortcutKeys={shortcutKeys} />}
|
|
146
|
+
</>
|
|
147
|
+
)}
|
|
148
|
+
</Button>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
ColorHighlightButton.displayName = 'ColorHighlightButton'
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { Editor } from '@tiptap/react'
|
|
4
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
5
|
+
import { useHotkeys } from 'react-hotkeys-hook'
|
|
6
|
+
import { useIsBreakpoint } from '../../hooks/use-is-breakpoint'
|
|
7
|
+
// --- Hooks ---
|
|
8
|
+
import { useTiptapEditor } from '../../hooks/use-tiptap-editor'
|
|
9
|
+
|
|
10
|
+
// --- Lib ---
|
|
11
|
+
import { isExtensionAvailable, isMarkInSchema, isNodeTypeSelected } from '../../lib/tiptap-utils'
|
|
12
|
+
|
|
13
|
+
// --- Icons ---
|
|
14
|
+
import { HighlighterIcon } from '../../tiptap-icons/highlighter-icon'
|
|
15
|
+
|
|
16
|
+
export const COLOR_HIGHLIGHT_SHORTCUT_KEY = 'mod+shift+h'
|
|
17
|
+
export const HIGHLIGHT_COLORS = [
|
|
18
|
+
{
|
|
19
|
+
label: 'Default background',
|
|
20
|
+
value: 'var(--tt-bg-color)',
|
|
21
|
+
colorValue: '#ffffff',
|
|
22
|
+
border: 'var(--tt-bg-color-contrast)'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: 'Gray background',
|
|
26
|
+
value: 'var(--tt-color-highlight-gray)',
|
|
27
|
+
colorValue: '#f8f8f7',
|
|
28
|
+
border: 'var(--tt-color-highlight-gray-contrast)'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: 'Brown background',
|
|
32
|
+
value: 'var(--tt-color-highlight-brown)',
|
|
33
|
+
colorValue: '#f4eeee',
|
|
34
|
+
border: 'var(--tt-color-highlight-brown-contrast)'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 'Orange background',
|
|
38
|
+
value: 'var(--tt-color-highlight-orange)',
|
|
39
|
+
colorValue: '#fbecdd',
|
|
40
|
+
border: 'var(--tt-color-highlight-orange-contrast)'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: 'Yellow background',
|
|
44
|
+
value: 'var(--tt-color-highlight-yellow)',
|
|
45
|
+
colorValue: '#fef9c3',
|
|
46
|
+
border: 'var(--tt-color-highlight-yellow-contrast)'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: 'Green background',
|
|
50
|
+
value: 'var(--tt-color-highlight-green)',
|
|
51
|
+
colorValue: '#dcfce7',
|
|
52
|
+
border: 'var(--tt-color-highlight-green-contrast)'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: 'Blue background',
|
|
56
|
+
value: 'var(--tt-color-highlight-blue)',
|
|
57
|
+
colorValue: '#e0f2fe',
|
|
58
|
+
border: 'var(--tt-color-highlight-blue-contrast)'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: 'Purple background',
|
|
62
|
+
value: 'var(--tt-color-highlight-purple)',
|
|
63
|
+
colorValue: '#f3e8ff',
|
|
64
|
+
border: 'var(--tt-color-highlight-purple-contrast)'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: 'Pink background',
|
|
68
|
+
value: 'var(--tt-color-highlight-pink)',
|
|
69
|
+
colorValue: '#fcf1f6',
|
|
70
|
+
border: 'var(--tt-color-highlight-pink-contrast)'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: 'Red background',
|
|
74
|
+
value: 'var(--tt-color-highlight-red)',
|
|
75
|
+
colorValue: '#ffe4e6',
|
|
76
|
+
border: 'var(--tt-color-highlight-red-contrast)'
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
export type HighlightColor = (typeof HIGHLIGHT_COLORS)[number]
|
|
80
|
+
|
|
81
|
+
export type HighlightMode = 'mark' | 'node'
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Configuration for the color highlight functionality
|
|
85
|
+
*/
|
|
86
|
+
export interface UseColorHighlightConfig {
|
|
87
|
+
/**
|
|
88
|
+
* The Tiptap editor instance.
|
|
89
|
+
*/
|
|
90
|
+
editor?: Editor | null
|
|
91
|
+
/**
|
|
92
|
+
* The color to apply when toggling the highlight.
|
|
93
|
+
*/
|
|
94
|
+
highlightColor?: string
|
|
95
|
+
/**
|
|
96
|
+
* Optional label to display alongside the icon.
|
|
97
|
+
*/
|
|
98
|
+
label?: string
|
|
99
|
+
/**
|
|
100
|
+
* Whether the button should hide when the mark is not available.
|
|
101
|
+
* @default false
|
|
102
|
+
*/
|
|
103
|
+
hideWhenUnavailable?: boolean
|
|
104
|
+
/**
|
|
105
|
+
* The highlighting mode to use.
|
|
106
|
+
* - "mark": Uses the highlight mark extension (default)
|
|
107
|
+
* - "node": Uses the node background extension
|
|
108
|
+
* @default "mark"
|
|
109
|
+
*/
|
|
110
|
+
mode?: HighlightMode
|
|
111
|
+
/**
|
|
112
|
+
* When true, uses the actual color value (colorValue) instead of CSS variable (value).
|
|
113
|
+
* @default false
|
|
114
|
+
*/
|
|
115
|
+
useColorValue?: boolean
|
|
116
|
+
/**
|
|
117
|
+
* Called when the highlight is applied.
|
|
118
|
+
*/
|
|
119
|
+
onApplied?: ({
|
|
120
|
+
color,
|
|
121
|
+
label,
|
|
122
|
+
mode
|
|
123
|
+
}: {
|
|
124
|
+
color: string
|
|
125
|
+
label: string
|
|
126
|
+
mode: HighlightMode
|
|
127
|
+
}) => void
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function pickHighlightColorsByValue(values: string[]) {
|
|
131
|
+
const colorMap = new Map(HIGHLIGHT_COLORS.map((color) => [color.value, color]))
|
|
132
|
+
return values
|
|
133
|
+
.map((value) => colorMap.get(value))
|
|
134
|
+
.filter((color): color is (typeof HIGHLIGHT_COLORS)[number] => !!color)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Gets the appropriate color value based on configuration
|
|
139
|
+
*/
|
|
140
|
+
export function getHighlightColorValue(color: string, useColorValue: boolean = false): string {
|
|
141
|
+
if (!useColorValue) return color
|
|
142
|
+
|
|
143
|
+
const colorItem = HIGHLIGHT_COLORS.find((c) => c.value === color || c.colorValue === color)
|
|
144
|
+
return colorItem?.colorValue || color
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Checks if highlight can be applied based on the mode and current editor state
|
|
149
|
+
*/
|
|
150
|
+
export function canColorHighlight(editor: Editor | null, mode: HighlightMode = 'mark'): boolean {
|
|
151
|
+
if (!editor || !editor.isEditable) return false
|
|
152
|
+
|
|
153
|
+
if (mode === 'mark') {
|
|
154
|
+
if (!isMarkInSchema('highlight', editor) || isNodeTypeSelected(editor, ['image'])) return false
|
|
155
|
+
|
|
156
|
+
return editor.can().setMark('highlight')
|
|
157
|
+
} else {
|
|
158
|
+
if (!isExtensionAvailable(editor, ['nodeBackground'])) return false
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
return editor.can().toggleNodeBackgroundColor('test')
|
|
162
|
+
} catch {
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Checks if highlight is currently active
|
|
170
|
+
*/
|
|
171
|
+
export function isColorHighlightActive(
|
|
172
|
+
editor: Editor | null,
|
|
173
|
+
highlightColor?: string,
|
|
174
|
+
mode: HighlightMode = 'mark'
|
|
175
|
+
): boolean {
|
|
176
|
+
if (!editor || !editor.isEditable) return false
|
|
177
|
+
|
|
178
|
+
if (mode === 'mark') {
|
|
179
|
+
return highlightColor
|
|
180
|
+
? editor.isActive('highlight', { color: highlightColor })
|
|
181
|
+
: editor.isActive('highlight')
|
|
182
|
+
} else {
|
|
183
|
+
if (!highlightColor) return false
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const { state } = editor
|
|
187
|
+
const { selection } = state
|
|
188
|
+
|
|
189
|
+
const $pos = selection.$anchor
|
|
190
|
+
for (let depth = $pos.depth; depth >= 0; depth--) {
|
|
191
|
+
const node = $pos.node(depth)
|
|
192
|
+
if (node && node.attrs?.backgroundColor === highlightColor) {
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return false
|
|
197
|
+
} catch {
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Removes highlight based on the mode
|
|
205
|
+
*/
|
|
206
|
+
export function removeHighlight(editor: Editor | null, mode: HighlightMode = 'mark'): boolean {
|
|
207
|
+
if (!editor || !editor.isEditable) return false
|
|
208
|
+
if (!canColorHighlight(editor, mode)) return false
|
|
209
|
+
|
|
210
|
+
if (mode === 'mark') {
|
|
211
|
+
return editor.chain().focus().unsetMark('highlight').run()
|
|
212
|
+
} else {
|
|
213
|
+
return editor.chain().focus().unsetNodeBackgroundColor().run()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Determines if the highlight button should be shown
|
|
219
|
+
*/
|
|
220
|
+
export function shouldShowButton(props: {
|
|
221
|
+
editor: Editor | null
|
|
222
|
+
hideWhenUnavailable: boolean
|
|
223
|
+
mode: HighlightMode
|
|
224
|
+
}): boolean {
|
|
225
|
+
const { editor, hideWhenUnavailable, mode } = props
|
|
226
|
+
|
|
227
|
+
if (!editor || !editor.isEditable) return false
|
|
228
|
+
|
|
229
|
+
if (!hideWhenUnavailable) {
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// hideWhenUnavailable=true: check schema/extension availability
|
|
234
|
+
if (mode === 'mark') {
|
|
235
|
+
if (!isMarkInSchema('highlight', editor)) return false
|
|
236
|
+
} else {
|
|
237
|
+
if (!isExtensionAvailable(editor, ['nodeBackground'])) return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!editor.isActive('code')) {
|
|
241
|
+
return canColorHighlight(editor, mode)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return true
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function useColorHighlight(config: UseColorHighlightConfig) {
|
|
248
|
+
const {
|
|
249
|
+
editor: providedEditor,
|
|
250
|
+
label,
|
|
251
|
+
highlightColor,
|
|
252
|
+
hideWhenUnavailable = false,
|
|
253
|
+
mode = 'mark',
|
|
254
|
+
useColorValue = false,
|
|
255
|
+
onApplied
|
|
256
|
+
} = config
|
|
257
|
+
|
|
258
|
+
const { editor } = useTiptapEditor(providedEditor)
|
|
259
|
+
const isMobile = useIsBreakpoint()
|
|
260
|
+
const [isVisible, setIsVisible] = useState<boolean>(true)
|
|
261
|
+
const canColorHighlightState = canColorHighlight(editor, mode)
|
|
262
|
+
const actualColor = highlightColor
|
|
263
|
+
? getHighlightColorValue(highlightColor, useColorValue)
|
|
264
|
+
: highlightColor
|
|
265
|
+
const isActive = isColorHighlightActive(editor, actualColor, mode)
|
|
266
|
+
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
if (!editor) return
|
|
269
|
+
|
|
270
|
+
const handleSelectionUpdate = () => {
|
|
271
|
+
setIsVisible(shouldShowButton({ editor, hideWhenUnavailable, mode }))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
handleSelectionUpdate()
|
|
275
|
+
|
|
276
|
+
editor.on('selectionUpdate', handleSelectionUpdate)
|
|
277
|
+
|
|
278
|
+
return () => {
|
|
279
|
+
editor.off('selectionUpdate', handleSelectionUpdate)
|
|
280
|
+
}
|
|
281
|
+
}, [editor, hideWhenUnavailable, mode])
|
|
282
|
+
|
|
283
|
+
const handleColorHighlight = useCallback(() => {
|
|
284
|
+
if (!editor || !canColorHighlightState || !actualColor || !label) return false
|
|
285
|
+
|
|
286
|
+
if (mode === 'mark') {
|
|
287
|
+
if (editor.state.storedMarks) {
|
|
288
|
+
const highlightMarkType = editor.schema.marks.highlight
|
|
289
|
+
if (highlightMarkType) {
|
|
290
|
+
editor.view.dispatch(editor.state.tr.removeStoredMark(highlightMarkType))
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
const success = editor.chain().focus().toggleHighlight({ color: actualColor }).run()
|
|
296
|
+
if (success) {
|
|
297
|
+
onApplied?.({ color: actualColor, label, mode })
|
|
298
|
+
}
|
|
299
|
+
return success
|
|
300
|
+
}, 0)
|
|
301
|
+
|
|
302
|
+
return true
|
|
303
|
+
} else {
|
|
304
|
+
const success = editor.chain().focus().toggleNodeBackgroundColor(actualColor).run()
|
|
305
|
+
|
|
306
|
+
if (success) {
|
|
307
|
+
onApplied?.({ color: actualColor, label, mode })
|
|
308
|
+
}
|
|
309
|
+
return success
|
|
310
|
+
}
|
|
311
|
+
}, [canColorHighlightState, actualColor, editor, label, onApplied, mode])
|
|
312
|
+
|
|
313
|
+
const handleRemoveHighlight = useCallback(() => {
|
|
314
|
+
const success = removeHighlight(editor, mode)
|
|
315
|
+
if (success) {
|
|
316
|
+
onApplied?.({ color: '', label: 'Remove highlight', mode })
|
|
317
|
+
}
|
|
318
|
+
return success
|
|
319
|
+
}, [editor, onApplied, mode])
|
|
320
|
+
|
|
321
|
+
useHotkeys(
|
|
322
|
+
COLOR_HIGHLIGHT_SHORTCUT_KEY,
|
|
323
|
+
(event) => {
|
|
324
|
+
event.preventDefault()
|
|
325
|
+
handleColorHighlight()
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
enabled: isVisible && canColorHighlightState,
|
|
329
|
+
enableOnContentEditable: !isMobile,
|
|
330
|
+
enableOnFormTags: true
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
isVisible,
|
|
336
|
+
isActive,
|
|
337
|
+
handleColorHighlight,
|
|
338
|
+
handleRemoveHighlight,
|
|
339
|
+
canColorHighlight: canColorHighlightState,
|
|
340
|
+
label: label || `Highlight`,
|
|
341
|
+
shortcutKeys: COLOR_HIGHLIGHT_SHORTCUT_KEY,
|
|
342
|
+
Icon: HighlighterIcon,
|
|
343
|
+
mode
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { Editor } from '@tiptap/react'
|
|
4
|
+
import { forwardRef, useMemo, useRef, useState } from 'react'
|
|
5
|
+
import { useIsBreakpoint } from '../../hooks/use-is-breakpoint'
|
|
6
|
+
// --- Hooks ---
|
|
7
|
+
import { useMenuNavigation } from '../../hooks/use-menu-navigation'
|
|
8
|
+
import { useTiptapEditor } from '../../hooks/use-tiptap-editor'
|
|
9
|
+
|
|
10
|
+
// --- Icons ---
|
|
11
|
+
import { BanIcon } from '../../tiptap-icons/ban-icon'
|
|
12
|
+
import { HighlighterIcon } from '../../tiptap-icons/highlighter-icon'
|
|
13
|
+
|
|
14
|
+
// --- UI Primitives ---
|
|
15
|
+
import type { ButtonProps } from '../../tiptap-ui-primitive/button'
|
|
16
|
+
import { Button, ButtonGroup } from '../../tiptap-ui-primitive/button'
|
|
17
|
+
import { Card, CardBody, CardItemGroup } from '../../tiptap-ui-primitive/card'
|
|
18
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../../tiptap-ui-primitive/popover'
|
|
19
|
+
import { Separator } from '../../tiptap-ui-primitive/separator'
|
|
20
|
+
|
|
21
|
+
// --- Tiptap UI ---
|
|
22
|
+
import type { HighlightColor, UseColorHighlightConfig } from '../color-highlight-button'
|
|
23
|
+
import {
|
|
24
|
+
ColorHighlightButton,
|
|
25
|
+
pickHighlightColorsByValue,
|
|
26
|
+
useColorHighlight
|
|
27
|
+
} from '../color-highlight-button'
|
|
28
|
+
|
|
29
|
+
export interface ColorHighlightPopoverContentProps {
|
|
30
|
+
/**
|
|
31
|
+
* The Tiptap editor instance.
|
|
32
|
+
*/
|
|
33
|
+
editor?: Editor | null
|
|
34
|
+
/**
|
|
35
|
+
* Optional colors to use in the highlight popover.
|
|
36
|
+
* If not provided, defaults to a predefined set of colors.
|
|
37
|
+
*/
|
|
38
|
+
colors?: HighlightColor[]
|
|
39
|
+
/**
|
|
40
|
+
* When true, uses the actual color value (colorValue) instead of CSS variable (value).
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
useColorValue?: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ColorHighlightPopoverProps
|
|
47
|
+
extends Omit<ButtonProps, 'type'>,
|
|
48
|
+
Pick<UseColorHighlightConfig, 'editor' | 'hideWhenUnavailable' | 'onApplied'> {
|
|
49
|
+
/**
|
|
50
|
+
* Optional colors to use in the highlight popover.
|
|
51
|
+
* If not provided, defaults to a predefined set of colors.
|
|
52
|
+
*/
|
|
53
|
+
colors?: HighlightColor[]
|
|
54
|
+
/**
|
|
55
|
+
* When true, uses the actual color value (colorValue) instead of CSS variable (value).
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
useColorValue?: boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const ColorHighlightPopoverButton = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
62
|
+
({ className, children, ...props }, ref) => (
|
|
63
|
+
<Button
|
|
64
|
+
type="button"
|
|
65
|
+
className={className}
|
|
66
|
+
variant="ghost"
|
|
67
|
+
data-appearance="default"
|
|
68
|
+
role="button"
|
|
69
|
+
tabIndex={-1}
|
|
70
|
+
aria-label="Highlight text"
|
|
71
|
+
tooltip="Highlight"
|
|
72
|
+
ref={ref}
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
{children ?? <HighlighterIcon className="tiptap-button-icon" />}
|
|
76
|
+
</Button>
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
ColorHighlightPopoverButton.displayName = 'ColorHighlightPopoverButton'
|
|
81
|
+
|
|
82
|
+
export function ColorHighlightPopoverContent({
|
|
83
|
+
editor,
|
|
84
|
+
colors = pickHighlightColorsByValue([
|
|
85
|
+
'var(--tt-color-highlight-green)',
|
|
86
|
+
'var(--tt-color-highlight-blue)',
|
|
87
|
+
'var(--tt-color-highlight-red)',
|
|
88
|
+
'var(--tt-color-highlight-purple)',
|
|
89
|
+
'var(--tt-color-highlight-yellow)'
|
|
90
|
+
]),
|
|
91
|
+
useColorValue = false
|
|
92
|
+
}: ColorHighlightPopoverContentProps) {
|
|
93
|
+
const { handleRemoveHighlight } = useColorHighlight({ editor })
|
|
94
|
+
const isMobile = useIsBreakpoint()
|
|
95
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
96
|
+
|
|
97
|
+
const menuItems = useMemo(
|
|
98
|
+
() => [...colors, { label: 'Remove highlight', value: 'none' }],
|
|
99
|
+
[colors]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const { selectedIndex } = useMenuNavigation({
|
|
103
|
+
containerRef,
|
|
104
|
+
items: menuItems,
|
|
105
|
+
orientation: 'both',
|
|
106
|
+
onSelect: (item) => {
|
|
107
|
+
if (!containerRef.current) return false
|
|
108
|
+
const highlightedElement = containerRef.current.querySelector(
|
|
109
|
+
'[data-highlighted="true"]'
|
|
110
|
+
) as HTMLElement
|
|
111
|
+
if (highlightedElement) highlightedElement.click()
|
|
112
|
+
if (item.value === 'none') handleRemoveHighlight()
|
|
113
|
+
return true
|
|
114
|
+
},
|
|
115
|
+
autoSelectFirstItem: false
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<Card ref={containerRef} tabIndex={0} style={isMobile ? { boxShadow: 'none', border: 0 } : {}}>
|
|
120
|
+
<CardBody style={isMobile ? { padding: 0 } : {}}>
|
|
121
|
+
<CardItemGroup orientation="horizontal">
|
|
122
|
+
<ButtonGroup orientation="horizontal">
|
|
123
|
+
{colors.map((color, index) => (
|
|
124
|
+
<ColorHighlightButton
|
|
125
|
+
key={color.value}
|
|
126
|
+
editor={editor}
|
|
127
|
+
highlightColor={useColorValue ? color.colorValue : color.value}
|
|
128
|
+
tooltip={color.label}
|
|
129
|
+
aria-label={`${color.label} highlight color`}
|
|
130
|
+
tabIndex={index === selectedIndex ? 0 : -1}
|
|
131
|
+
data-highlighted={selectedIndex === index}
|
|
132
|
+
useColorValue={useColorValue}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
</ButtonGroup>
|
|
136
|
+
<Separator />
|
|
137
|
+
<ButtonGroup orientation="horizontal">
|
|
138
|
+
<Button
|
|
139
|
+
onClick={handleRemoveHighlight}
|
|
140
|
+
aria-label="Remove highlight"
|
|
141
|
+
tooltip="Remove highlight"
|
|
142
|
+
tabIndex={selectedIndex === colors.length ? 0 : -1}
|
|
143
|
+
type="button"
|
|
144
|
+
role="menuitem"
|
|
145
|
+
variant="ghost"
|
|
146
|
+
data-highlighted={selectedIndex === colors.length}
|
|
147
|
+
>
|
|
148
|
+
<BanIcon className="tiptap-button-icon" />
|
|
149
|
+
</Button>
|
|
150
|
+
</ButtonGroup>
|
|
151
|
+
</CardItemGroup>
|
|
152
|
+
</CardBody>
|
|
153
|
+
</Card>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function ColorHighlightPopover({
|
|
158
|
+
editor: providedEditor,
|
|
159
|
+
colors = pickHighlightColorsByValue([
|
|
160
|
+
'var(--tt-color-highlight-green)',
|
|
161
|
+
'var(--tt-color-highlight-blue)',
|
|
162
|
+
'var(--tt-color-highlight-red)',
|
|
163
|
+
'var(--tt-color-highlight-purple)',
|
|
164
|
+
'var(--tt-color-highlight-yellow)'
|
|
165
|
+
]),
|
|
166
|
+
hideWhenUnavailable = false,
|
|
167
|
+
useColorValue = false,
|
|
168
|
+
onApplied,
|
|
169
|
+
...props
|
|
170
|
+
}: ColorHighlightPopoverProps) {
|
|
171
|
+
const { editor } = useTiptapEditor(providedEditor)
|
|
172
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
173
|
+
const { isVisible, canColorHighlight, isActive, label, Icon } = useColorHighlight({
|
|
174
|
+
editor,
|
|
175
|
+
hideWhenUnavailable,
|
|
176
|
+
onApplied
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
if (!isVisible) return null
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
|
183
|
+
<PopoverTrigger asChild>
|
|
184
|
+
<ColorHighlightPopoverButton
|
|
185
|
+
disabled={!canColorHighlight}
|
|
186
|
+
data-active-state={isActive ? 'on' : 'off'}
|
|
187
|
+
data-disabled={!canColorHighlight}
|
|
188
|
+
aria-pressed={isActive}
|
|
189
|
+
aria-label={label}
|
|
190
|
+
tooltip={label}
|
|
191
|
+
{...props}
|
|
192
|
+
>
|
|
193
|
+
<Icon className="tiptap-button-icon" />
|
|
194
|
+
</ColorHighlightPopoverButton>
|
|
195
|
+
</PopoverTrigger>
|
|
196
|
+
<PopoverContent aria-label="Highlight colors">
|
|
197
|
+
<ColorHighlightPopoverContent
|
|
198
|
+
editor={editor}
|
|
199
|
+
colors={colors}
|
|
200
|
+
useColorValue={useColorValue}
|
|
201
|
+
/>
|
|
202
|
+
</PopoverContent>
|
|
203
|
+
</Popover>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export default ColorHighlightPopover
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './color-highlight-popover'
|