@pushwoosh/dumb-components 1.0.43 → 1.0.45
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/Chip/Chip.constants.d.ts +5 -0
- package/Chip/Chip.constants.js +15 -0
- package/Chip/Chip.d.ts +8 -0
- package/Chip/Chip.js +23 -0
- package/Chip/Chip.types.d.ts +7 -0
- package/Chip/Chip.types.js +1 -0
- package/Chip/index.d.ts +1 -0
- package/Chip/index.js +1 -0
- package/Chip/styles.d.ts +6 -0
- package/Chip/styles.js +15 -0
- package/RichMessageEditor/DropContent.d.ts +5 -0
- package/RichMessageEditor/DropContent.js +14 -0
- package/RichMessageEditor/EmojiPicker.d.ts +6 -0
- package/RichMessageEditor/EmojiPicker.js +17 -0
- package/RichMessageEditor/RichMessageEditor.d.ts +3 -0
- package/RichMessageEditor/RichMessageEditor.js +114 -0
- package/RichMessageEditor/RichMessageEditor.styled.d.ts +2 -0
- package/RichMessageEditor/RichMessageEditor.styled.js +10 -0
- package/RichMessageEditor/RichMessageEditor.types.d.ts +27 -0
- package/RichMessageEditor/RichMessageEditor.types.js +1 -0
- package/RichMessageEditor/RichMessageViewer.d.ts +6 -0
- package/RichMessageEditor/RichMessageViewer.js +28 -0
- package/RichMessageEditor/RichMessageViewer.styled.d.ts +2 -0
- package/RichMessageEditor/RichMessageViewer.styled.js +10 -0
- package/RichMessageEditor/assets/EmojiIcon.svg +14 -0
- package/RichMessageEditor/assets/PersonalizationIcon.svg +14 -0
- package/RichMessageEditor/index.d.ts +3 -0
- package/RichMessageEditor/index.js +2 -0
- package/RichMessageEditor/slate/DynamicContentElement.d.ts +2 -0
- package/RichMessageEditor/slate/DynamicContentElement.js +53 -0
- package/RichMessageEditor/slate/SlateEditor.d.ts +18 -0
- package/RichMessageEditor/slate/SlateEditor.js +33 -0
- package/RichMessageEditor/slate/SlateEditor.styled.d.ts +16 -0
- package/RichMessageEditor/slate/SlateEditor.styled.js +9 -0
- package/RichMessageEditor/slate/SlateEditor.utils.d.ts +5 -0
- package/RichMessageEditor/slate/SlateEditor.utils.js +28 -0
- package/RichMessageEditor/slate/helpers.d.ts +10 -0
- package/RichMessageEditor/slate/helpers.js +104 -0
- package/RichMessageEditor/slate/types.d.ts +26 -0
- package/RichMessageEditor/slate/types.js +1 -0
- package/RichMessageEditor/types.d.ts +2 -0
- package/RichMessageEditor/types.js +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Color } from '@pushwoosh/kit-constants';
|
|
2
|
+
export const variants = {
|
|
3
|
+
primary: {
|
|
4
|
+
color: Color.PRIMARY,
|
|
5
|
+
background: Color.BRIGHT_LIGHT
|
|
6
|
+
},
|
|
7
|
+
danger: {
|
|
8
|
+
color: Color.DANGER,
|
|
9
|
+
background: Color.DANGER_LIGHT
|
|
10
|
+
},
|
|
11
|
+
warning: {
|
|
12
|
+
color: Color.WARNING,
|
|
13
|
+
background: Color.WARNING_LIGHT
|
|
14
|
+
}
|
|
15
|
+
};
|
package/Chip/Chip.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export declare const Chip: React.ForwardRefExoticComponent<{
|
|
3
|
+
$variant?: import("./Chip.types").ChipVariant;
|
|
4
|
+
$selected?: boolean;
|
|
5
|
+
$onClose?: React.HTMLAttributes<HTMLDivElement>["onClick"];
|
|
6
|
+
} & {
|
|
7
|
+
children?: React.ReactNode | undefined;
|
|
8
|
+
} & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
package/Chip/Chip.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { CloseIcon } from '@pushwoosh/kit-icons';
|
|
3
|
+
import { ChipStyled } from './styles';
|
|
4
|
+
export const Chip = forwardRef(({
|
|
5
|
+
$variant = 'primary',
|
|
6
|
+
$selected,
|
|
7
|
+
$onClose,
|
|
8
|
+
children,
|
|
9
|
+
...rest
|
|
10
|
+
}, ref) => {
|
|
11
|
+
return React.createElement(ChipStyled, {
|
|
12
|
+
ref: ref,
|
|
13
|
+
"$variant": $variant,
|
|
14
|
+
"$selected": $selected,
|
|
15
|
+
"$closeable": !!$onClose,
|
|
16
|
+
...rest
|
|
17
|
+
}, children, $onClose && React.createElement(CloseIcon, {
|
|
18
|
+
size: "small",
|
|
19
|
+
onClick: $onClose,
|
|
20
|
+
as: "span",
|
|
21
|
+
className: "closeIcon"
|
|
22
|
+
}));
|
|
23
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type HTMLAttributes, type PropsWithChildren } from 'react';
|
|
2
|
+
export type ChipVariant = 'primary' | 'danger' | 'warning';
|
|
3
|
+
export type ChipProps = PropsWithChildren<{
|
|
4
|
+
$variant?: ChipVariant;
|
|
5
|
+
$selected?: boolean;
|
|
6
|
+
$onClose?: HTMLAttributes<HTMLDivElement>['onClick'];
|
|
7
|
+
}> & HTMLAttributes<HTMLDivElement>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/Chip/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Chip } from './Chip';
|
package/Chip/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Chip } from './Chip';
|
package/Chip/styles.d.ts
ADDED
package/Chip/styles.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Color } from '@pushwoosh/kit-constants';
|
|
3
|
+
import { variants } from './Chip.constants';
|
|
4
|
+
export const ChipStyled = styled.div.withConfig({
|
|
5
|
+
displayName: "ChipStyled",
|
|
6
|
+
componentId: "sc-e2g39l-0"
|
|
7
|
+
})(["display:inline-flex;min-height:24px;padding:0 ", " 0 8px;align-items:center;gap:4px;cursor:default;border-radius:4px;color:", ";background:", ";font-weight:400;line-height:140%;& > .closeIcon{cursor:pointer;transition:transform 0.2s;&:hover{transform:scale(1.3);}}"], ({
|
|
8
|
+
$closeable
|
|
9
|
+
}) => $closeable ? '4px' : '8px', ({
|
|
10
|
+
$variant,
|
|
11
|
+
$selected
|
|
12
|
+
}) => $selected ? Color.CLEAR : variants[$variant].color, ({
|
|
13
|
+
$variant,
|
|
14
|
+
$selected
|
|
15
|
+
}) => $selected ? Color.MAIN : variants[$variant].background);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DropContentStyled } from './RichMessageEditor.styled';
|
|
3
|
+
export const DropContent = ({
|
|
4
|
+
top,
|
|
5
|
+
left,
|
|
6
|
+
children
|
|
7
|
+
}) => {
|
|
8
|
+
return React.createElement(DropContentStyled, {
|
|
9
|
+
style: {
|
|
10
|
+
top,
|
|
11
|
+
left
|
|
12
|
+
}
|
|
13
|
+
}, children);
|
|
14
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Picker from '@emoji-mart/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
async function data() {
|
|
4
|
+
const response = await fetch('https://cdn.jsdelivr.net/npm/@emoji-mart/data');
|
|
5
|
+
return response.json();
|
|
6
|
+
}
|
|
7
|
+
export const EmojiPicker = ({
|
|
8
|
+
onEmojiSelect
|
|
9
|
+
}) => {
|
|
10
|
+
return React.createElement(Picker, {
|
|
11
|
+
data: data,
|
|
12
|
+
onEmojiSelect: emoji => {
|
|
13
|
+
onEmojiSelect(emoji.native);
|
|
14
|
+
},
|
|
15
|
+
theme: "light"
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { isEqual } from 'es-toolkit';
|
|
2
|
+
import React, { useRef, useState } from 'react';
|
|
3
|
+
import { Transforms } from 'slate';
|
|
4
|
+
import { Collapse, Horizontal, useClickAway } from '@pushwoosh/kit-helpers';
|
|
5
|
+
import { FieldBox } from '../FieldBox';
|
|
6
|
+
import EmojiIcon from './assets/EmojiIcon.svg';
|
|
7
|
+
import PersonalizationIcon from './assets/PersonalizationIcon.svg';
|
|
8
|
+
import { DropContent } from './DropContent';
|
|
9
|
+
import { EmojiPicker } from './EmojiPicker';
|
|
10
|
+
import { ImSpan } from './RichMessageEditor.styled';
|
|
11
|
+
import { getDcByPath } from './slate/helpers';
|
|
12
|
+
import { SlateEditable, SlateEditor } from './slate/SlateEditor';
|
|
13
|
+
export const RichMessageEditor = ({
|
|
14
|
+
title,
|
|
15
|
+
initialValue,
|
|
16
|
+
minHeight,
|
|
17
|
+
canUsePersonalization,
|
|
18
|
+
canUseEmoji,
|
|
19
|
+
onChange,
|
|
20
|
+
renderDynamicContent
|
|
21
|
+
}) => {
|
|
22
|
+
const rootRef = useRef(null);
|
|
23
|
+
const [showDrop, setShowDrop] = useState();
|
|
24
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
25
|
+
const showDropArea = val => {
|
|
26
|
+
const rect = rootRef.current.getBoundingClientRect();
|
|
27
|
+
setShowDrop({
|
|
28
|
+
...val,
|
|
29
|
+
xValue: rect.right + 8,
|
|
30
|
+
yValue: rect.top
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const onSelectionChange = selection => {
|
|
34
|
+
if (isEqual(selection.anchor, selection.focus)) {
|
|
35
|
+
const {
|
|
36
|
+
path
|
|
37
|
+
} = selection.anchor;
|
|
38
|
+
if (path.length > 2) {
|
|
39
|
+
showDropArea({
|
|
40
|
+
type: 'personalization',
|
|
41
|
+
path: path.slice(0, -1)
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
setShowDrop(undefined);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const insertDynamicContent = editor => {
|
|
49
|
+
const newDC = {
|
|
50
|
+
type: 'dc',
|
|
51
|
+
dc: {
|
|
52
|
+
tagName: '',
|
|
53
|
+
modifier: 'regular',
|
|
54
|
+
defaultValue: ''
|
|
55
|
+
},
|
|
56
|
+
children: [{
|
|
57
|
+
text: ''
|
|
58
|
+
}]
|
|
59
|
+
};
|
|
60
|
+
Transforms.insertNodes(editor, newDC);
|
|
61
|
+
Transforms.select(editor, editor.selection);
|
|
62
|
+
onSelectionChange(editor.selection);
|
|
63
|
+
};
|
|
64
|
+
useClickAway(true, rootRef, () => {
|
|
65
|
+
setShowDrop(undefined);
|
|
66
|
+
setIsFocused(false);
|
|
67
|
+
});
|
|
68
|
+
return React.createElement(SlateEditor, {
|
|
69
|
+
initialValue: initialValue,
|
|
70
|
+
onChange: onChange,
|
|
71
|
+
onSelectionChange: onSelectionChange
|
|
72
|
+
}, ({
|
|
73
|
+
editor
|
|
74
|
+
}) => React.createElement(FieldBox, {
|
|
75
|
+
title: title,
|
|
76
|
+
boxRef: rootRef,
|
|
77
|
+
actions: React.createElement(Collapse, {
|
|
78
|
+
expanded: isFocused
|
|
79
|
+
}, React.createElement(Horizontal, {
|
|
80
|
+
"$gap": 8
|
|
81
|
+
}, canUsePersonalization && React.createElement(ImSpan, {
|
|
82
|
+
onClick: () => insertDynamicContent(editor)
|
|
83
|
+
}, React.createElement(PersonalizationIcon, null), "Personalization"), canUseEmoji && React.createElement(ImSpan, {
|
|
84
|
+
onClick: () => showDropArea({
|
|
85
|
+
type: 'emoji'
|
|
86
|
+
})
|
|
87
|
+
}, React.createElement(EmojiIcon, null), "Emoji")))
|
|
88
|
+
}, React.createElement(SlateEditable, {
|
|
89
|
+
minHeight: minHeight,
|
|
90
|
+
onFocus: () => {
|
|
91
|
+
setIsFocused(true);
|
|
92
|
+
const {
|
|
93
|
+
selection
|
|
94
|
+
} = editor;
|
|
95
|
+
if (selection) {
|
|
96
|
+
onSelectionChange(selection);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}), showDrop && React.createElement(DropContent, {
|
|
100
|
+
top: showDrop.yValue,
|
|
101
|
+
left: showDrop.xValue
|
|
102
|
+
}, showDrop.type === 'personalization' && (renderDynamicContent === null || renderDynamicContent === void 0 ? void 0 : renderDynamicContent({
|
|
103
|
+
dc: getDcByPath(editor.children, showDrop.path).dc,
|
|
104
|
+
onChange: newDC => {
|
|
105
|
+
Transforms.setNodes(editor, {
|
|
106
|
+
dc: newDC
|
|
107
|
+
}, {
|
|
108
|
+
at: showDrop.path
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
})), showDrop.type === 'emoji' && React.createElement(EmojiPicker, {
|
|
112
|
+
onEmojiSelect: s => editor.insertText(s)
|
|
113
|
+
}))));
|
|
114
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Color } from '@pushwoosh/kit-constants';
|
|
3
|
+
export const ImSpan = styled.span.withConfig({
|
|
4
|
+
displayName: "ImSpan",
|
|
5
|
+
componentId: "sc-kdvgj9-0"
|
|
6
|
+
})(["color:", ";position:relative;padding-left:20px;font-size:12px;font-style:normal;font-weight:500;line-height:130%;text-transform:uppercase;cursor:pointer;> svg{position:absolute;left:0;top:-1px;}"], Color.SOFT);
|
|
7
|
+
export const DropContentStyled = styled.div.withConfig({
|
|
8
|
+
displayName: "DropContentStyled",
|
|
9
|
+
componentId: "sc-kdvgj9-1"
|
|
10
|
+
})(["position:fixed;z-index:10;"]);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { DynamicContent } from './slate/types';
|
|
3
|
+
type DropValueCommon = {
|
|
4
|
+
xValue: number;
|
|
5
|
+
yValue: number;
|
|
6
|
+
};
|
|
7
|
+
export type DropValuePersonalization = {
|
|
8
|
+
type: 'personalization';
|
|
9
|
+
path: number[];
|
|
10
|
+
};
|
|
11
|
+
export type DropValueEmoji = {
|
|
12
|
+
type: 'emoji';
|
|
13
|
+
};
|
|
14
|
+
export type DropValue = DropValueCommon & DropValueEmoji | DropValueCommon & DropValuePersonalization;
|
|
15
|
+
export type RichMessageEditorProps = {
|
|
16
|
+
title: ReactNode;
|
|
17
|
+
initialValue: string;
|
|
18
|
+
onChange: (value: string) => void;
|
|
19
|
+
minHeight?: string;
|
|
20
|
+
canUsePersonalization?: boolean;
|
|
21
|
+
canUseEmoji?: boolean;
|
|
22
|
+
renderDynamicContent?: (props: {
|
|
23
|
+
dc: DynamicContent;
|
|
24
|
+
onChange: (newDC: DynamicContent) => void;
|
|
25
|
+
}) => ReactNode;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { Fragment, useMemo } from 'react';
|
|
2
|
+
import { DynamicContentBracket, DynamicContentRoot } from './RichMessageViewer.styled';
|
|
3
|
+
import { isElement, string2content } from './slate/helpers';
|
|
4
|
+
const arrayJoinReact = (arr, separator) => arr.flatMap((item, index) => index ? [separator, item] : [item]);
|
|
5
|
+
export const RichMessageViewer = ({
|
|
6
|
+
value,
|
|
7
|
+
lineBreaks
|
|
8
|
+
}) => {
|
|
9
|
+
const content = useMemo(() => {
|
|
10
|
+
return Array.isArray(value) ? value : string2content(value);
|
|
11
|
+
}, [value]);
|
|
12
|
+
return arrayJoinReact(content.map((node, index) => {
|
|
13
|
+
const children = node.children.map((child, idx) => {
|
|
14
|
+
if (isElement(child) && child.type === 'dc') {
|
|
15
|
+
return (
|
|
16
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
17
|
+
React.createElement(DynamicContentRoot, {
|
|
18
|
+
key: idx
|
|
19
|
+
}, React.createElement(DynamicContentBracket, null, '{'), "\u00A0", child.dc.tagName, "\u00A0", React.createElement(DynamicContentBracket, null, '}'))
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return child.text;
|
|
23
|
+
});
|
|
24
|
+
return React.createElement(Fragment, {
|
|
25
|
+
key: index
|
|
26
|
+
}, children); // eslint-disable-line react/no-array-index-key
|
|
27
|
+
}), lineBreaks ? React.createElement("br", null) : ' ');
|
|
28
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Color, FontWeight, ShapeRadius } from '@pushwoosh/kit-constants';
|
|
3
|
+
export const DynamicContentRoot = styled.span.withConfig({
|
|
4
|
+
displayName: "DynamicContentRoot",
|
|
5
|
+
componentId: "sc-f10jz5-0"
|
|
6
|
+
})(["background-color:", ";border-radius:", ";white-space:nowrap;font-weight:", ";padding:0 2px;"], Color.EXCEPTIONAL_LIGHT, ShapeRadius.CONTROL, FontWeight.MEDIUM);
|
|
7
|
+
export const DynamicContentBracket = styled.span.withConfig({
|
|
8
|
+
displayName: "DynamicContentBracket",
|
|
9
|
+
componentId: "sc-f10jz5-1"
|
|
10
|
+
})(["color:", ";"], Color.EXCEPTIONAL);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
width="16"
|
|
3
|
+
height="16"
|
|
4
|
+
viewBox="0 0 16 16"
|
|
5
|
+
fill="none"
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
>
|
|
8
|
+
<path
|
|
9
|
+
fill-rule="evenodd"
|
|
10
|
+
clip-rule="evenodd"
|
|
11
|
+
d="M2.75 8C2.75 5.10051 5.10051 2.75 8 2.75C10.8995 2.75 13.25 5.10051 13.25 8C13.25 10.8995 10.8995 13.25 8 13.25C5.10051 13.25 2.75 10.8995 2.75 8ZM8 1.25C4.27208 1.25 1.25 4.27208 1.25 8C1.25 11.7279 4.27208 14.75 8 14.75C11.7279 14.75 14.75 11.7279 14.75 8C14.75 4.27208 11.7279 1.25 8 1.25ZM5.66669 7.33334C6.21897 7.33334 6.66669 6.88563 6.66669 6.33334C6.66669 5.78106 6.21897 5.33334 5.66669 5.33334C5.1144 5.33334 4.66669 5.78106 4.66669 6.33334C4.66669 6.88563 5.1144 7.33334 5.66669 7.33334ZM11.3333 6.33334C11.3333 6.88563 10.8856 7.33334 10.3333 7.33334C9.78103 7.33334 9.33331 6.88563 9.33331 6.33334C9.33331 5.78106 9.78103 5.33334 10.3333 5.33334C10.8856 5.33334 11.3333 5.78106 11.3333 6.33334ZM11.4225 9.94557C11.6215 9.58228 11.4883 9.12647 11.125 8.9275C10.7617 8.72852 10.3059 8.86173 10.1069 9.22502C9.81509 9.75789 9.03837 10.2439 8.00002 10.2439C6.96166 10.2439 6.18494 9.75789 5.89309 9.22502C5.69412 8.86173 5.23831 8.72852 4.87501 8.9275C4.51172 9.12647 4.37852 9.58228 4.57749 9.94557C5.19205 11.0676 6.54974 11.7439 8.00002 11.7439C9.45029 11.7439 10.808 11.0676 11.4225 9.94557Z"
|
|
12
|
+
fill="currentColor"
|
|
13
|
+
/>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
width="16"
|
|
3
|
+
height="16"
|
|
4
|
+
viewBox="0 0 16 16"
|
|
5
|
+
fill="none"
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
>
|
|
8
|
+
<path
|
|
9
|
+
fill-rule="evenodd"
|
|
10
|
+
clip-rule="evenodd"
|
|
11
|
+
d="M5 1.25C5.41421 1.25 5.75 1.58579 5.75 2C5.75 2.41421 5.41421 2.75 5 2.75C3.75736 2.75 2.75 3.75736 2.75 5V6.75C2.75 7.13672 2.64024 7.49781 2.45017 7.80381C2.37602 7.92319 2.37602 8.07682 2.45017 8.1962C2.64024 8.50219 2.75 8.86328 2.75 9.25V11C2.75 12.2426 3.75736 13.25 5 13.25C5.41421 13.25 5.75 13.5858 5.75 14C5.75 14.4142 5.41421 14.75 5 14.75C2.92893 14.75 1.25 13.0711 1.25 11V9.25C1.25 8.97386 1.02614 8.75 0.75 8.75C0.335786 8.75 0 8.41421 0 8C0 7.58579 0.335786 7.25 0.75 7.25C1.02614 7.25 1.25 7.02614 1.25 6.75V5C1.25 2.92893 2.92893 1.25 5 1.25ZM11 1.25C10.5858 1.25 10.25 1.58579 10.25 2C10.25 2.41421 10.5858 2.75 11 2.75C12.2426 2.75 13.25 3.75736 13.25 5V6.75C13.25 7.13672 13.3598 7.49781 13.5498 7.80381C13.624 7.92319 13.624 8.07682 13.5498 8.1962C13.3598 8.50219 13.25 8.86328 13.25 9.25V11C13.25 12.2426 12.2426 13.25 11 13.25C10.5858 13.25 10.25 13.5858 10.25 14C10.25 14.4142 10.5858 14.75 11 14.75C13.0711 14.75 14.75 13.0711 14.75 11V9.25C14.75 8.97386 14.9739 8.75 15.25 8.75C15.6642 8.75 16 8.41421 16 8C16 7.58579 15.6642 7.25 15.25 7.25C14.9739 7.25 14.75 7.02614 14.75 6.75V5C14.75 2.92893 13.0711 1.25 11 1.25ZM4.00438 7.98268C4.00677 7.4304 4.45643 6.98464 5.00871 6.98703C5.56099 6.98943 6.00675 7.43909 6.00436 7.99137C6.00196 8.54365 5.5523 8.98941 5.00002 8.98702C4.44774 8.98462 4.00198 8.53496 4.00438 7.98268ZM8.00434 7.00001C7.45206 6.99761 7.00241 7.44338 7.00001 7.99566C6.99761 8.54794 7.44338 8.99759 7.99566 8.99999C8.54794 9.00239 8.99759 8.55662 8.99999 8.00434C9.00239 7.45206 8.55662 7.00241 8.00434 7.00001ZM11.0087 7.01304C10.4564 7.01064 10.0068 7.4564 10.0044 8.00868C10.002 8.56096 10.4477 9.01062 11 9.01302C11.5523 9.01541 12.002 8.56965 12.0044 8.01737C12.0068 7.46509 11.561 7.01543 11.0087 7.01304Z"
|
|
12
|
+
fill="currentColor"
|
|
13
|
+
/>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Transforms } from 'slate';
|
|
3
|
+
import { useSelected, useSlateStatic } from 'slate-react';
|
|
4
|
+
import { findPath } from './helpers';
|
|
5
|
+
import { Chip } from '../../Chip';
|
|
6
|
+
const DynamicContentElementComponent = ({
|
|
7
|
+
attributes,
|
|
8
|
+
element,
|
|
9
|
+
children
|
|
10
|
+
}) => {
|
|
11
|
+
const editor = useSlateStatic();
|
|
12
|
+
const selected = useSelected();
|
|
13
|
+
const {
|
|
14
|
+
tagName
|
|
15
|
+
} = element.dc;
|
|
16
|
+
return React.createElement(Chip, {
|
|
17
|
+
...attributes,
|
|
18
|
+
as: "span",
|
|
19
|
+
contentEditable: false,
|
|
20
|
+
"$variant": tagName ? 'primary' : 'danger',
|
|
21
|
+
"$selected": selected,
|
|
22
|
+
"$onClose": event => {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
event.stopPropagation();
|
|
25
|
+
const path = findPath(editor.children, element);
|
|
26
|
+
Transforms.delete(editor, {
|
|
27
|
+
at: path
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}, React.createElement("span", null, tagName || 'Not set'), children);
|
|
31
|
+
};
|
|
32
|
+
const Element = props => {
|
|
33
|
+
const {
|
|
34
|
+
attributes,
|
|
35
|
+
children,
|
|
36
|
+
element
|
|
37
|
+
} = props;
|
|
38
|
+
switch (element.type) {
|
|
39
|
+
case 'dc':
|
|
40
|
+
return React.createElement(DynamicContentElementComponent, {
|
|
41
|
+
...props
|
|
42
|
+
});
|
|
43
|
+
default:
|
|
44
|
+
return React.createElement("p", {
|
|
45
|
+
...attributes
|
|
46
|
+
}, children);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export function renderElement(props) {
|
|
50
|
+
return React.createElement(Element, {
|
|
51
|
+
...props
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type FC, type ReactNode } from 'react';
|
|
2
|
+
import { type Selection } from 'slate';
|
|
3
|
+
import { type CustomEditor } from './custom-types';
|
|
4
|
+
type SlateEditor = {
|
|
5
|
+
initialValue: string;
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
onSelectionChange: (selection: Selection) => void;
|
|
8
|
+
children: (props: {
|
|
9
|
+
editor: CustomEditor;
|
|
10
|
+
}) => ReactNode;
|
|
11
|
+
};
|
|
12
|
+
export declare const SlateEditor: FC<SlateEditor>;
|
|
13
|
+
type SlateEditableProps = {
|
|
14
|
+
minHeight?: string;
|
|
15
|
+
onFocus?: () => void;
|
|
16
|
+
};
|
|
17
|
+
export declare const SlateEditable: FC<SlateEditableProps>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Slate } from 'slate-react';
|
|
3
|
+
import { renderElement } from './DynamicContentElement';
|
|
4
|
+
import { content2string, string2content } from './helpers';
|
|
5
|
+
import { EditableStyled } from './SlateEditor.styled';
|
|
6
|
+
import { createMKEditor } from './SlateEditor.utils';
|
|
7
|
+
export const SlateEditor = ({
|
|
8
|
+
initialValue: initialValueString,
|
|
9
|
+
onChange,
|
|
10
|
+
onSelectionChange,
|
|
11
|
+
children
|
|
12
|
+
}) => {
|
|
13
|
+
const [initialValue] = useState(() => string2content(initialValueString));
|
|
14
|
+
const [editor] = useState(createMKEditor);
|
|
15
|
+
return React.createElement(Slate, {
|
|
16
|
+
editor: editor,
|
|
17
|
+
initialValue: initialValue,
|
|
18
|
+
onValueChange: value => onChange(content2string(value)),
|
|
19
|
+
onSelectionChange: onSelectionChange
|
|
20
|
+
}, children({
|
|
21
|
+
editor
|
|
22
|
+
}));
|
|
23
|
+
};
|
|
24
|
+
export const SlateEditable = ({
|
|
25
|
+
minHeight,
|
|
26
|
+
onFocus
|
|
27
|
+
}) => {
|
|
28
|
+
return React.createElement(EditableStyled, {
|
|
29
|
+
"$minHeight": minHeight,
|
|
30
|
+
renderElement: renderElement,
|
|
31
|
+
onFocus: onFocus
|
|
32
|
+
});
|
|
33
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const EditableStyled: import("styled-components").StyledComponent<import("react").ForwardRefExoticComponent<{
|
|
2
|
+
decorate?: ((entry: import("slate").NodeEntry) => import("slate").DecoratedRange[]) | undefined;
|
|
3
|
+
onDOMBeforeInput?: ((event: InputEvent) => void) | undefined;
|
|
4
|
+
placeholder?: string | undefined;
|
|
5
|
+
readOnly?: boolean | undefined;
|
|
6
|
+
role?: string | undefined;
|
|
7
|
+
style?: React.CSSProperties | undefined;
|
|
8
|
+
renderElement?: ((props: import("slate-react").RenderElementProps) => import("react").JSX.Element) | undefined;
|
|
9
|
+
renderLeaf?: ((props: import("slate-react").RenderLeafProps) => import("react").JSX.Element) | undefined;
|
|
10
|
+
renderPlaceholder?: ((props: import("slate-react").RenderPlaceholderProps) => import("react").JSX.Element) | undefined;
|
|
11
|
+
scrollSelectionIntoView?: ((editor: import("slate-react").ReactEditor, domRange: Range) => void) | undefined;
|
|
12
|
+
as?: React.ElementType<any> | undefined;
|
|
13
|
+
disableDefaultStyles?: boolean | undefined;
|
|
14
|
+
} & import("react").TextareaHTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>, any, {
|
|
15
|
+
$minHeight?: string;
|
|
16
|
+
}, never>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Editable } from 'slate-react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { Color, FontSize, FontWeight, ShapeRadius } from '@pushwoosh/kit-constants';
|
|
4
|
+
export const EditableStyled = styled(Editable).withConfig({
|
|
5
|
+
displayName: "EditableStyled",
|
|
6
|
+
componentId: "sc-1frhl1f-0"
|
|
7
|
+
})(["font-weight:", ";font-size:", ";line-height:140%;padding:8px;color:", ";caret-color:", ";border:1px solid ", ";border-radius:", ";overflow-x:auto;", " &:focus{border-color:", ";outline:none;}& > p{margin:4px 0;&:first-child{margin-top:0;}&:last-child{margin-bottom:0;}}"], FontWeight.REGULAR, FontSize.REGULAR, Color.MAIN, Color.BRIGHT, Color.FORM, ShapeRadius.CONTROL, ({
|
|
8
|
+
$minHeight
|
|
9
|
+
}) => $minHeight && `min-height: ${$minHeight};`, Color.BRIGHT);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type CustomTypes, type Editor } from 'slate';
|
|
2
|
+
import { type DynamicContent } from './types';
|
|
3
|
+
export declare const withDynamicContent: (editor: Editor) => import("./custom-types").CustomEditor;
|
|
4
|
+
export declare const createMKEditor: () => import("./custom-types").CustomEditor;
|
|
5
|
+
export declare const insertDynamicContent: (editor: CustomTypes["Editor"], dc: DynamicContent) => void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Transforms, createEditor } from 'slate';
|
|
2
|
+
import { withHistory } from 'slate-history';
|
|
3
|
+
import { withReact } from 'slate-react';
|
|
4
|
+
export const withDynamicContent = editor => {
|
|
5
|
+
const {
|
|
6
|
+
isInline,
|
|
7
|
+
isVoid
|
|
8
|
+
} = editor;
|
|
9
|
+
editor.isInline = element => {
|
|
10
|
+
return element.type === 'dc' ? true : isInline(element);
|
|
11
|
+
};
|
|
12
|
+
editor.isVoid = element => {
|
|
13
|
+
return element.type === 'dc' ? true : isVoid(element);
|
|
14
|
+
};
|
|
15
|
+
return editor;
|
|
16
|
+
};
|
|
17
|
+
export const createMKEditor = () => withDynamicContent(withReact(withHistory(createEditor())));
|
|
18
|
+
export const insertDynamicContent = (editor, dc) => {
|
|
19
|
+
const newDC = {
|
|
20
|
+
type: 'dc',
|
|
21
|
+
dc,
|
|
22
|
+
children: [{
|
|
23
|
+
text: ''
|
|
24
|
+
}]
|
|
25
|
+
};
|
|
26
|
+
Transforms.insertNodes(editor, newDC);
|
|
27
|
+
Transforms.move(editor);
|
|
28
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Descendant } from 'slate';
|
|
2
|
+
import { type CustomElement, type DynamicContent, type DynamicContentElement, type SlateEditorContent } from './types';
|
|
3
|
+
export declare function isElement(x: Descendant): x is CustomElement;
|
|
4
|
+
export declare function getDcByPath(value: SlateEditorContent, path: number[]): DynamicContentElement | undefined;
|
|
5
|
+
export declare function findAllDc(value: SlateEditorContent): DynamicContentElement[];
|
|
6
|
+
export declare function findPath(value: SlateEditorContent, elToFind: Descendant): number[];
|
|
7
|
+
export declare function parseDc(value: string): DynamicContent;
|
|
8
|
+
export declare function stringifyDc(dc: DynamicContent): string;
|
|
9
|
+
export declare function string2content(value: string): SlateEditorContent;
|
|
10
|
+
export declare function content2string(state: SlateEditorContent): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export function isElement(x) {
|
|
2
|
+
return !!x.type;
|
|
3
|
+
}
|
|
4
|
+
export function getDcByPath(value, path) {
|
|
5
|
+
let current = value;
|
|
6
|
+
for (let i = 0; i < path.length; i += 1) {
|
|
7
|
+
const el = current[path[i]];
|
|
8
|
+
if (el) {
|
|
9
|
+
if (isElement(el) && el.type === 'dc') {
|
|
10
|
+
return el;
|
|
11
|
+
}
|
|
12
|
+
} else {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
current = el.children;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
export function findAllDc(value) {
|
|
20
|
+
const dces = [];
|
|
21
|
+
function walkDeep(children) {
|
|
22
|
+
for (const el of children) {
|
|
23
|
+
if (isElement(el)) {
|
|
24
|
+
if (el.type === 'dc') {
|
|
25
|
+
dces.push(el);
|
|
26
|
+
}
|
|
27
|
+
walkDeep(el.children);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
walkDeep(value);
|
|
32
|
+
return dces;
|
|
33
|
+
}
|
|
34
|
+
export function findPath(value, elToFind) {
|
|
35
|
+
for (let i = 0; i < value.length; i++) {
|
|
36
|
+
const el = value[i];
|
|
37
|
+
if (el === elToFind) {
|
|
38
|
+
return [i];
|
|
39
|
+
}
|
|
40
|
+
if (isElement(el)) {
|
|
41
|
+
const path = findPath(el.children, elToFind);
|
|
42
|
+
if (path.length > 0) {
|
|
43
|
+
return [i, ...path];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
export function parseDc(value) {
|
|
50
|
+
const [tagName, modifier, defaultValue = ''] = value.slice(1, -1).split('|');
|
|
51
|
+
return {
|
|
52
|
+
tagName,
|
|
53
|
+
modifier,
|
|
54
|
+
defaultValue
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function stringifyDc(dc) {
|
|
58
|
+
const {
|
|
59
|
+
tagName,
|
|
60
|
+
modifier,
|
|
61
|
+
defaultValue
|
|
62
|
+
} = dc;
|
|
63
|
+
return `{${tagName}|${modifier}${defaultValue ? `|${defaultValue}` : ''}}`;
|
|
64
|
+
}
|
|
65
|
+
// This regex matches a string that starts with '{', contains at least one '|', and ends with '}'.
|
|
66
|
+
// It captures the entire string, including the '{' and '}'.
|
|
67
|
+
// The lookahead (?=[^}]*\|) ensures that there is at least one '|' before the closing '}'.
|
|
68
|
+
// The regex is not greedy, so it will stop at the first '}' after the last '|'.
|
|
69
|
+
// This means it will match strings like '{tag|modifier|default}', '{tag|modifier}', and '{tag|modifier|}'.
|
|
70
|
+
// It will not match strings like '{tag|modifier|default}' if there is no '}' after the last '|'.
|
|
71
|
+
// This regex is used to split the string into parts, where the parts are either text or dynamic content.
|
|
72
|
+
const DynamicContentRegex = /(\{(?=[^}]*\|)[^}]*?})/;
|
|
73
|
+
export function string2content(value) {
|
|
74
|
+
return value.split('\n').map(line => ({
|
|
75
|
+
type: 'paragraph',
|
|
76
|
+
children: line.split(DynamicContentRegex).map((text, index) => {
|
|
77
|
+
if (index % 2 === 1) {
|
|
78
|
+
return {
|
|
79
|
+
type: 'dc',
|
|
80
|
+
dc: parseDc(text),
|
|
81
|
+
children: [{
|
|
82
|
+
text: ''
|
|
83
|
+
}]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
text
|
|
88
|
+
};
|
|
89
|
+
})
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
export function content2string(state) {
|
|
93
|
+
return state.map(el => {
|
|
94
|
+
const {
|
|
95
|
+
children
|
|
96
|
+
} = el;
|
|
97
|
+
return children.map(el => {
|
|
98
|
+
if (isElement(el) && el.type === 'dc') {
|
|
99
|
+
return stringifyDc(el.dc);
|
|
100
|
+
}
|
|
101
|
+
return el.text;
|
|
102
|
+
}).join('');
|
|
103
|
+
}).join('\n');
|
|
104
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Descendant } from 'slate';
|
|
2
|
+
export type SlateEditorContent = Descendant[];
|
|
3
|
+
export type DynamicContent = {
|
|
4
|
+
tagName: string;
|
|
5
|
+
modifier: string;
|
|
6
|
+
defaultValue: string;
|
|
7
|
+
};
|
|
8
|
+
export type ParagraphElement = {
|
|
9
|
+
type: 'paragraph';
|
|
10
|
+
children: SlateEditorContent;
|
|
11
|
+
};
|
|
12
|
+
export type CustomElement = DynamicContentElement | ParagraphElement;
|
|
13
|
+
export type CustomText = {
|
|
14
|
+
bold?: boolean;
|
|
15
|
+
italic?: boolean;
|
|
16
|
+
code?: boolean;
|
|
17
|
+
text: string;
|
|
18
|
+
};
|
|
19
|
+
export type EmptyText = {
|
|
20
|
+
text: string;
|
|
21
|
+
};
|
|
22
|
+
export type DynamicContentElement = {
|
|
23
|
+
type: 'dc';
|
|
24
|
+
dc: DynamicContent;
|
|
25
|
+
children: EmptyText[];
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushwoosh/dumb-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.45",
|
|
4
4
|
"description": "React components to build Pushwoosh products",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.js",
|
|
@@ -37,11 +37,12 @@
|
|
|
37
37
|
"@codemirror/lang-html": "^6.4.9",
|
|
38
38
|
"@codemirror/lang-json": "^6.0.1",
|
|
39
39
|
"@codemirror/lang-liquid": "^6.2.2",
|
|
40
|
+
"@emoji-mart/react": "^1.1.1",
|
|
40
41
|
"@floating-ui/react-dom": "^2.1.2",
|
|
41
42
|
"@lezer/highlight": "^1.2.1",
|
|
42
43
|
"@pushwoosh/kit-constants": "^1.6.9",
|
|
43
44
|
"@pushwoosh/kit-helpers": "^4.8.2",
|
|
44
|
-
"@pushwoosh/kit-icons": "^2.1.
|
|
45
|
+
"@pushwoosh/kit-icons": "^2.1.2",
|
|
45
46
|
"@pushwoosh/kit-typography": "^1.7.3",
|
|
46
47
|
"@tippyjs/react": "^4.2.6",
|
|
47
48
|
"@uiw/codemirror-themes": "^4.23.8",
|
|
@@ -50,7 +51,10 @@
|
|
|
50
51
|
"lodash": "^4.17.21",
|
|
51
52
|
"polished": "^4.3.1",
|
|
52
53
|
"react-select": "^5.10.0",
|
|
53
|
-
"react-transition-group": "^4.4.5"
|
|
54
|
+
"react-transition-group": "^4.4.5",
|
|
55
|
+
"slate": "^0.112.0",
|
|
56
|
+
"slate-history": "^0.110.3",
|
|
57
|
+
"slate-react": "^0.112.1"
|
|
54
58
|
},
|
|
55
59
|
"peerDependencies": {
|
|
56
60
|
"react": ">= 16.14.0",
|