onchain-lexical-ui 0.0.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.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type {JSX} from 'react';
10
+
11
+ import {isDOMNode} from 'lexical';
12
+ import * as React from 'react';
13
+ import {
14
+ ReactNode,
15
+ useCallback,
16
+ useEffect,
17
+ useMemo,
18
+ useRef,
19
+ useState,
20
+ } from 'react';
21
+ import {createPortal} from 'react-dom';
22
+
23
+ import Styles from './DropDown.module.less';
24
+
25
+ type DropDownContextType = {
26
+ registerItem: (ref: React.RefObject<HTMLButtonElement>) => void;
27
+ };
28
+
29
+ const DropDownContext = React.createContext<DropDownContextType | null>(null);
30
+
31
+ const dropDownPadding = 4;
32
+
33
+ export function DropDownItem({
34
+ children,
35
+ className,
36
+ onClick,
37
+ title,
38
+ }: {
39
+ children: React.ReactNode;
40
+ className: string;
41
+ onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
42
+ title?: string;
43
+ }) {
44
+ const ref = useRef<HTMLButtonElement>(null);
45
+
46
+ const dropDownContext = React.useContext(DropDownContext);
47
+
48
+ if (dropDownContext === null) {
49
+ throw new Error('DropDownItem must be used within a DropDown');
50
+ }
51
+
52
+ const {registerItem} = dropDownContext;
53
+
54
+ useEffect(() => {
55
+ if (ref && ref.current) {
56
+ registerItem(ref);
57
+ }
58
+ }, [ref, registerItem]);
59
+
60
+ return (
61
+ <button
62
+ className={className}
63
+ onClick={onClick}
64
+ ref={ref}
65
+ title={title}
66
+ type="button">
67
+ {children}
68
+ </button>
69
+ );
70
+ }
71
+
72
+ function DropDownItems({
73
+ children,
74
+ dropDownRef,
75
+ onClose,
76
+ }: {
77
+ children: React.ReactNode;
78
+ dropDownRef: React.Ref<HTMLDivElement>;
79
+ onClose: () => void;
80
+ }) {
81
+ const [items, setItems] = useState<React.RefObject<HTMLButtonElement>[]>();
82
+ const [highlightedItem, setHighlightedItem] =
83
+ useState<React.RefObject<HTMLButtonElement>>();
84
+
85
+ const registerItem = useCallback(
86
+ (itemRef: React.RefObject<HTMLButtonElement>) => {
87
+ setItems((prev) => (prev ? [...prev, itemRef] : [itemRef]));
88
+ },
89
+ [setItems],
90
+ );
91
+
92
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
93
+ if (!items) {
94
+ return;
95
+ }
96
+
97
+ const key = event.key;
98
+
99
+ if (['Escape', 'ArrowUp', 'ArrowDown', 'Tab'].includes(key)) {
100
+ event.preventDefault();
101
+ }
102
+
103
+ if (key === 'Escape' || key === 'Tab') {
104
+ onClose();
105
+ } else if (key === 'ArrowUp') {
106
+ setHighlightedItem((prev) => {
107
+ if (!prev) {
108
+ return items[0];
109
+ }
110
+ const index = items.indexOf(prev) - 1;
111
+ return items[index === -1 ? items.length - 1 : index];
112
+ });
113
+ } else if (key === 'ArrowDown') {
114
+ setHighlightedItem((prev) => {
115
+ if (!prev) {
116
+ return items[0];
117
+ }
118
+ return items[items.indexOf(prev) + 1];
119
+ });
120
+ }
121
+ };
122
+
123
+ const contextValue = useMemo(
124
+ () => ({
125
+ registerItem,
126
+ }),
127
+ [registerItem],
128
+ );
129
+
130
+ useEffect(() => {
131
+ if (items && !highlightedItem) {
132
+ setHighlightedItem(items[0]);
133
+ }
134
+
135
+ if (highlightedItem && highlightedItem.current) {
136
+ highlightedItem.current.focus();
137
+ }
138
+ }, [items, highlightedItem]);
139
+
140
+ return (
141
+ <DropDownContext.Provider value={contextValue}>
142
+ <div
143
+ className={`${Styles.dropdown}`}
144
+ ref={dropDownRef}
145
+ onKeyDown={handleKeyDown}>
146
+ {children}
147
+ </div>
148
+ </DropDownContext.Provider>
149
+ );
150
+ }
151
+
152
+ export default function DropDown({
153
+ disabled = false,
154
+ buttonLabel,
155
+ buttonAriaLabel,
156
+ buttonClassName,
157
+ buttonIconClassName,
158
+ children,
159
+ stopCloseOnClickSelf,
160
+ }: {
161
+ disabled?: boolean;
162
+ buttonAriaLabel?: string;
163
+ buttonClassName: string;
164
+ buttonIconClassName?: string;
165
+ buttonLabel?: string;
166
+ children: ReactNode;
167
+ stopCloseOnClickSelf?: boolean;
168
+ }): JSX.Element {
169
+ const dropDownRef = useRef<HTMLDivElement>(null);
170
+ const buttonRef = useRef<HTMLButtonElement>(null);
171
+ const [showDropDown, setShowDropDown] = useState(false);
172
+
173
+ const handleClose = () => {
174
+ setShowDropDown(false);
175
+ if (buttonRef && buttonRef.current) {
176
+ buttonRef.current.focus();
177
+ }
178
+ };
179
+
180
+ useEffect(() => {
181
+ const button = buttonRef.current;
182
+ const dropDown = dropDownRef.current;
183
+
184
+ if (showDropDown && button !== null && dropDown !== null) {
185
+ const {top, left} = button.getBoundingClientRect();
186
+ dropDown.style.top = `${top + button.offsetHeight + dropDownPadding}px`;
187
+ dropDown.style.left = `${Math.min(
188
+ left,
189
+ window.innerWidth - dropDown.offsetWidth - 20,
190
+ )}px`;
191
+ }
192
+ }, [dropDownRef, buttonRef, showDropDown]);
193
+
194
+ useEffect(() => {
195
+ const button = buttonRef.current;
196
+
197
+ if (button !== null && showDropDown) {
198
+ const handle = (event: MouseEvent) => {
199
+ const target = event.target;
200
+ if (!isDOMNode(target)) {
201
+ return;
202
+ }
203
+ if (stopCloseOnClickSelf) {
204
+ if (dropDownRef.current && dropDownRef.current.contains(target)) {
205
+ return;
206
+ }
207
+ }
208
+ if (!button.contains(target)) {
209
+ setShowDropDown(false);
210
+ }
211
+ };
212
+ document.addEventListener('click', handle);
213
+
214
+ return () => {
215
+ document.removeEventListener('click', handle);
216
+ };
217
+ }
218
+ }, [dropDownRef, buttonRef, showDropDown, stopCloseOnClickSelf]);
219
+
220
+ useEffect(() => {
221
+ const handleButtonPositionUpdate = () => {
222
+ if (showDropDown) {
223
+ const button = buttonRef.current;
224
+ const dropDown = dropDownRef.current;
225
+ if (button !== null && dropDown !== null) {
226
+ const {top} = button.getBoundingClientRect();
227
+ const newPosition = top + button.offsetHeight + dropDownPadding;
228
+ if (newPosition !== dropDown.getBoundingClientRect().top) {
229
+ dropDown.style.top = `${newPosition}px`;
230
+ }
231
+ }
232
+ }
233
+ };
234
+
235
+ document.addEventListener('scroll', handleButtonPositionUpdate);
236
+
237
+ return () => {
238
+ document.removeEventListener('scroll', handleButtonPositionUpdate);
239
+ };
240
+ }, [buttonRef, dropDownRef, showDropDown]);
241
+
242
+ return (
243
+ <>
244
+ <button
245
+ type="button"
246
+ disabled={disabled}
247
+ aria-label={buttonAriaLabel || buttonLabel}
248
+ className={buttonClassName}
249
+ onClick={() => setShowDropDown(!showDropDown)}
250
+ ref={buttonRef}>
251
+ {buttonIconClassName && <span className={buttonIconClassName} />}
252
+ {buttonLabel && (
253
+ <span className="text dropdown-button-text">{buttonLabel}</span>
254
+ )}
255
+ <i className="chevron-down" />
256
+ </button>
257
+
258
+ {showDropDown &&
259
+ createPortal(
260
+ <DropDownItems dropDownRef={dropDownRef} onClose={handleClose}>
261
+ {children}
262
+ </DropDownItems>,
263
+ document.body,
264
+ )}
265
+ </>
266
+ );
267
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import * as React from 'react';
10
+
11
+ import ColorPicker from './ColorPicker';
12
+ import DropDown from './DropDown';
13
+
14
+ type Props = {
15
+ disabled?: boolean;
16
+ buttonAriaLabel?: string;
17
+ buttonClassName: string;
18
+ buttonIconClassName?: string;
19
+ buttonLabel?: string;
20
+ title?: string;
21
+ stopCloseOnClickSelf?: boolean;
22
+ color: string;
23
+ onChange?: (color: string, skipHistoryStack: boolean) => void;
24
+ };
25
+
26
+ export default function DropdownColorPicker({
27
+ disabled = false,
28
+ stopCloseOnClickSelf = true,
29
+ color,
30
+ onChange,
31
+ ...rest
32
+ }: Props) {
33
+ return (
34
+ <DropDown
35
+ {...rest}
36
+ disabled={disabled}
37
+ stopCloseOnClickSelf={stopCloseOnClickSelf}>
38
+ <ColorPicker color={color} onChange={onChange} />
39
+ </DropDown>
40
+ );
41
+ }
@@ -0,0 +1,43 @@
1
+ .editor-shell {
2
+ width: 100%;
3
+ border-radius: 2px;
4
+ max-width: 1100px;
5
+ color: #000;
6
+ position: relative;
7
+ font-weight: 400;
8
+
9
+ :global {
10
+ .editor-container {
11
+ background: #fff;
12
+ position: relative;
13
+ display: block;
14
+ // border-bottom-left-radius: 10px;
15
+ // border-bottom-right-radius: 10px;
16
+ max-height: calc(100vh - 200px);
17
+ overflow-y: auto;
18
+ }
19
+
20
+ .editor-container.tree-view {
21
+ border-radius: 0;
22
+ }
23
+
24
+ .editor-container.plain-text {
25
+ border-top-left-radius: 10px;
26
+ border-top-right-radius: 10px;
27
+ }
28
+ .chevron-down {
29
+ background-color: transparent;
30
+ background-size: contain;
31
+ display: inline-block;
32
+ height: 8px;
33
+ width: 8px;
34
+ }
35
+ .keyword {
36
+ color: rgb(241, 118, 94);
37
+ font-weight: bold;
38
+ }
39
+ }
40
+ }
41
+ div.editor-shell {
42
+ line-height: 1.7;
43
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import * as React from 'react';
9
+
10
+ import Styles from './index.module.less';
11
+
12
+ const EditorShellStyles: React.FC<{
13
+ children: React.ReactNode;
14
+ }> = ({children}) => {
15
+ return <div className={Styles['editor-shell']}>{children}</div>;
16
+ };
17
+
18
+ export default EditorShellStyles;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ *
8
+ */
9
+
10
+ .EquationEditor_inlineEditor {
11
+ padding: 0;
12
+ margin: 0;
13
+ border: 0;
14
+ outline: 0;
15
+ color: #8421a2;
16
+ background-color: inherit;
17
+ resize: none;
18
+ }
19
+
20
+ .EquationEditor_blockEditor {
21
+ padding: 0;
22
+ margin: 0;
23
+ border: 0;
24
+ outline: 0;
25
+ color: #8421a2;
26
+ background-color: inherit;
27
+ resize: none;
28
+ width: 100%;
29
+ }
30
+
31
+ .EquationEditor_inputBackground {
32
+ background-color: #eee;
33
+ padding: 0 4px;
34
+ margin: 0 2px;
35
+ border-radius: 2px;
36
+ }
37
+
38
+ .EquationEditor_dollarSign {
39
+ text-align: left;
40
+ color: #b0b0b0;
41
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type {JSX, Ref, RefObject} from 'react';
10
+
11
+ import {ChangeEvent, forwardRef} from 'react';
12
+
13
+ import Styles from './EquationEditor.module.less';
14
+
15
+ type BaseEquationEditorProps = {
16
+ equation: string;
17
+ inline: boolean;
18
+ setEquation: (equation: string) => void;
19
+ };
20
+
21
+ function EquationEditor(
22
+ {equation, setEquation, inline}: BaseEquationEditorProps,
23
+ forwardedRef: Ref<HTMLInputElement | HTMLTextAreaElement>,
24
+ ): JSX.Element {
25
+ const onChange = (event: ChangeEvent) => {
26
+ setEquation((event.target as HTMLInputElement).value);
27
+ };
28
+
29
+ return (
30
+ <div className={Styles.EquationEditor_inputBackground}>
31
+ <span className={Styles.EquationEditor_dollarSign}>
32
+ {inline ? '' : '$'}
33
+ {'$'}
34
+ </span>
35
+ <textarea
36
+ className={Styles.EquationEditor_blockEditor}
37
+ value={equation}
38
+ onChange={onChange}
39
+ ref={forwardedRef as RefObject<HTMLTextAreaElement>}
40
+ />
41
+ <span className={Styles.EquationEditor_dollarSign}>
42
+ {'$'}
43
+ {inline ? '' : '$'}
44
+ </span>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ export default forwardRef(EquationEditor);
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ *
8
+ */
9
+
10
+ .ExcalidrawModal__overlay {
11
+ display: flex;
12
+ align-items: center;
13
+ position: fixed;
14
+ flex-direction: column;
15
+ top: 0px;
16
+ bottom: 0px;
17
+ left: 0px;
18
+ right: 0px;
19
+ flex-grow: 0px;
20
+ flex-shrink: 1px;
21
+ z-index: 100;
22
+ background-color: rgba(40, 40, 40, 0.6);
23
+ }
24
+ .ExcalidrawModal__actions {
25
+ text-align: end;
26
+ position: absolute;
27
+ right: 5px;
28
+ top: 5px;
29
+ z-index: 1;
30
+ }
31
+ .ExcalidrawModal__actions button {
32
+ background-color: #fff;
33
+ border-radius: 5px;
34
+ }
35
+ .ExcalidrawModal__row {
36
+ position: relative;
37
+ padding: 40px 5px 5px;
38
+ width: 70vw;
39
+ height: 70vh;
40
+ border-radius: 8px;
41
+ box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1),
42
+ inset 0 0 0 1px rgba(255, 255, 255, 0.5);
43
+ }
44
+ .ExcalidrawModal__row > div {
45
+ border-radius: 5px;
46
+ }
47
+ .ExcalidrawModal__modal {
48
+ position: relative;
49
+ z-index: 10;
50
+ top: 50px;
51
+ width: auto;
52
+ left: 0;
53
+ display: flex;
54
+ justify-content: center;
55
+ align-items: center;
56
+ border-radius: 8px;
57
+ background-color: #eee;
58
+ }
59
+ .ExcalidrawModal__discardModal {
60
+ margin-top: 60px;
61
+ text-align: center;
62
+ }