@valbuild/ui 0.18.0 → 0.19.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.
@@ -1,14 +1,21 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
2
  import { AlignJustify, X } from "react-feather";
3
3
  import classNames from "classnames";
4
+ import { Resizable } from "react-resizable";
4
5
 
5
6
  export type ValWindowProps = {
6
- children: React.ReactNode;
7
+ children:
8
+ | [React.ReactNode, React.ReactNode, React.ReactNode]
9
+ | React.ReactNode;
10
+
7
11
  onClose: () => void;
8
12
  position?: { left: number; top: number };
9
13
  isInitialized?: true;
10
14
  };
11
15
 
16
+ const MIN_WIDTH = 320;
17
+ const MIN_HEIGHT = 320;
18
+
12
19
  export function ValWindow({
13
20
  position,
14
21
  onClose,
@@ -30,99 +37,94 @@ export function ValWindow({
30
37
  }, []);
31
38
 
32
39
  //
33
- const [size, resizeRef, onMouseDownResize] = useResize();
40
+ const [size, setSize] = useState<{ height: number; width: number }>();
41
+ //
42
+ const bottomRef = useRef<HTMLDivElement>(null);
34
43
 
35
44
  return (
36
- <div
45
+ <Resizable
46
+ width={size?.width || MIN_WIDTH}
47
+ height={size?.height || MIN_HEIGHT}
48
+ onResize={(_, { size }) => setSize(size)}
49
+ handle={
50
+ <div className="fixed bottom-0 right-0 cursor-se-resize">
51
+ <svg
52
+ height="18"
53
+ viewBox="0 0 18 18"
54
+ width="18"
55
+ xmlns="http://www.w3.org/2000/svg"
56
+ >
57
+ <path
58
+ d="m14.228 16.227a1 1 0 0 1 -.707-1.707l1-1a1 1 0 0 1 1.416 1.414l-1 1a1 1 0 0 1 -.707.293zm-5.638 0a1 1 0 0 1 -.707-1.707l6.638-6.638a1 1 0 0 1 1.416 1.414l-6.638 6.638a1 1 0 0 1 -.707.293zm-5.84 0a1 1 0 0 1 -.707-1.707l12.477-12.477a1 1 0 1 1 1.415 1.414l-12.478 12.477a1 1 0 0 1 -.707.293z"
59
+ fill="currentColor"
60
+ />
61
+ </svg>
62
+ </div>
63
+ }
64
+ draggableOpts={{}}
37
65
  className={classNames(
38
- "absolute inset-0 h-[100svh] w-full tablet:w-auto tablet:h-auto tablet:min-h-fit tablet:rounded bg-base text-primary drop-shadow-2xl min-w-[320px] transition-opacity duration-300 delay-75 max-w-full",
66
+ "absolute inset-0 w-full tablet:w-auto tablet:h-auto tablet:min-h-fit tablet:rounded bg-base text-primary drop-shadow-2xl min-w-[320px] transition-opacity duration-300 delay-75 max-w-full",
39
67
  {
40
68
  "opacity-0": !isInitialized,
41
69
  "opacity-100": isInitialized,
42
70
  }
43
71
  )}
44
- ref={resizeRef}
45
- style={{
46
- left: draggedPosition.left,
47
- top: draggedPosition.top,
48
- width: size?.width || 320,
49
- height: size?.height || 320,
50
- }}
51
72
  >
52
73
  <div
53
- ref={dragRef}
54
- className="relative flex justify-center px-2 pt-2 text-primary"
55
- >
56
- <AlignJustify
57
- size={16}
58
- className="hidden w-full cursor-grab tablet:block"
59
- onMouseDown={(e) => {
60
- e.preventDefault();
61
- e.stopPropagation();
62
- onMouseDownDrag();
63
- }}
64
- />
65
- <button
66
- className="absolute top-0 right-0 px-4 py-2 focus:outline-none focus-visible:outline-highlight"
67
- onClick={onClose}
68
- >
69
- <X size={16} />
70
- </button>
71
- </div>
72
- <div style={{ height: (size?.height || 320) - 64 }}>{children}</div>
73
- <div
74
- className="absolute bottom-0 right-0 hidden ml-auto select-none tablet:block text-border cursor-nwse-resize"
75
74
  style={{
76
- height: 16,
77
- width: 16,
75
+ width: size?.width || MIN_WIDTH,
76
+ height: size?.height || MIN_HEIGHT,
77
+ left: draggedPosition.left,
78
+ top: draggedPosition.top,
78
79
  }}
79
- onMouseDown={onMouseDownResize}
80
80
  >
81
- <svg
82
- height="18"
83
- viewBox="0 0 18 18"
84
- width="18"
85
- xmlns="http://www.w3.org/2000/svg"
81
+ <div
82
+ ref={dragRef}
83
+ className="relative flex items-center justify-center px-2 pt-2 text-primary"
86
84
  >
87
- <path
88
- d="m14.228 16.227a1 1 0 0 1 -.707-1.707l1-1a1 1 0 0 1 1.416 1.414l-1 1a1 1 0 0 1 -.707.293zm-5.638 0a1 1 0 0 1 -.707-1.707l6.638-6.638a1 1 0 0 1 1.416 1.414l-6.638 6.638a1 1 0 0 1 -.707.293zm-5.84 0a1 1 0 0 1 -.707-1.707l12.477-12.477a1 1 0 1 1 1.415 1.414l-12.478 12.477a1 1 0 0 1 -.707.293z"
89
- fill="currentColor"
85
+ <AlignJustify
86
+ size={16}
87
+ className="hidden w-full cursor-grab tablet:block"
88
+ onMouseDown={(e) => {
89
+ e.preventDefault();
90
+ e.stopPropagation();
91
+ onMouseDownDrag();
92
+ }}
90
93
  />
91
- </svg>
94
+ <button
95
+ className="absolute top-0 right-0 px-4 py-2 focus:outline-none focus-visible:outline-highlight"
96
+ onClick={onClose}
97
+ >
98
+ <X size={16} />
99
+ </button>
100
+ </div>
101
+ <form
102
+ className="h-full"
103
+ onSubmit={(ev) => {
104
+ ev.preventDefault();
105
+ }}
106
+ >
107
+ {Array.isArray(children) && children.slice(0, 1)}
108
+ <div
109
+ className="relative overflow-scroll"
110
+ style={{
111
+ height:
112
+ (size?.height || MIN_HEIGHT) -
113
+ (64 +
114
+ (bottomRef.current?.getBoundingClientRect()?.height || 0)),
115
+ }}
116
+ >
117
+ {Array.isArray(children) ? children.slice(1, -1) : children}
118
+ </div>
119
+ <div ref={bottomRef} className="w-full px-4 pb-0">
120
+ {Array.isArray(children) && children.slice(-1)}
121
+ </div>
122
+ </form>
92
123
  </div>
93
- </div>
124
+ </Resizable>
94
125
  );
95
126
  }
96
127
 
97
- function useResize() {
98
- const ref = useRef<HTMLDivElement>(null);
99
- const [size, setSize] = useState<{ height: number; width: number }>();
100
-
101
- const handler = (mouseDownEvent: React.MouseEvent) => {
102
- const startSize = ref.current?.getBoundingClientRect();
103
-
104
- const startPosition = { x: mouseDownEvent.pageX, y: mouseDownEvent.pageY };
105
- function onMouseMove(mouseMoveEvent: MouseEvent) {
106
- if (startSize) {
107
- const nextWidth =
108
- startSize.width - startPosition.x + mouseMoveEvent.pageX;
109
- const nextHeight =
110
- startSize.height - startPosition.y + mouseMoveEvent.pageY;
111
- setSize({
112
- width: nextWidth > 320 ? nextWidth : 320,
113
- height: nextHeight > 320 ? nextHeight : 320,
114
- });
115
- }
116
- }
117
- function onMouseUp() {
118
- document.body.removeEventListener("mousemove", onMouseMove);
119
- }
120
- document.body.addEventListener("mousemove", onMouseMove);
121
- document.body.addEventListener("mouseup", onMouseUp, { once: true });
122
- };
123
- return [size, ref, handler] as const;
124
- }
125
-
126
128
  function useDrag({
127
129
  position: initPosition,
128
130
  }: {
@@ -140,8 +142,8 @@ function useDrag({
140
142
  top: top < 0 ? 0 : top,
141
143
  });
142
144
  } else {
143
- const left = window.innerWidth / 2 - 320 / 2 - window.scrollX;
144
- const top = window.innerHeight / 2 - 320 / 2 + window.scrollY;
145
+ const left = window.innerWidth / 2 - MIN_WIDTH / 2 - window.scrollX;
146
+ const top = window.innerHeight / 2 - MIN_HEIGHT / 2 + window.scrollY;
145
147
  setPosition({
146
148
  left,
147
149
  top,
@@ -182,7 +184,7 @@ function useDrag({
182
184
  // TODO: rename hook from useDrag to usePosition or something since we also check for screen width here?
183
185
  useEffect(() => {
184
186
  const onResize = () => {
185
- if (window.screen.width < 640) {
187
+ if (window.screen.width < MIN_WIDTH * 2) {
186
188
  setPosition({
187
189
  left: 0,
188
190
  top: 0,
@@ -1,4 +1,4 @@
1
- import { RichText } from "@valbuild/core";
1
+ import { AnyRichTextOptions, RichText } from "@valbuild/core";
2
2
  import { LexicalEditor } from "lexical";
3
3
  import { useEffect, useState } from "react";
4
4
  import { RichTextEditor } from "../RichTextEditor/RichTextEditor";
@@ -15,7 +15,11 @@ export type Inputs = {
15
15
  data: TextData;
16
16
  }
17
17
  | { status: "completed"; type: "image"; data: ImageData }
18
- | { status: "completed"; type: "richtext"; data: RichText };
18
+ | {
19
+ status: "completed";
20
+ type: "richtext";
21
+ data: RichText<AnyRichTextOptions>;
22
+ };
19
23
  };
20
24
 
21
25
  export type FormProps = {
@@ -1,6 +1,6 @@
1
1
  export type TextData = string;
2
2
  export type TextInputProps = {
3
- name: string;
3
+ name?: string;
4
4
  text: TextData;
5
5
  disabled?: boolean;
6
6
  onChange: (value: string) => void;
@@ -3,7 +3,7 @@ import {
3
3
  RichTextEditorProps,
4
4
  } from "../components/RichTextEditor/RichTextEditor";
5
5
  import { Meta, Story } from "@storybook/react";
6
- import { RichText, SourcePath } from "@valbuild/core";
6
+ import { AnyRichTextOptions, RichText } from "@valbuild/core";
7
7
 
8
8
  export default {
9
9
  title: "RichTextEditor",
@@ -18,299 +18,51 @@ export const DropdownStory = Template.bind({});
18
18
 
19
19
  DropdownStory.args = {
20
20
  richtext: {
21
- valPath: "/test" as SourcePath,
21
+ _type: "richtext",
22
22
  children: [
23
+ { tag: "h1", children: ["Title 1"] },
24
+ { tag: "h2", children: ["Title 2"] },
25
+ { tag: "h3", children: ["Title 3"] },
26
+ { tag: "h4", children: ["Title 4"] },
27
+ { tag: "h5", children: ["Title 5"] },
28
+ { tag: "h6", children: ["Title 6"] },
23
29
  {
30
+ tag: "p",
24
31
  children: [
25
32
  {
26
- detail: 0,
27
- format: 0,
28
- mode: "normal",
29
- style: "",
30
- text: "Heading 1",
31
- type: "text",
32
- version: 1,
33
+ tag: "span",
34
+ classes: ["bold", "italic", "line-through"],
35
+ children: ["Formatted span"],
33
36
  },
34
37
  ],
35
- direction: "ltr",
36
- format: "",
37
- indent: 0,
38
- type: "heading",
39
- version: 1,
40
- tag: "h1",
41
- },
42
- {
43
- children: [
44
- {
45
- detail: 0,
46
- format: 0,
47
- mode: "normal",
48
- style: "",
49
- text: "Heading 2",
50
- type: "text",
51
- version: 1,
52
- },
53
- ],
54
- direction: "ltr",
55
- format: "",
56
- indent: 0,
57
- type: "heading",
58
- version: 1,
59
- tag: "h2",
60
- },
61
- {
62
- children: [],
63
- direction: "ltr",
64
- format: "",
65
- indent: 0,
66
- type: "paragraph",
67
- version: 1,
68
- },
69
- {
70
- children: [
71
- {
72
- detail: 0,
73
- format: 0,
74
- mode: "normal",
75
- style: "",
76
- text: "Normal",
77
- type: "text",
78
- version: 1,
79
- },
80
- ],
81
- direction: "ltr",
82
- format: "",
83
- indent: 0,
84
- type: "paragraph",
85
- version: 1,
86
- },
87
- {
88
- children: [
89
- {
90
- detail: 0,
91
- format: 0,
92
- mode: "normal",
93
- style: "font-size: 20px;",
94
- text: "Normal Font size 20",
95
- type: "text",
96
- version: 1,
97
- },
98
- ],
99
- direction: "ltr",
100
- format: "",
101
- indent: 0,
102
- type: "paragraph",
103
- version: 1,
104
- },
105
- {
106
- children: [
107
- {
108
- detail: 0,
109
- format: 0,
110
- mode: "normal",
111
- style: "font-family: serif;",
112
- text: "Normal font type serif",
113
- type: "text",
114
- version: 1,
115
- },
116
- ],
117
- direction: "ltr",
118
- format: "",
119
- indent: 0,
120
- type: "paragraph",
121
- version: 1,
122
- },
123
- {
124
- children: [
125
- {
126
- detail: 0,
127
- format: 1,
128
- mode: "normal",
129
- style: "font-family: serif;",
130
- text: "Serif and bold",
131
- type: "text",
132
- version: 1,
133
- },
134
- ],
135
- direction: "ltr",
136
- format: "",
137
- indent: 0,
138
- type: "paragraph",
139
- version: 1,
140
- },
141
- {
142
- children: [
143
- {
144
- detail: 0,
145
- format: 2,
146
- mode: "normal",
147
- style: "",
148
- text: "Arial and italic",
149
- type: "text",
150
- version: 1,
151
- },
152
- ],
153
- direction: "ltr",
154
- format: "",
155
- indent: 0,
156
- type: "paragraph",
157
- version: 1,
158
- },
159
- {
160
- children: [],
161
- direction: null,
162
- format: "",
163
- indent: 0,
164
- type: "paragraph",
165
- version: 1,
166
- },
167
- {
168
- children: [
169
- {
170
- children: [
171
- {
172
- detail: 0,
173
- format: 0,
174
- mode: "normal",
175
- style: "",
176
- text: "Num list 1",
177
- type: "text",
178
- version: 1,
179
- },
180
- ],
181
- direction: "ltr",
182
- format: "",
183
- indent: 0,
184
- type: "listitem",
185
- version: 1,
186
- value: 1,
187
- },
188
- {
189
- children: [
190
- {
191
- detail: 0,
192
- format: 0,
193
- mode: "normal",
194
- style: "",
195
- text: "Num list 2",
196
- type: "text",
197
- version: 1,
198
- },
199
- // TODO: image
200
- // {
201
- // altText: "",
202
- // height: 0,
203
- // maxWidth: 0,
204
- // src: "https://picsum.photos/id/237/200/300",
205
- // type: "image",
206
- // version: 1,
207
- // width: 0,
208
- // },
209
- ],
210
- direction: "ltr",
211
- format: "",
212
- indent: 0,
213
- type: "listitem",
214
- version: 1,
215
- value: 2,
216
- },
217
- ],
218
- direction: "ltr",
219
- format: "",
220
- indent: 0,
221
- type: "list",
222
- version: 1,
223
- listType: "number",
224
- start: 1,
225
- tag: "ol",
226
- },
227
- {
228
- children: [],
229
- direction: null,
230
- format: "",
231
- indent: 0,
232
- type: "paragraph",
233
- version: 1,
234
38
  },
235
39
  {
40
+ tag: "ul",
236
41
  children: [
237
42
  {
43
+ tag: "li",
238
44
  children: [
239
45
  {
240
- detail: 0,
241
- format: 0,
242
- mode: "normal",
243
- style: "",
244
- text: "Bullet list 1",
245
- type: "text",
246
- version: 1,
247
- },
248
- ],
249
- direction: "ltr",
250
- format: "",
251
- indent: 0,
252
- type: "listitem",
253
- version: 1,
254
- value: 1,
255
- },
256
- {
257
- children: [
258
- {
259
- detail: 0,
260
- format: 0,
261
- mode: "normal",
262
- style: "",
263
- text: "Bullet list 2",
264
- type: "text",
265
- version: 1,
46
+ tag: "ol",
47
+ dir: "rtl",
48
+ children: [
49
+ {
50
+ tag: "li",
51
+ children: [
52
+ {
53
+ tag: "span",
54
+ classes: ["italic"],
55
+ children: ["number 1.1"],
56
+ },
57
+ ],
58
+ },
59
+ { tag: "li", children: ["number 1.2"] },
60
+ ],
266
61
  },
267
62
  ],
268
- direction: "ltr",
269
- format: "",
270
- indent: 0,
271
- type: "listitem",
272
- version: 1,
273
- value: 2,
274
63
  },
275
64
  ],
276
- direction: "ltr",
277
- format: "",
278
- indent: 0,
279
- type: "list",
280
- version: 1,
281
- listType: "bullet",
282
- start: 1,
283
- tag: "ul",
284
- },
285
- {
286
- children: [],
287
- direction: null,
288
- format: "",
289
- indent: 0,
290
- type: "paragraph",
291
- version: 1,
292
- },
293
- {
294
- children: [],
295
- direction: "ltr",
296
- format: "",
297
- indent: 0,
298
- type: "paragraph",
299
- version: 1,
300
- },
301
- {
302
- children: [],
303
- direction: null,
304
- format: "",
305
- indent: 0,
306
- type: "paragraph",
307
- version: 1,
308
65
  },
309
66
  ],
310
- direction: "ltr",
311
- format: "",
312
- indent: 0,
313
- type: "root",
314
- version: 1,
315
- } as RichText,
67
+ } as RichText<AnyRichTextOptions>,
316
68
  };
@@ -0,0 +1,78 @@
1
+ import { Internal } from "@valbuild/core";
2
+ import { ChangeEvent } from "react";
3
+
4
+ const textEncoder = new TextEncoder();
5
+
6
+ export function readImage(ev: ChangeEvent<HTMLInputElement>) {
7
+ return new Promise<{
8
+ src: string;
9
+ sha256: string;
10
+ width?: number;
11
+ height?: number;
12
+ mimeType?: string;
13
+ fileExt?: string;
14
+ }>((resolve, reject) => {
15
+ const imageFile = ev.currentTarget.files?.[0];
16
+ const reader = new FileReader();
17
+ reader.addEventListener("load", () => {
18
+ const result = reader.result;
19
+ if (typeof result === "string") {
20
+ const image = new Image();
21
+ image.addEventListener("load", async () => {
22
+ const sha256 = await Internal.getSHA256Hash(
23
+ textEncoder.encode(result)
24
+ );
25
+ if (image.naturalWidth && image.naturalHeight) {
26
+ const mimeType = getMimeType(result);
27
+ console.log(result.slice(0, 30), mimeType);
28
+ resolve({
29
+ src: result,
30
+ width: image.naturalWidth,
31
+ height: image.naturalHeight,
32
+ sha256,
33
+ mimeType,
34
+ fileExt: mimeType && mimeTypeToFileExt(mimeType),
35
+ });
36
+ } else {
37
+ resolve({
38
+ src: result,
39
+ sha256,
40
+ });
41
+ }
42
+ });
43
+ image.src = result;
44
+ } else if (!result) {
45
+ reject({ message: "Empty result" });
46
+ } else {
47
+ reject({ message: "Unexpected image result type", result });
48
+ }
49
+ });
50
+ if (imageFile) {
51
+ reader.readAsDataURL(imageFile);
52
+ }
53
+ });
54
+ }
55
+
56
+ const MIME_TYPE_REGEX =
57
+ /^data:(image\/(png|jpeg|jpg|gif|webp|bmp|tiff|ico|svg\+xml));base64,/;
58
+
59
+ function getMimeType(base64Url: string): string | undefined {
60
+ const match = MIME_TYPE_REGEX.exec(base64Url);
61
+ if (match && match[1]) {
62
+ return match[1];
63
+ }
64
+ return;
65
+ }
66
+
67
+ function mimeTypeToFileExt(mimeType: string) {
68
+ if (mimeType === "image/svg+xml") {
69
+ return "svg";
70
+ }
71
+ if (mimeType === "image/vnd.microsoft.icon") {
72
+ return "ico";
73
+ }
74
+ if (mimeType.startsWith("image/")) {
75
+ return mimeType.slice("image/".length);
76
+ }
77
+ return mimeType;
78
+ }
@@ -4,10 +4,10 @@ module.exports = {
4
4
  darkMode: ["class", '[data-mode="dark"]'],
5
5
  theme: {
6
6
  zIndex: {
7
- overlay: 8999, // 1 less than the NextJS error z-index: 9000
8
- hover: 8996,
9
- window: 8997,
10
- full: 8998,
7
+ hover: 1,
8
+ window: 2,
9
+ full: 3,
10
+ overlay: 4,
11
11
  },
12
12
  colors: {
13
13
  base: "var(--val-theme-base)",
package/tsconfig.json CHANGED
@@ -11,7 +11,8 @@
11
11
  "noEmit": true,
12
12
  "target": "ES5",
13
13
  "outDir": "dist",
14
- "rootDir": "src"
14
+ "rootDir": "src",
15
+ "skipLibCheck": true
15
16
  },
16
17
  "include": ["src/**/*"]
17
18
  }