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,369 @@
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 {calculateZoomLevel} from '@lexical/utils';
12
+ import {useEffect, useMemo, useRef, useState} from 'react';
13
+ import * as React from 'react';
14
+
15
+ import Styles from './ColorPicker.module.less';
16
+ import TextInput from './TextInput';
17
+
18
+ let skipAddingToHistoryStack = false;
19
+
20
+ interface ColorPickerProps {
21
+ color: string;
22
+ onChange?: (value: string, skipHistoryStack: boolean) => void;
23
+ }
24
+
25
+ export function parseAllowedColor(input: string) {
26
+ return /^rgb\(\d+, \d+, \d+\)$/.test(input) ? input : '';
27
+ }
28
+
29
+ const basicColors = [
30
+ '#d0021b',
31
+ '#f5a623',
32
+ '#f8e71c',
33
+ '#8b572a',
34
+ '#7ed321',
35
+ '#417505',
36
+ '#bd10e0',
37
+ '#9013fe',
38
+ '#4a90e2',
39
+ '#50e3c2',
40
+ '#b8e986',
41
+ '#000000',
42
+ '#4a4a4a',
43
+ '#9b9b9b',
44
+ '#ffffff',
45
+ ];
46
+
47
+ const WIDTH = 214;
48
+ const HEIGHT = 150;
49
+
50
+ export default function ColorPicker({
51
+ color,
52
+ onChange,
53
+ }: Readonly<ColorPickerProps>): JSX.Element {
54
+ const [selfColor, setSelfColor] = useState(transformColor('hex', color));
55
+ const [inputColor, setInputColor] = useState(color);
56
+ const innerDivRef = useRef(null);
57
+
58
+ const saturationPosition = useMemo(
59
+ () => ({
60
+ x: (selfColor.hsv.s / 100) * WIDTH,
61
+ y: ((100 - selfColor.hsv.v) / 100) * HEIGHT,
62
+ }),
63
+ [selfColor.hsv.s, selfColor.hsv.v],
64
+ );
65
+
66
+ const huePosition = useMemo(
67
+ () => ({
68
+ x: (selfColor.hsv.h / 360) * WIDTH,
69
+ }),
70
+ [selfColor.hsv],
71
+ );
72
+
73
+ const onSetHex = (hex: string) => {
74
+ setInputColor(hex);
75
+ if (/^#[0-9A-Fa-f]{6}$/i.test(hex)) {
76
+ const newColor = transformColor('hex', hex);
77
+ setSelfColor(newColor);
78
+ }
79
+ };
80
+
81
+ const onMoveSaturation = ({x, y}: Position) => {
82
+ const newHsv = {
83
+ ...selfColor.hsv,
84
+ s: (x / WIDTH) * 100,
85
+ v: 100 - (y / HEIGHT) * 100,
86
+ };
87
+ const newColor = transformColor('hsv', newHsv);
88
+ setSelfColor(newColor);
89
+ setInputColor(newColor.hex);
90
+ };
91
+
92
+ const onMoveHue = ({x}: Position) => {
93
+ const newHsv = {...selfColor.hsv, h: (x / WIDTH) * 360};
94
+ const newColor = transformColor('hsv', newHsv);
95
+
96
+ setSelfColor(newColor);
97
+ setInputColor(newColor.hex);
98
+ };
99
+
100
+ useEffect(() => {
101
+ // Check if the dropdown is actually active
102
+ if (innerDivRef.current !== null && onChange) {
103
+ onChange(selfColor.hex, skipAddingToHistoryStack);
104
+ setInputColor(selfColor.hex);
105
+ }
106
+ }, [selfColor, onChange]);
107
+
108
+ useEffect(() => {
109
+ if (color === undefined) {
110
+ return;
111
+ }
112
+ const newColor = transformColor('hex', color);
113
+ setSelfColor(newColor);
114
+ setInputColor(newColor.hex);
115
+ }, [color]);
116
+
117
+ return (
118
+ <div
119
+ className={Styles['color-picker-wrapper']}
120
+ style={{width: WIDTH}}
121
+ ref={innerDivRef}>
122
+ <TextInput label="Hex" onChange={onSetHex} value={inputColor} />
123
+ <div className="color-picker-basic-color">
124
+ {basicColors.map((basicColor) => (
125
+ <button
126
+ className={basicColor === selfColor.hex ? ' active' : ''}
127
+ key={basicColor}
128
+ style={{backgroundColor: basicColor}}
129
+ onClick={() => {
130
+ setInputColor(basicColor);
131
+ setSelfColor(transformColor('hex', basicColor));
132
+ }}
133
+ />
134
+ ))}
135
+ </div>
136
+ <MoveWrapper
137
+ className="color-picker-saturation"
138
+ style={{backgroundColor: `hsl(${selfColor.hsv.h}, 100%, 50%)`}}
139
+ onChange={onMoveSaturation}>
140
+ <div
141
+ className="color-picker-saturation_cursor"
142
+ style={{
143
+ backgroundColor: selfColor.hex,
144
+ left: saturationPosition.x,
145
+ top: saturationPosition.y,
146
+ }}
147
+ />
148
+ </MoveWrapper>
149
+ <MoveWrapper className="color-picker-hue" onChange={onMoveHue}>
150
+ <div
151
+ className="color-picker-hue_cursor"
152
+ style={{
153
+ backgroundColor: `hsl(${selfColor.hsv.h}, 100%, 50%)`,
154
+ left: huePosition.x,
155
+ }}
156
+ />
157
+ </MoveWrapper>
158
+ <div
159
+ className="color-picker-color"
160
+ style={{backgroundColor: selfColor.hex}}
161
+ />
162
+ </div>
163
+ );
164
+ }
165
+
166
+ export interface Position {
167
+ x: number;
168
+ y: number;
169
+ }
170
+
171
+ interface MoveWrapperProps {
172
+ className?: string;
173
+ style?: React.CSSProperties;
174
+ onChange: (position: Position) => void;
175
+ children: JSX.Element;
176
+ }
177
+
178
+ function MoveWrapper({className, style, onChange, children}: MoveWrapperProps) {
179
+ const divRef = useRef<HTMLDivElement>(null);
180
+ const draggedRef = useRef(false);
181
+
182
+ const move = (e: React.MouseEvent | MouseEvent): void => {
183
+ if (divRef.current) {
184
+ const {current: div} = divRef;
185
+ const {width, height, left, top} = div.getBoundingClientRect();
186
+ const zoom = calculateZoomLevel(div);
187
+ const x = clamp(e.clientX / zoom - left, width, 0);
188
+ const y = clamp(e.clientY / zoom - top, height, 0);
189
+
190
+ onChange({x, y});
191
+ }
192
+ };
193
+
194
+ const onMouseDown = (e: React.MouseEvent): void => {
195
+ if (e.button !== 0) {
196
+ return;
197
+ }
198
+
199
+ move(e);
200
+
201
+ const onMouseMove = (_e: MouseEvent): void => {
202
+ draggedRef.current = true;
203
+ skipAddingToHistoryStack = true;
204
+ move(_e);
205
+ };
206
+
207
+ const onMouseUp = (_e: MouseEvent): void => {
208
+ if (draggedRef.current) {
209
+ skipAddingToHistoryStack = false;
210
+ }
211
+
212
+ document.removeEventListener('mousemove', onMouseMove, false);
213
+ document.removeEventListener('mouseup', onMouseUp, false);
214
+
215
+ move(_e);
216
+ draggedRef.current = false;
217
+ };
218
+
219
+ document.addEventListener('mousemove', onMouseMove, false);
220
+ document.addEventListener('mouseup', onMouseUp, false);
221
+ };
222
+
223
+ return (
224
+ <div
225
+ ref={divRef}
226
+ className={className}
227
+ style={style}
228
+ onMouseDown={onMouseDown}>
229
+ {children}
230
+ </div>
231
+ );
232
+ }
233
+
234
+ function clamp(value: number, max: number, min: number) {
235
+ return value > max ? max : value < min ? min : value;
236
+ }
237
+
238
+ interface RGB {
239
+ b: number;
240
+ g: number;
241
+ r: number;
242
+ }
243
+ interface HSV {
244
+ h: number;
245
+ s: number;
246
+ v: number;
247
+ }
248
+ interface Color {
249
+ hex: string;
250
+ hsv: HSV;
251
+ rgb: RGB;
252
+ }
253
+
254
+ export function toHex(value: string): string {
255
+ if (!value.startsWith('#')) {
256
+ const ctx = document.createElement('canvas').getContext('2d');
257
+
258
+ if (!ctx) {
259
+ throw new Error('2d context not supported or canvas already initialized');
260
+ }
261
+
262
+ ctx.fillStyle = value;
263
+
264
+ return ctx.fillStyle;
265
+ } else if (value.length === 4 || value.length === 5) {
266
+ value = value
267
+ .split('')
268
+ .map((v, i) => (i ? v + v : '#'))
269
+ .join('');
270
+
271
+ return value;
272
+ } else if (value.length === 7 || value.length === 9) {
273
+ return value;
274
+ }
275
+
276
+ return '#000000';
277
+ }
278
+
279
+ function hex2rgb(hex: string): RGB {
280
+ const rbgArr = (
281
+ hex
282
+ .replace(
283
+ /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
284
+ (m, r, g, b) => '#' + r + r + g + g + b + b,
285
+ )
286
+ .substring(1)
287
+ .match(/.{2}/g) || []
288
+ ).map((x) => parseInt(x, 16));
289
+
290
+ return {
291
+ b: rbgArr[2],
292
+ g: rbgArr[1],
293
+ r: rbgArr[0],
294
+ };
295
+ }
296
+
297
+ function rgb2hsv({r, g, b}: RGB): HSV {
298
+ r /= 255;
299
+ g /= 255;
300
+ b /= 255;
301
+
302
+ const max = Math.max(r, g, b);
303
+ const d = max - Math.min(r, g, b);
304
+
305
+ const h = d
306
+ ? (max === r
307
+ ? (g - b) / d + (g < b ? 6 : 0)
308
+ : max === g
309
+ ? 2 + (b - r) / d
310
+ : 4 + (r - g) / d) * 60
311
+ : 0;
312
+ const s = max ? (d / max) * 100 : 0;
313
+ const v = max * 100;
314
+
315
+ return {h, s, v};
316
+ }
317
+
318
+ function hsv2rgb({h, s, v}: HSV): RGB {
319
+ s /= 100;
320
+ v /= 100;
321
+
322
+ const i = ~~(h / 60);
323
+ const f = h / 60 - i;
324
+ const p = v * (1 - s);
325
+ const q = v * (1 - s * f);
326
+ const t = v * (1 - s * (1 - f));
327
+ const index = i % 6;
328
+
329
+ const r = Math.round([v, q, p, p, t, v][index] * 255);
330
+ const g = Math.round([t, v, v, q, p, p][index] * 255);
331
+ const b = Math.round([p, p, t, v, v, q][index] * 255);
332
+
333
+ return {b, g, r};
334
+ }
335
+
336
+ function rgb2hex({b, g, r}: RGB): string {
337
+ return '#' + [r, g, b].map((x) => x.toString(16).padStart(2, '0')).join('');
338
+ }
339
+
340
+ function transformColor<M extends keyof Color, C extends Color[M]>(
341
+ format: M,
342
+ color: C,
343
+ ): Color {
344
+ let hex: Color['hex'] = toHex('#121212');
345
+ let rgb: Color['rgb'] = hex2rgb(hex);
346
+ let hsv: Color['hsv'] = rgb2hsv(rgb);
347
+
348
+ if (format === 'hex') {
349
+ const value = color as Color['hex'];
350
+
351
+ hex = toHex(value);
352
+ rgb = hex2rgb(hex);
353
+ hsv = rgb2hsv(rgb);
354
+ } else if (format === 'rgb') {
355
+ const value = color as Color['rgb'];
356
+
357
+ rgb = value;
358
+ hex = rgb2hex(rgb);
359
+ hsv = rgb2hsv(rgb);
360
+ } else if (format === 'hsv') {
361
+ const value = color as Color['hsv'];
362
+
363
+ hsv = value;
364
+ rgb = hsv2rgb(hsv);
365
+ hex = rgb2hex(rgb);
366
+ }
367
+
368
+ return {hex, hsv, rgb};
369
+ }
@@ -0,0 +1,78 @@
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
+ .ContentEditable__root {
10
+ border: 0;
11
+ font-size: 15px;
12
+ display: block;
13
+ position: relative;
14
+ outline: 0;
15
+ // padding: 8px 46px 40px;
16
+ min-height: 150px;
17
+ & > [instance='true'] {
18
+ position: relative;
19
+ background-color: #f9f9f9;
20
+ border-left: 26px solid #e6e6e6;
21
+ padding: 0 10px;
22
+ margin-bottom: 6px;
23
+ > [number='true'] {
24
+ left: 10px;
25
+ }
26
+ > [bar='true'] {
27
+ & [data-bar='right'] {
28
+ right: 10px;
29
+ }
30
+ }
31
+ & [instance='true'] {
32
+ border-top: 6px solid transparent;
33
+ &::before {
34
+ content: '';
35
+ display: block;
36
+ background-color: #fff;
37
+ height: 6px;
38
+ position: absolute;
39
+ top: -6px;
40
+ left: -36px;
41
+ right: -10px;
42
+
43
+ }
44
+ > [bar='true'] {
45
+ & [data-bar='left'] {
46
+ left: -10px;
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ /* @media (max-width: 1025px) {
53
+ .ContentEditable__root {
54
+ padding-left: 8px;
55
+ padding-right: 8px;
56
+ }
57
+ } */
58
+
59
+ .ContentEditable__placeholder {
60
+ font-size: 15px;
61
+ color: #999;
62
+ overflow: hidden;
63
+ position: absolute;
64
+ text-overflow: ellipsis;
65
+ top: 8px;
66
+ left: 46px;
67
+ right: 28px;
68
+ user-select: none;
69
+ white-space: nowrap;
70
+ display: inline-block;
71
+ pointer-events: none;
72
+ }
73
+ @media (max-width: 1025px) {
74
+ .ContentEditable__placeholder {
75
+ left: 8px;
76
+ right: 8px;
77
+ }
78
+ }
@@ -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 type {JSX} from 'react';
10
+
11
+ import {ContentEditable} from '@lexical/react/LexicalContentEditable';
12
+ import * as React from 'react';
13
+
14
+ import Styles from './ContentEditable.module.less';
15
+
16
+ type Props = {
17
+ className?: string;
18
+ placeholderClassName?: string;
19
+ placeholder: string;
20
+ };
21
+
22
+ export default function LexicalContentEditable({
23
+ className,
24
+ placeholder,
25
+ placeholderClassName,
26
+ }: Props): JSX.Element {
27
+ return (
28
+ <ContentEditable
29
+ className={className ?? Styles.ContentEditable__root}
30
+ aria-placeholder={placeholder}
31
+ placeholder={
32
+ <div
33
+ className={
34
+ placeholderClassName ?? Styles.ContentEditable__placeholder
35
+ }>
36
+ {placeholder}
37
+ </div>
38
+ }
39
+ />
40
+ );
41
+ }
@@ -0,0 +1,23 @@
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
+ .DialogActions {
10
+ display: flex;
11
+ flex-direction: row;
12
+ justify-content: right;
13
+ margin-top: 20px;
14
+ }
15
+
16
+ .DialogButtonsList {
17
+ display: flex;
18
+ flex-direction: column;
19
+ justify-content: right;
20
+ button {
21
+ margin-bottom: 20px;
22
+ }
23
+ }
package/src/Dialog.tsx ADDED
@@ -0,0 +1,34 @@
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 * as React from 'react';
12
+ import {ReactNode} from 'react';
13
+
14
+ import Styles from './Dialog.module.less';
15
+
16
+ type Props = Readonly<{
17
+ 'data-test-id'?: string;
18
+ children: ReactNode;
19
+ }>;
20
+
21
+ export function DialogButtonsList({children}: Props): JSX.Element {
22
+ return <div className={Styles.DialogButtonsList}>{children}</div>;
23
+ }
24
+
25
+ export function DialogActions({
26
+ 'data-test-id': dataTestId,
27
+ children,
28
+ }: Props): JSX.Element {
29
+ return (
30
+ <div className={Styles.DialogActions} data-test-id={dataTestId}>
31
+ {children}
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,95 @@
1
+ .dropdown {
2
+ z-index: 100;
3
+ display: block;
4
+ position: fixed;
5
+ box-shadow:
6
+ 0 12px 28px 0 rgba(0, 0, 0, 0.2),
7
+ 0 2px 4px 0 rgba(0, 0, 0, 0.1),
8
+ inset 0 0 0 1px rgba(255, 255, 255, 0.5);
9
+ // border-radius: 8px;
10
+ min-height: 40px;
11
+ background-color: #fff;
12
+ z-index: 1000;
13
+ :global {
14
+ .item {
15
+ margin: 0 8px 0 8px;
16
+ padding: 8px;
17
+ color: #050505;
18
+ cursor: pointer;
19
+ line-height: 16px;
20
+ font-size: 15px;
21
+ display: flex;
22
+ align-content: center;
23
+ flex-direction: row;
24
+ flex-shrink: 0;
25
+ justify-content: space-between;
26
+ background-color: #fff;
27
+ // border-radius: 8px;
28
+ border: 0;
29
+ max-width: 250px;
30
+ min-width: 100px;
31
+ &:first-child {
32
+ margin-top: 8px;
33
+ }
34
+
35
+ &:last-child {
36
+ margin-bottom: 8px;
37
+ }
38
+
39
+ &:hover {
40
+ background-color: #eee;
41
+ }
42
+
43
+ &.wide {
44
+ align-items: center;
45
+ width: 248px;
46
+
47
+ .icon-text-container {
48
+ display: flex;
49
+
50
+ .text {
51
+ min-width: 120px;
52
+ }
53
+ }
54
+ }
55
+
56
+ .shortcut {
57
+ color: #939393;
58
+ align-self: flex-end;
59
+ }
60
+
61
+ .active {
62
+ display: flex;
63
+ width: 20px;
64
+ height: 20px;
65
+ background-size: contain;
66
+ }
67
+
68
+ .text {
69
+ display: flex;
70
+ line-height: 20px;
71
+ flex-grow: 1;
72
+ min-width: 150px;
73
+ }
74
+
75
+ .icon {
76
+ display: flex;
77
+ width: 20px;
78
+ height: 20px;
79
+ user-select: none;
80
+ margin-right: 12px;
81
+ line-height: 16px;
82
+ background-size: contain;
83
+ background-position: center;
84
+ background-repeat: no-repeat;
85
+ }
86
+ }
87
+
88
+ .divider {
89
+ width: auto;
90
+ background-color: #eee;
91
+ margin: 4px 8px;
92
+ height: 1px;
93
+ }
94
+ }
95
+ }