@pushwoosh/dumb-components 1.0.42 → 1.0.44

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.
Files changed (45) hide show
  1. package/Chip/Chip.constants.d.ts +5 -0
  2. package/Chip/Chip.constants.js +15 -0
  3. package/Chip/Chip.d.ts +8 -0
  4. package/Chip/Chip.js +23 -0
  5. package/Chip/Chip.types.d.ts +7 -0
  6. package/Chip/Chip.types.js +1 -0
  7. package/Chip/index.d.ts +1 -0
  8. package/Chip/index.js +1 -0
  9. package/Chip/styles.d.ts +6 -0
  10. package/Chip/styles.js +15 -0
  11. package/PushPreview/PushPreview.js +2 -2
  12. package/PushPreview/types.d.ts +2 -1
  13. package/RichMessageEditor/DropContent.d.ts +5 -0
  14. package/RichMessageEditor/DropContent.js +14 -0
  15. package/RichMessageEditor/EmojiPicker.d.ts +6 -0
  16. package/RichMessageEditor/EmojiPicker.js +17 -0
  17. package/RichMessageEditor/RichMessageEditor.d.ts +3 -0
  18. package/RichMessageEditor/RichMessageEditor.js +114 -0
  19. package/RichMessageEditor/RichMessageEditor.styled.d.ts +2 -0
  20. package/RichMessageEditor/RichMessageEditor.styled.js +10 -0
  21. package/RichMessageEditor/RichMessageEditor.types.d.ts +27 -0
  22. package/RichMessageEditor/RichMessageEditor.types.js +1 -0
  23. package/RichMessageEditor/RichMessageViewer.d.ts +6 -0
  24. package/RichMessageEditor/RichMessageViewer.js +28 -0
  25. package/RichMessageEditor/RichMessageViewer.styled.d.ts +2 -0
  26. package/RichMessageEditor/RichMessageViewer.styled.js +10 -0
  27. package/RichMessageEditor/assets/EmojiIcon.svg +14 -0
  28. package/RichMessageEditor/assets/PersonalizationIcon.svg +14 -0
  29. package/RichMessageEditor/index.d.ts +3 -0
  30. package/RichMessageEditor/index.js +2 -0
  31. package/RichMessageEditor/slate/DynamicContentElement.d.ts +2 -0
  32. package/RichMessageEditor/slate/DynamicContentElement.js +53 -0
  33. package/RichMessageEditor/slate/SlateEditor.d.ts +18 -0
  34. package/RichMessageEditor/slate/SlateEditor.js +33 -0
  35. package/RichMessageEditor/slate/SlateEditor.styled.d.ts +16 -0
  36. package/RichMessageEditor/slate/SlateEditor.styled.js +9 -0
  37. package/RichMessageEditor/slate/SlateEditor.utils.d.ts +5 -0
  38. package/RichMessageEditor/slate/SlateEditor.utils.js +28 -0
  39. package/RichMessageEditor/slate/helpers.d.ts +10 -0
  40. package/RichMessageEditor/slate/helpers.js +104 -0
  41. package/RichMessageEditor/slate/types.d.ts +26 -0
  42. package/RichMessageEditor/slate/types.js +1 -0
  43. package/RichMessageEditor/types.d.ts +2 -0
  44. package/RichMessageEditor/types.js +1 -0
  45. package/package.json +7 -3
@@ -0,0 +1,5 @@
1
+ import { type ChipVariant } from './Chip.types';
2
+ export declare const variants: Record<ChipVariant, {
3
+ color: string;
4
+ background: string;
5
+ }>;
@@ -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 {};
@@ -0,0 +1 @@
1
+ export { Chip } from './Chip';
package/Chip/index.js ADDED
@@ -0,0 +1 @@
1
+ export { Chip } from './Chip';
@@ -0,0 +1,6 @@
1
+ import type { ChipVariant } from './Chip.types';
2
+ export declare const ChipStyled: import("styled-components").StyledComponent<"div", any, {
3
+ $variant: ChipVariant;
4
+ $selected?: boolean;
5
+ $closeable?: boolean;
6
+ }, never>;
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);
@@ -15,8 +15,8 @@ export function PushPreview(props) {
15
15
  isLoading = false,
16
16
  width
17
17
  } = props;
18
- const title = getLocalizedContent(pushPreset === null || pushPreset === void 0 ? void 0 : pushPreset.title, language) || appTitle;
19
- const content = getLocalizedContent(pushPreset === null || pushPreset === void 0 ? void 0 : pushPreset.content, language);
18
+ const title = getLocalizedContent((pushPreset === null || pushPreset === void 0 ? void 0 : pushPreset.title) || (pushPreset === null || pushPreset === void 0 ? void 0 : pushPreset.localizedTitle), language) || appTitle;
19
+ const content = getLocalizedContent((pushPreset === null || pushPreset === void 0 ? void 0 : pushPreset.content) || (pushPreset === null || pushPreset === void 0 ? void 0 : pushPreset.localizedContent), language);
20
20
  const icon = content ? (pushPreset === null || pushPreset === void 0 ? void 0 : pushPreset.icon) || appIcon : PREVIEW_ICON_URL;
21
21
  const isLocked = !pushPreset || !content || view === 'silent';
22
22
  if (isLoading) {
@@ -1,8 +1,9 @@
1
1
  export type PushPreviewView = 'compact' | 'extended' | 'silent';
2
2
  export interface PushBasicData {
3
3
  title?: Record<string, string>;
4
- subTitle?: Record<string, string>;
5
4
  content?: Record<string, string>;
5
+ localizedTitle?: Record<string, string>;
6
+ localizedContent?: Record<string, string>;
6
7
  icon?: string;
7
8
  }
8
9
  export interface PushPreviewProps<T> {
@@ -0,0 +1,5 @@
1
+ import { type FC, type PropsWithChildren } from 'react';
2
+ export declare const DropContent: FC<PropsWithChildren<{
3
+ top: number;
4
+ left: number;
5
+ }>>;
@@ -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,6 @@
1
+ import { type FC } from 'react';
2
+ type EmojiPickerProps = {
3
+ onEmojiSelect: (emoji: string) => void;
4
+ };
5
+ export declare const EmojiPicker: FC<EmojiPickerProps>;
6
+ export {};
@@ -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,3 @@
1
+ import { type FC } from 'react';
2
+ import { type RichMessageEditorProps } from './RichMessageEditor.types';
3
+ export declare const RichMessageEditor: FC<RichMessageEditorProps>;
@@ -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,2 @@
1
+ export declare const ImSpan: import("styled-components").StyledComponent<"span", any, {}, never>;
2
+ export declare const DropContentStyled: import("styled-components").StyledComponent<"div", any, {}, never>;
@@ -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,6 @@
1
+ import { type FC } from 'react';
2
+ import { type RichMessageEditorContent } from './types';
3
+ export declare const RichMessageViewer: FC<{
4
+ value: RichMessageEditorContent | string;
5
+ lineBreaks?: boolean;
6
+ }>;
@@ -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,2 @@
1
+ export declare const DynamicContentRoot: import("styled-components").StyledComponent<"span", any, {}, never>;
2
+ export declare const DynamicContentBracket: import("styled-components").StyledComponent<"span", any, {}, never>;
@@ -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,3 @@
1
+ export { type RichMessageEditorContent } from './types';
2
+ export { RichMessageEditor } from './RichMessageEditor';
3
+ export { RichMessageViewer } from './RichMessageViewer';
@@ -0,0 +1,2 @@
1
+ export { RichMessageEditor } from './RichMessageEditor';
2
+ export { RichMessageViewer } from './RichMessageViewer';
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare function renderElement(props: any): React.JSX.Element;
@@ -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,2 @@
1
+ import { type SlateEditorContent as RichMessageEditorContent } from './slate/types';
2
+ export { type RichMessageEditorContent };
@@ -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.42",
3
+ "version": "1.0.44",
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.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",