@valbuild/ui 0.12.0 → 0.13.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.
- package/package.json +1 -1
- package/src/assets/icons/Bold.tsx +23 -0
- package/src/assets/icons/Chevron.tsx +28 -0
- package/src/assets/icons/FontColor.tsx +30 -0
- package/src/assets/icons/ImageIcon.tsx +21 -0
- package/src/assets/icons/Italic.tsx +24 -0
- package/src/assets/icons/Strikethrough.tsx +22 -0
- package/src/assets/icons/Underline.tsx +22 -0
- package/src/assets/icons/Undo.tsx +20 -0
- package/src/components/Button.tsx +58 -0
- package/src/components/Checkbox.tsx +51 -0
- package/src/components/Dropdown.tsx +92 -0
- package/src/components/EditButton.tsx +10 -0
- package/src/components/ErrorText.tsx +3 -0
- package/src/components/RichTextEditor/ContentEditable.tsx +9 -0
- package/src/components/RichTextEditor/Nodes/ImageNode.tsx +117 -0
- package/src/components/RichTextEditor/Plugins/AutoFocus.tsx +12 -0
- package/src/components/RichTextEditor/Plugins/ImagePlugin.tsx +46 -0
- package/src/components/RichTextEditor/Plugins/Toolbar.tsx +381 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +176 -0
- package/src/components/UploadModal.tsx +109 -0
- package/src/components/ValOverlay.tsx +41 -0
- package/src/components/ValWindow.stories.tsx +182 -0
- package/src/components/ValWindow.tsx +137 -0
- package/src/components/forms/Form.tsx +122 -0
- package/src/components/forms/FormContainer.tsx +24 -0
- package/src/components/forms/ImageForm.tsx +195 -0
- package/src/components/forms/TextForm.tsx +22 -0
- package/src/exports.ts +3 -0
- package/src/index.css +79 -0
- package/src/index.tsx +14 -0
- package/src/server.ts +41 -0
- package/src/stories/Button.stories.tsx +20 -0
- package/src/stories/Checkbox.stories.tsx +14 -0
- package/src/stories/Dropdown.stories.tsx +23 -0
- package/src/stories/Introduction.mdx +221 -0
- package/src/stories/RichTextEditor.stories.tsx +314 -0
- package/src/stories/assets/code-brackets.svg +1 -0
- package/src/stories/assets/colors.svg +1 -0
- package/src/stories/assets/comments.svg +1 -0
- package/src/stories/assets/direction.svg +1 -0
- package/src/stories/assets/flow.svg +1 -0
- package/src/stories/assets/plugin.svg +1 -0
- package/src/stories/assets/repo.svg +1 -0
- package/src/stories/assets/stackalt.svg +1 -0
- package/src/vite-env.d.ts +1 -0
- package/src/vite-index.tsx +7 -0
- package/src/vite-server.ts +8 -0
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
const Bold: FC<{ className?: string }> = ({ className }) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth="2"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
className={className}
|
|
14
|
+
width={25}
|
|
15
|
+
height={25}
|
|
16
|
+
>
|
|
17
|
+
<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
|
|
18
|
+
<path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default Bold;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
const Chevron: FC<{ className?: string }> = ({ className }) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
width="16"
|
|
7
|
+
height="16"
|
|
8
|
+
viewBox="0 0 12 12"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
className={className}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
fillRule="evenodd"
|
|
14
|
+
clipRule="evenodd"
|
|
15
|
+
d="M3.70733 1.00015L8.7784 6.07121L8.07129 6.77832L3.00022 1.70725L3.70733 1.00015Z"
|
|
16
|
+
fill="currentColor"
|
|
17
|
+
/>
|
|
18
|
+
<path
|
|
19
|
+
fillRule="evenodd"
|
|
20
|
+
clipRule="evenodd"
|
|
21
|
+
d="M3.00015 10.4709L8.07121 5.39983L8.77832 6.10693L3.70725 11.178L3.00015 10.4709Z"
|
|
22
|
+
fill="currentColor"
|
|
23
|
+
/>
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default Chevron;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
const FontColor: FC<{ className?: string }> = ({ className }) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth="2"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
className={className}
|
|
14
|
+
width={25}
|
|
15
|
+
height={25}
|
|
16
|
+
>
|
|
17
|
+
<g>
|
|
18
|
+
<path d="M29,27H3c-0.6,0-1,0.4-1,1s0.4,1,1,1h26c0.6,0,1-0.4,1-1S29.6,27,29,27z" />
|
|
19
|
+
<path
|
|
20
|
+
d="M6.4,16.7C6.4,16.7,6.4,16.7,6.4,16.7l7,7c0.2,0.2,0.4,0.3,0.7,0.3s0.5-0.1,0.7-0.3l6.9-6.9c0,0,0,0,0,0l1.5-1.5
|
|
21
|
+
c0.4-0.4,0.4-1,0-1.4l-8.9-9c0,0,0,0,0,0l-2.5-2.5c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l1.8,1.8l-7.7,7.7c-0.4,0.4-0.4,1,0,1.4
|
|
22
|
+
L6.4,16.7z M13.6,7L14,7.5c0,0,0,0,0,0l7,7L20.6,15H7.5l-1-1L13.6,7z"
|
|
23
|
+
/>
|
|
24
|
+
<path d="M25,24c1.7,0,3-1.3,3-3c0-1.4-1.8-3.2-2.3-3.7c-0.4-0.4-1-0.4-1.4,0C23.8,17.8,22,19.6,22,21C22,22.7,23.3,24,25,24z" />
|
|
25
|
+
</g>
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default FontColor;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
const ImageIcon: FC<{ className?: string }> = ({ className }) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
viewBox="0 0 32 32"
|
|
8
|
+
className={className}
|
|
9
|
+
width={25}
|
|
10
|
+
height={25}
|
|
11
|
+
strokeWidth="4"
|
|
12
|
+
>
|
|
13
|
+
<g>
|
|
14
|
+
<path d="M25,2H7A5,5,0,0,0,2,7V25a5,5,0,0,0,5,5H25a5,5,0,0,0,5-5V7A5,5,0,0,0,25,2ZM7,4H25a3,3,0,0,1,3,3v5.59l-1.88-1.88a3,3,0,0,0-4.24,0l-7.95,8-3-2.42a3,3,0,0,0-3.8,0L4,18.86V7A3,3,0,0,1,7,4ZM25,28H7a3,3,0,0,1-3-3V21.47l4.38-3.66a1,1,0,0,1,1.27,0l3.73,3a1,1,0,0,0,1.33-.07l8.58-8.59a1,1,0,0,1,1.42,0L28,15.41V25A3,3,0,0,1,25,28Z" />
|
|
15
|
+
<path d="M10,13a3,3,0,1,0-3-3A3,3,0,0,0,10,13Zm0-4a1,1,0,1,1-1,1A1,1,0,0,1,10,9Z" />
|
|
16
|
+
</g>
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default ImageIcon;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
const Italic: FC<{ className?: string }> = ({ className }) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth="2"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
className={className}
|
|
14
|
+
width={25}
|
|
15
|
+
height={25}
|
|
16
|
+
>
|
|
17
|
+
<line x1="19" y1="4" x2="10" y2="4"></line>
|
|
18
|
+
<line x1="14" y1="20" x2="5" y2="20"></line>
|
|
19
|
+
<line x1="15" y1="4" x2="9" y2="20"></line>
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default Italic;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
const Strikethrough: FC<{ className?: string }> = ({ className }) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
stroke="currentColor"
|
|
9
|
+
strokeWidth="1"
|
|
10
|
+
className={className}
|
|
11
|
+
width={25}
|
|
12
|
+
height={25}
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
fillRule="evenodd"
|
|
16
|
+
d="M13,15 L13,21 C13,21.5522847 12.5522847,22 12,22 C11.4477153,22 11,21.5522847 11,21 L11,15 L13,15 Z M3,13 C2.44771525,13 2,12.5522847 2,12 C2,11.4477153 2.44771525,11 3,11 L21,11 C21.5522847,11 22,11.4477153 22,12 C22,12.5522847 21.5522847,13 21,13 L3,13 Z M19,2 C19.5522847,2 20,2.44771525 20,3 C20,3.55228475 19.5522847,4 19,4 L13,4 L13,9 L11,9 L11,4 L5,4 C4.44771525,4 4,3.55228475 4,3 C4,2.44771525 4.44771525,2 5,2 L19,2 Z"
|
|
17
|
+
/>
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default Strikethrough;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
const Underline: FC<{ className?: string }> = ({ className }) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth="2"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
className={className}
|
|
14
|
+
width={25}
|
|
15
|
+
height={25}
|
|
16
|
+
>
|
|
17
|
+
<path d="M18 4V11C18 14.3137 15.3137 17 12 17C8.68629 17 6 14.3137 6 11V4M4 21H20" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default Underline;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
const Undo: FC<{ className?: string }> = ({ className }) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
width="25"
|
|
7
|
+
height="25"
|
|
8
|
+
viewBox="0 0 256 256"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
className={className}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M55.265 167.072c-.975-1.973-3.388-2.796-5.372-1.847L42 169s22.5 53.5 85.5 56c60-1.5 96.627-48.626 97-96.5.373-47.874-37-95.5-95.5-96-57.5-1-79.556 45.004-79.556 45.004-1.073 1.93-1.944 1.698-1.944-.501V51.997a4 4 0 0 0-4-3.997H37c-2.209 0-4 1.8-4 4.008v48.984A3.998 3.998 0 0 0 36.998 105h50.504a3.995 3.995 0 0 0 3.998-3.993v-6.014c0-2.205-1.79-4.02-4.008-4.053l-25.484-.38c-2.214-.033-3.223-1.679-2.182-3.628C59.826 86.932 78 45 128.5 45.5c49 .5 82.751 41.929 82.5 83.242C208 184 166 211 127.5 210c-54.5 0-72.235-42.928-72.235-42.928z"
|
|
14
|
+
fillRule="evenodd"
|
|
15
|
+
/>
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default Undo;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import classNames from "classnames";
|
|
2
|
+
import { FC, SVGProps } from "react";
|
|
3
|
+
|
|
4
|
+
export interface ButtonProps
|
|
5
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
6
|
+
variant?: "primary" | "secondary";
|
|
7
|
+
icon?: React.ReactElement<SVGProps<SVGSVGElement>>;
|
|
8
|
+
active?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
tooltip?: string;
|
|
11
|
+
}
|
|
12
|
+
const Button: FC<ButtonProps> = ({
|
|
13
|
+
variant = "primary",
|
|
14
|
+
onClick,
|
|
15
|
+
children,
|
|
16
|
+
icon,
|
|
17
|
+
// active = false,
|
|
18
|
+
disabled = false,
|
|
19
|
+
tooltip,
|
|
20
|
+
}) => {
|
|
21
|
+
return (
|
|
22
|
+
<button
|
|
23
|
+
disabled={disabled}
|
|
24
|
+
className={classNames(
|
|
25
|
+
"font-sans font-[12px] tracking-[0.04em] py-[8px] px-[12px] h-[40px] rounded whitespace-nowrap group relative text-primary",
|
|
26
|
+
{
|
|
27
|
+
"font-bold": variant === "primary",
|
|
28
|
+
"bg-base hover:bg-base text-fill disabled:bg-fill disabled:text-base":
|
|
29
|
+
variant === "primary",
|
|
30
|
+
"bg-transparent border border-primary text-primary hover:border-highlight hover:text-highlight disabled:bg-fill disabled:text-base":
|
|
31
|
+
variant !== "primary",
|
|
32
|
+
}
|
|
33
|
+
)}
|
|
34
|
+
onClick={onClick}
|
|
35
|
+
>
|
|
36
|
+
{tooltip && (
|
|
37
|
+
<div
|
|
38
|
+
className={`absolute bottom-[-75%] left-0 z-20 bg-black w-fit h-fit text-base hidden group-hover:block`}
|
|
39
|
+
>
|
|
40
|
+
<div>{tooltip}</div>
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
<span className="flex flex-row items-center justify-center gap-2">
|
|
44
|
+
{icon && icon}
|
|
45
|
+
{children}
|
|
46
|
+
</span>
|
|
47
|
+
</button>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default Button;
|
|
52
|
+
export function PrimaryButton({ children }: { children: React.ReactNode }) {
|
|
53
|
+
return (
|
|
54
|
+
<button className="px-4 py-[2px] font-serif border rounded-sm border-border bg-fill text-primary hover:dark:bg-yellow hover:bg-warm-black hover:dark:text-dark-gray hover:text-white focus-visible:border-highlight focus:outline-none">
|
|
55
|
+
{children}
|
|
56
|
+
</button>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import classNames from "classnames";
|
|
2
|
+
import { FC } from "react";
|
|
3
|
+
|
|
4
|
+
export interface CheckboxProps {
|
|
5
|
+
label: string;
|
|
6
|
+
checked: boolean;
|
|
7
|
+
setChecked: React.Dispatch<React.SetStateAction<boolean>>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Checkbox: FC<CheckboxProps> = ({ label, checked, setChecked }) => {
|
|
11
|
+
const handleChange = (checked: boolean) => {
|
|
12
|
+
setChecked(checked);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div>
|
|
17
|
+
<label className="flex items-center">
|
|
18
|
+
<input
|
|
19
|
+
type="checkbox"
|
|
20
|
+
checked={checked}
|
|
21
|
+
onChange={(event) => handleChange(event.target.checked)}
|
|
22
|
+
className="hidden"
|
|
23
|
+
/>
|
|
24
|
+
<span
|
|
25
|
+
className={classNames(
|
|
26
|
+
"w-[14px] h-[14px] border rounded border-gray-400 flex items-center justify-center",
|
|
27
|
+
{
|
|
28
|
+
"bg-highlight": checked,
|
|
29
|
+
"bg-fill": !checked,
|
|
30
|
+
}
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{checked && (
|
|
34
|
+
<svg
|
|
35
|
+
width="10"
|
|
36
|
+
height="7"
|
|
37
|
+
viewBox="0 0 10 7"
|
|
38
|
+
fill="none"
|
|
39
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
40
|
+
>
|
|
41
|
+
<path d="M1 1L5 6L9 1" stroke="#1A1A1A" stroke-linecap="square" />
|
|
42
|
+
</svg>
|
|
43
|
+
)}
|
|
44
|
+
</span>
|
|
45
|
+
<span className="ml-2 font-mono text-primary">{label}</span>
|
|
46
|
+
</label>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default Checkbox;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { SVGProps, useEffect, useRef, useState } from "react";
|
|
2
|
+
import Chevron from "../assets/icons/Chevron";
|
|
3
|
+
import Button from "./Button";
|
|
4
|
+
|
|
5
|
+
export interface DropdownProps {
|
|
6
|
+
options: string[];
|
|
7
|
+
label: string;
|
|
8
|
+
onChange: (selectedOption: string) => void;
|
|
9
|
+
icon?: React.ReactElement<SVGProps<SVGSVGElement>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Dropdown: React.FC<DropdownProps> = ({
|
|
13
|
+
options,
|
|
14
|
+
onChange,
|
|
15
|
+
label,
|
|
16
|
+
icon,
|
|
17
|
+
}) => {
|
|
18
|
+
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
19
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
const [selectedOption, setSelectedOption] = useState<number>(0);
|
|
21
|
+
|
|
22
|
+
const handleToggle = () => {
|
|
23
|
+
setIsOpen(!isOpen);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const handleSelect = (option: string, idx: number) => {
|
|
27
|
+
setSelectedOption(idx);
|
|
28
|
+
onChange(option);
|
|
29
|
+
setIsOpen(false);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const handleOutsideClick = (event: MouseEvent) => {
|
|
34
|
+
if (
|
|
35
|
+
isOpen &&
|
|
36
|
+
dropdownRef.current &&
|
|
37
|
+
!dropdownRef.current.contains(event.target as Node)
|
|
38
|
+
) {
|
|
39
|
+
setIsOpen(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
document.addEventListener("click", handleOutsideClick);
|
|
43
|
+
return () => {
|
|
44
|
+
document.removeEventListener("click", handleOutsideClick);
|
|
45
|
+
};
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div ref={dropdownRef}>
|
|
50
|
+
<Button
|
|
51
|
+
onClick={(ev) => {
|
|
52
|
+
ev.preventDefault();
|
|
53
|
+
handleToggle();
|
|
54
|
+
}}
|
|
55
|
+
icon={
|
|
56
|
+
<Chevron
|
|
57
|
+
className={`rotate-[-90deg] transition-transform duration-150 ease-in-out ${
|
|
58
|
+
isOpen ? "" : "rotate-[90deg]"
|
|
59
|
+
}`}
|
|
60
|
+
/>
|
|
61
|
+
}
|
|
62
|
+
>
|
|
63
|
+
<span className="flex flex-row items-center justify-center gap-1">
|
|
64
|
+
{label}
|
|
65
|
+
{icon && icon}
|
|
66
|
+
</span>
|
|
67
|
+
</Button>
|
|
68
|
+
{isOpen && (
|
|
69
|
+
<div className="absolute left-0 mt-2 w-48 border shadow-lg font-mono font-[500] tracking-[0.04em] text-[14px] border-base text-primary bg-border z-10">
|
|
70
|
+
<div className="py-1 rounded-md">
|
|
71
|
+
{options?.map((option, idx) => (
|
|
72
|
+
<button
|
|
73
|
+
key={option}
|
|
74
|
+
onClick={(ev) => {
|
|
75
|
+
ev.preventDefault();
|
|
76
|
+
handleSelect(option, idx);
|
|
77
|
+
}}
|
|
78
|
+
className={`w-full text-left px-4 py-2 hover:bg-base hover:text-highlight ${
|
|
79
|
+
idx === selectedOption && "font-bold bg-base hover:bg-base"
|
|
80
|
+
}`}
|
|
81
|
+
>
|
|
82
|
+
{option}
|
|
83
|
+
</button>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default Dropdown;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function EditButton({ onClick }: { onClick: () => void }) {
|
|
2
|
+
return (
|
|
3
|
+
<button
|
|
4
|
+
className="px-[24px] py-4 font-serif text-lg font-semibold border-2 rounded-md text-warm-black dark:border-white border-warm-black bg-highlight"
|
|
5
|
+
onClick={onClick}
|
|
6
|
+
>
|
|
7
|
+
Edit page
|
|
8
|
+
</button>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DecoratorNode,
|
|
3
|
+
LexicalNode,
|
|
4
|
+
NodeKey,
|
|
5
|
+
SerializedLexicalNode,
|
|
6
|
+
Spread,
|
|
7
|
+
} from "lexical";
|
|
8
|
+
|
|
9
|
+
export interface ImagePayload {
|
|
10
|
+
altText: string;
|
|
11
|
+
height?: number;
|
|
12
|
+
key?: NodeKey;
|
|
13
|
+
maxWidth?: number;
|
|
14
|
+
src: string;
|
|
15
|
+
width?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type SerializedImageNode = Spread<
|
|
19
|
+
{
|
|
20
|
+
altText: string;
|
|
21
|
+
width?: number;
|
|
22
|
+
maxWidth: number;
|
|
23
|
+
height?: number;
|
|
24
|
+
src: string;
|
|
25
|
+
},
|
|
26
|
+
SerializedLexicalNode
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
export class ImageNode extends DecoratorNode<JSX.Element> {
|
|
30
|
+
__src: string;
|
|
31
|
+
__altText: string;
|
|
32
|
+
__width: "inherit" | number;
|
|
33
|
+
__height: "inherit" | number;
|
|
34
|
+
__maxWidth: number;
|
|
35
|
+
|
|
36
|
+
static getType(): string {
|
|
37
|
+
return "image";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static clone(node: ImageNode): ImageNode {
|
|
41
|
+
return new ImageNode(
|
|
42
|
+
node.__src,
|
|
43
|
+
node.__altText,
|
|
44
|
+
node.__width,
|
|
45
|
+
node.__height,
|
|
46
|
+
node.__maxWidth,
|
|
47
|
+
node.__key
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
src: string,
|
|
53
|
+
altText?: string,
|
|
54
|
+
width?: "inherit" | number,
|
|
55
|
+
height?: "inherit" | number,
|
|
56
|
+
maxWidth?: number,
|
|
57
|
+
key?: NodeKey
|
|
58
|
+
) {
|
|
59
|
+
super(key);
|
|
60
|
+
this.__src = src;
|
|
61
|
+
this.__altText = altText || "";
|
|
62
|
+
this.__width = width || "inherit";
|
|
63
|
+
this.__height = height || "inherit";
|
|
64
|
+
this.__maxWidth = maxWidth || 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
exportJSON(): SerializedImageNode {
|
|
68
|
+
return {
|
|
69
|
+
altText: this.__altText,
|
|
70
|
+
height: this.__height === "inherit" ? 0 : this.__height,
|
|
71
|
+
maxWidth: this.__maxWidth,
|
|
72
|
+
src: this.__src,
|
|
73
|
+
type: "image",
|
|
74
|
+
version: 1,
|
|
75
|
+
width: this.__width === "inherit" ? 0 : this.__width,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static importJSON(serializedNode: SerializedImageNode): ImageNode {
|
|
80
|
+
const { src } = serializedNode;
|
|
81
|
+
const node = $createImageNode(src);
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
createDOM(): HTMLElement {
|
|
86
|
+
return document.createElement("div");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
updateDOM(): false {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
decorate(): JSX.Element {
|
|
94
|
+
return (
|
|
95
|
+
<img
|
|
96
|
+
key={this.__key}
|
|
97
|
+
src={this.__src}
|
|
98
|
+
alt={this.__altText}
|
|
99
|
+
width={this.__width}
|
|
100
|
+
height={this.__height}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function $createImageNode(
|
|
106
|
+
src: string,
|
|
107
|
+
altText?: string,
|
|
108
|
+
width?: "inherit" | number,
|
|
109
|
+
height?: "inherit" | number,
|
|
110
|
+
maxWidth?: number
|
|
111
|
+
): ImageNode {
|
|
112
|
+
return new ImageNode(src, altText, width, height, maxWidth);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function $isImageNode(node: LexicalNode | null): boolean {
|
|
116
|
+
return node instanceof ImageNode;
|
|
117
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
3
|
+
|
|
4
|
+
export function AutoFocus() {
|
|
5
|
+
const [editor] = useLexicalComposerContext();
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
editor.focus();
|
|
9
|
+
}, [editor]);
|
|
10
|
+
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
2
|
+
import { $wrapNodeInElement, mergeRegister } from "@lexical/utils";
|
|
3
|
+
import {
|
|
4
|
+
$createParagraphNode,
|
|
5
|
+
$insertNodes,
|
|
6
|
+
$isRootOrShadowRoot,
|
|
7
|
+
COMMAND_PRIORITY_EDITOR,
|
|
8
|
+
createCommand,
|
|
9
|
+
LexicalCommand,
|
|
10
|
+
} from "lexical";
|
|
11
|
+
import { useEffect } from "react";
|
|
12
|
+
|
|
13
|
+
import { $createImageNode, ImageNode, ImagePayload } from "../Nodes/ImageNode";
|
|
14
|
+
|
|
15
|
+
export type InsertImagePayload = Readonly<ImagePayload>;
|
|
16
|
+
|
|
17
|
+
export const INSERT_IMAGE_COMMAND: LexicalCommand<InsertImagePayload> =
|
|
18
|
+
createCommand("INSERT_IMAGE_COMMAND");
|
|
19
|
+
|
|
20
|
+
export default function ImagesPlugin(): JSX.Element | null {
|
|
21
|
+
const [editor] = useLexicalComposerContext();
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!editor.hasNodes([ImageNode])) {
|
|
25
|
+
throw new Error("ImagesPlugin: ImageNode not registered on editor");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return mergeRegister(
|
|
29
|
+
editor.registerCommand<InsertImagePayload>(
|
|
30
|
+
INSERT_IMAGE_COMMAND,
|
|
31
|
+
(payload) => {
|
|
32
|
+
const imageNode = $createImageNode(payload.src);
|
|
33
|
+
$insertNodes([imageNode]);
|
|
34
|
+
if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
|
|
35
|
+
$wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
40
|
+
COMMAND_PRIORITY_EDITOR
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
}, [editor]);
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|