@team-monolith/cds 1.6.0 → 1.7.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.
Files changed (24) hide show
  1. package/dist/components/Popover.d.ts +16 -0
  2. package/dist/components/Popover.js +59 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +2 -0
  5. package/dist/patterns/LexicalEditor/LexicalEditor.js +8 -0
  6. package/dist/patterns/LexicalEditor/nodes/ColoredQuoteNode.d.ts +21 -0
  7. package/dist/patterns/LexicalEditor/nodes/ColoredQuoteNode.js +50 -0
  8. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdder.js +1 -1
  9. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +19 -16
  10. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/getContextMenuOptions.js +117 -10
  11. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.d.ts +6 -0
  12. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +172 -0
  13. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuItem.js +2 -1
  14. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +5 -3
  15. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +1 -0
  16. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/FloatingLinkEditor.js +1 -1
  17. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/index.js +1 -1
  18. package/dist/patterns/LexicalEditor/theme.d.ts +7 -0
  19. package/dist/patterns/LexicalEditor/theme.js +73 -1
  20. package/dist/patterns/SegmentedControl/SegmentedControlButton.d.ts +3 -1
  21. package/dist/patterns/SegmentedControl/SegmentedControlButton.js +3 -6
  22. package/dist/patterns/SegmentedControl/SegmentedControlSquareButton.d.ts +2 -1
  23. package/dist/patterns/SegmentedControl/SegmentedControlSquareButton.js +4 -7
  24. package/package.json +1 -1
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ export interface PopoverProps {
3
+ className?: string;
4
+ /** 제목 */
5
+ headline?: React.ReactNode;
6
+ /** 내용 */
7
+ content?: React.ReactNode;
8
+ /** 좌측 아이콘 */
9
+ icon?: React.ReactNode;
10
+ /** 우측 버튼 텍스트 */
11
+ buttonLabel?: string;
12
+ /** 우측 버튼 클릭시 동작 */
13
+ onButtonClick?: () => void;
14
+ }
15
+ declare const Popover: React.ForwardRefExoticComponent<PopoverProps & React.RefAttributes<HTMLDivElement>>;
16
+ export default Popover;
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styled from "@emotion/styled";
3
+ import React from "react";
4
+ import shadows from "../foundation/shadows";
5
+ import { RESET_BUTTON } from "../utils/reset";
6
+ import { HOVER } from "../utils/hover";
7
+ import { css } from "@emotion/react";
8
+ const Popover = React.forwardRef((props, ref) => {
9
+ const { className, headline, content, icon, buttonLabel, onButtonClick } = props;
10
+ return (_jsxs(Container, Object.assign({ ref: ref, className: className }, { children: [icon && _jsx(Icon, { children: icon }), _jsxs(ContentArea, { children: [headline && _jsx(Headline, { children: headline }), content && _jsx(Content, { children: content })] }), buttonLabel && (_jsx(PopoverButton, Object.assign({ type: "button", onClick: onButtonClick }, { children: buttonLabel })))] })));
11
+ });
12
+ export default Popover;
13
+ const Container = styled.div(({ theme }) => css `
14
+ display: inline-flex;
15
+ align-items: center;
16
+ width: auto;
17
+ padding: 6px 16px;
18
+ gap: 12px;
19
+ border-radius: 100px;
20
+ background-color: ${theme.color.foreground.neutralBase};
21
+ color: ${theme.color.background.neutralBase};
22
+ box-shadow: ${shadows.shadow32};
23
+ `);
24
+ const Icon = styled.div `
25
+ display: block;
26
+ width: 20px;
27
+ height: 20px;
28
+ svg {
29
+ width: 100%;
30
+ height: 100%;
31
+ }
32
+ `;
33
+ const ContentArea = styled.div `
34
+ flex: 1 0 auto;
35
+ font-size: 14px;
36
+ line-height: 20px;
37
+ `;
38
+ const Headline = styled.div `
39
+ margin-bottom: 2px;
40
+ font-weight: 700;
41
+ `;
42
+ const Content = styled.div `
43
+ font-weight: 400;
44
+ `;
45
+ const PopoverButton = styled.button(({ theme }) => css `
46
+ ${RESET_BUTTON}
47
+ margin: 4px;
48
+ padding: 6px 8px;
49
+ cursor: pointer;
50
+ background-color: ${theme.color.background.primary};
51
+ color: ${theme.color.background.neutralAlt};
52
+ font-size: 14px;
53
+ line-height: 16px;
54
+ border-radius: 8px;
55
+ ${HOVER(css `
56
+ color: ${theme.color.foreground.neutralAlt};
57
+ background: ${theme.color.background.primaryActive};
58
+ `)}
59
+ `);
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export { default as Pagination } from "./components/Pagination";
12
12
  export * from "./components/Pagination";
13
13
  export { default as PinInput } from "./components/PinInput";
14
14
  export * from "./components/PinInput";
15
+ export { default as Popover } from "./components/Popover";
16
+ export * from "./components/Popover";
15
17
  export { default as RadioInput } from "./components/RadioInput";
16
18
  export * from "./components/RadioInput";
17
19
  export { default as SquareButton } from "./components/SquareButton";
package/dist/index.js CHANGED
@@ -12,6 +12,8 @@ export { default as Pagination } from "./components/Pagination";
12
12
  export * from "./components/Pagination";
13
13
  export { default as PinInput } from "./components/PinInput";
14
14
  export * from "./components/PinInput";
15
+ export { default as Popover } from "./components/Popover";
16
+ export * from "./components/Popover";
15
17
  export { default as RadioInput } from "./components/RadioInput";
16
18
  export * from "./components/RadioInput";
17
19
  export { default as SquareButton } from "./components/SquareButton";
@@ -10,6 +10,7 @@ import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
10
10
  import { getTheme } from "./theme";
11
11
  import { useTheme } from "@emotion/react";
12
12
  import Plugins from "./Plugins";
13
+ import { ColoredQuoteNode } from "./nodes/ColoredQuoteNode";
13
14
  function validateValue(value) {
14
15
  var _a, _b;
15
16
  if (value && typeof value !== "object") {
@@ -46,6 +47,13 @@ export function LexicalEditor(props) {
46
47
  LinkNode,
47
48
  ImageNode,
48
49
  HorizontalRuleNode,
50
+ ColoredQuoteNode,
51
+ {
52
+ replace: QuoteNode,
53
+ with: (_node) => {
54
+ return new ColoredQuoteNode("grey");
55
+ },
56
+ },
49
57
  ],
50
58
  theme: getTheme(theme),
51
59
  editorState: validateValue(value) ? JSON.stringify(value) : undefined,
@@ -0,0 +1,21 @@
1
+ import { QuoteNode } from "@lexical/rich-text";
2
+ import { EditorConfig, LexicalNode, NodeKey, SerializedElementNode } from "lexical";
3
+ type COLOR = "red" | "yellow" | "blue" | "green" | "grey";
4
+ export type SerializedColoredQuoteNode = SerializedElementNode & {
5
+ color: COLOR;
6
+ };
7
+ export declare class ColoredQuoteNode extends QuoteNode {
8
+ __color: COLOR;
9
+ static getType(): string;
10
+ static clone(node: ColoredQuoteNode): ColoredQuoteNode;
11
+ constructor(color: COLOR, key?: NodeKey);
12
+ setColor(color: COLOR): void;
13
+ getColor(): COLOR;
14
+ createDOM(config: EditorConfig): HTMLElement;
15
+ updateDOM(prevNode: ColoredQuoteNode, dom: HTMLElement): boolean;
16
+ static importJSON(serializedNode: SerializedColoredQuoteNode): QuoteNode;
17
+ exportJSON(): SerializedColoredQuoteNode;
18
+ }
19
+ export declare function $createColoredQuoteNode(color: COLOR): QuoteNode;
20
+ export declare function $isColoredQuoteNode(node: LexicalNode | null | undefined): node is ColoredQuoteNode;
21
+ export {};
@@ -0,0 +1,50 @@
1
+ import { QuoteNode } from "@lexical/rich-text";
2
+ import { $applyNodeReplacement, } from "lexical";
3
+ import { addClassNamesToElement } from "@lexical/utils";
4
+ export class ColoredQuoteNode extends QuoteNode {
5
+ static getType() {
6
+ return "colored-quote";
7
+ }
8
+ static clone(node) {
9
+ return new ColoredQuoteNode(node.__color, node.__key);
10
+ }
11
+ constructor(color, key) {
12
+ super(key);
13
+ this.__color = color;
14
+ }
15
+ setColor(color) {
16
+ const self = this.getWritable();
17
+ self.__color = color;
18
+ }
19
+ getColor() {
20
+ const self = this.getLatest();
21
+ return self.__color;
22
+ }
23
+ createDOM(config) {
24
+ const element = document.createElement("blockquote");
25
+ addClassNamesToElement(element, config.theme.coloredQuote[this.__color]);
26
+ return element;
27
+ }
28
+ updateDOM(prevNode, dom) {
29
+ if (prevNode.__color !== this.__color) {
30
+ return true;
31
+ }
32
+ return super.updateDOM(prevNode, dom);
33
+ }
34
+ static importJSON(serializedNode) {
35
+ const node = $createColoredQuoteNode(serializedNode.color);
36
+ node.setFormat(serializedNode.format);
37
+ node.setIndent(serializedNode.indent);
38
+ node.setDirection(serializedNode.direction);
39
+ return node;
40
+ }
41
+ exportJSON() {
42
+ return Object.assign(Object.assign({}, super.exportJSON()), { type: "colored-quote", color: this.__color });
43
+ }
44
+ }
45
+ export function $createColoredQuoteNode(color) {
46
+ return $applyNodeReplacement(new ColoredQuoteNode(color));
47
+ }
48
+ export function $isColoredQuoteNode(node) {
49
+ return node instanceof ColoredQuoteNode;
50
+ }
@@ -5,7 +5,7 @@ import plus from "./plus.svg";
5
5
  import menu from "./menu.svg";
6
6
  import styled from "@emotion/styled";
7
7
  function ComponentAdder(props, ref) {
8
- const { className, onPlusClick, onMenuClick, onMenuDragStart, onMenuDragEnd } = props;
8
+ const { className, onPlusClick, onMenuClick, onMenuDragStart, onMenuDragEnd, } = props;
9
9
  const [editor] = useLexicalComposerContext();
10
10
  return (_jsxs(Container, Object.assign({ ref: ref, className: className }, { children: [_jsx(PlusContainer, Object.assign({ onClick: onPlusClick }, { children: editor._editable && _jsx(PlusIcon, {}) })), _jsx(MenuContainer, Object.assign({ draggable: true, onClick: onMenuClick, onDragStart: onMenuDragStart, onDragEnd: onMenuDragEnd }, { children: editor._editable && _jsx(MenuIcon, {}) }))] })));
11
11
  }
@@ -15,7 +15,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/reac
15
15
  /** @jsxImportSource @emotion/react */
16
16
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
17
17
  import { useCallback, useEffect, useRef, useState } from "react";
18
- import { $getNearestNodeFromDOMNode, $createParagraphNode, $isParagraphNode, $isElementNode, $isTextNode, $getSelection, $isRangeSelection, } from "lexical";
18
+ import { $getNearestNodeFromDOMNode, $createParagraphNode, $isParagraphNode, $getSelection, $isRangeSelection, $createNodeSelection, $setSelection, $isNodeSelection, } from "lexical";
19
19
  import ReactDOM, { createPortal } from "react-dom";
20
20
  // LexicalNodeMenuPlugin는 특정 Node 옆에 메뉴를 노출시키는 플러그인
21
21
  // 공식 문서에는 아무 내용도 없음...
@@ -26,7 +26,7 @@ import { ComponentPickerMenuList, getBaseOptions, } from "../ComponentPickerMenu
26
26
  import { useFloatingMenu } from "./useFloatingMenu";
27
27
  import ComponentAdder from "./ComponentAdder";
28
28
  import styled from "@emotion/styled";
29
- import { getContextMenuOptions } from "./getContextMenuOptions";
29
+ import { useContextMenuOptions } from "./useContextMenuOptions";
30
30
  import { InsertImageDialog } from "../ImagesPlugin/InsertImageDialog";
31
31
  import { ZINDEX } from "../../../../utils/zIndex";
32
32
  export const COMPONENT_ADDER_MENU_CLASSNAME = "component-adder-menu";
@@ -66,7 +66,7 @@ export function ComponentAdderPlugin(props) {
66
66
  useEffect(() => {
67
67
  const updateListener = () => {
68
68
  editor.getEditorState().read(() => {
69
- var _a;
69
+ var _a, _b;
70
70
  if (nodeKey === null) {
71
71
  return;
72
72
  }
@@ -77,15 +77,11 @@ export function ComponentAdderPlugin(props) {
77
77
  // 비교할 때는 selection이 Nested 하게 있을 수 있어 Root 바로 아래의 Node를 찾아 비교합니다.
78
78
  const nodes = selection.getNodes();
79
79
  const isAllNodesNotMatched = nodes.every((node) => {
80
- const nodeAndParents = [node, ...node.getParents()];
81
- const maybeTopNode = nodeAndParents[nodeAndParents.length - 2];
82
- // 선택된 노드 자체가 Root Node라면
83
- // maybeTopNode가 undefined가 됩니다.
84
- // 다른 노드를 선택한 것으로 간주하고 true(not matched)를 반환합니다.
85
- if (!maybeTopNode) {
80
+ const topLevelElement = node.getTopLevelElement();
81
+ if (!topLevelElement) {
86
82
  return true;
87
83
  }
88
- return maybeTopNode.getKey() !== nodeKey;
84
+ return topLevelElement.getKey() !== nodeKey;
89
85
  });
90
86
  if (isAllNodesNotMatched) {
91
87
  setQuery("");
@@ -98,6 +94,13 @@ export function ComponentAdderPlugin(props) {
98
94
  setQuery((_a = getTextUpToAnchor(selection)) !== null && _a !== void 0 ? _a : "");
99
95
  }
100
96
  }
97
+ else if ($isNodeSelection(selection)) {
98
+ const nodes = selection.getNodes();
99
+ if (((_b = nodes[0]) === null || _b === void 0 ? void 0 : _b.getKey()) !== nodeKey) {
100
+ setQuery("");
101
+ setNodeKey(null);
102
+ }
103
+ }
101
104
  else {
102
105
  setQuery("");
103
106
  setNodeKey(null);
@@ -123,6 +126,7 @@ export function ComponentAdderPlugin(props) {
123
126
  return newResolves.filter((newResolve) => resolves.indexOf(newResolve) === -1);
124
127
  });
125
128
  });
129
+ const getContextMenuOptions = useContextMenuOptions();
126
130
  const filteredOptions = options.filter((option) => {
127
131
  const regex = new RegExp(query, "i");
128
132
  return (regex.test(option.title) ||
@@ -187,12 +191,11 @@ export function ComponentAdderPlugin(props) {
187
191
  if (!newNode) {
188
192
  return;
189
193
  }
190
- if ($isElementNode(newNode)) {
191
- newNode.selectStart();
192
- }
193
- else if ($isTextNode(newNode)) {
194
- newNode.select();
195
- }
194
+ // Set a node selection
195
+ const nodeSelection = $createNodeSelection();
196
+ // Add a node key to the selection.
197
+ nodeSelection.add(newNode.getKey());
198
+ $setSelection(nodeSelection);
196
199
  const contextMenuOptions = getContextMenuOptions(editor, newNode, setOpen);
197
200
  setOptions(contextMenuOptions);
198
201
  if (nodeKey === null) {
@@ -2,12 +2,112 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
3
  * Context Menu (:: 버튼)
4
4
  */
5
- import { $getSelection, $isRangeSelection, } from "lexical";
5
+ import { $createParagraphNode, ParagraphNode, } from "lexical";
6
6
  import { ComponentPickerOption, getBaseOptions, } from "../ComponentPickerMenuPlugin";
7
- import { $createHeadingNode, HeadingNode } from "@lexical/rich-text";
7
+ import { $createHeadingNode, $createQuoteNode, HeadingNode, } from "@lexical/rich-text";
8
8
  import { $setBlocksType } from "@lexical/selection";
9
9
  import { ListItemNode, ListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
10
- import { H1Icon, H2Icon, H3Icon, CloseFillIcon, ListUnorderedIcon, ListOrderedIcon, } from "../../../../icons";
10
+ import { H1Icon, H2Icon, H3Icon, CloseFillIcon, ListUnorderedIcon, ListOrderedIcon, CodeViewIcon, DoubleQuotesLIcon, ImageLineIcon, SeparatorIcon, TextIcon, } from "../../../../icons";
11
+ import { INSERT_HORIZONTAL_RULE_COMMAND } from "@lexical/react/LexicalHorizontalRuleNode";
12
+ import { $createCodeNode } from "@lexical/code";
13
+ function getParagraphContextOptions(editor, node, setOpen) {
14
+ return [
15
+ new ComponentPickerOption("본문", {
16
+ icon: _jsx(TextIcon, {}),
17
+ keywords: ["normal", "paragraph", "p", "text", "본문", "단락", "내용"],
18
+ onSelect: () => editor.update(() => {
19
+ const selection = node.selectStart();
20
+ $setBlocksType(selection, () => $createParagraphNode());
21
+ }),
22
+ }),
23
+ ...[1, 2, 3].map((n) => {
24
+ const titleMap = {
25
+ 1: "큰 제목",
26
+ 2: "중간 제목",
27
+ 3: "작은 제목",
28
+ };
29
+ const iconMap = {
30
+ 1: _jsx(H1Icon, {}),
31
+ 2: _jsx(H2Icon, {}),
32
+ 3: _jsx(H3Icon, {}),
33
+ };
34
+ return new ComponentPickerOption(titleMap[n], {
35
+ icon: iconMap[n],
36
+ keywords: ["heading", "header", `h${n}`, titleMap[n]],
37
+ onSelect: () => editor.update(() => {
38
+ const selection = node.selectStart();
39
+ $setBlocksType(selection, () => $createHeadingNode(`h${n}`));
40
+ }),
41
+ });
42
+ }),
43
+ new ComponentPickerOption("일반 목록", {
44
+ icon: _jsx(ListUnorderedIcon, {}),
45
+ keywords: ["bulleted list", "unordered list", "ul"],
46
+ onSelect: () => {
47
+ node.selectStart();
48
+ editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
49
+ },
50
+ }),
51
+ new ComponentPickerOption("숫자 목록", {
52
+ icon: _jsx(ListOrderedIcon, {}),
53
+ keywords: ["numbered list", "ordered list", "ol"],
54
+ onSelect: () => {
55
+ node.selectStart();
56
+ editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
57
+ },
58
+ }),
59
+ new ComponentPickerOption("인용 블록", {
60
+ icon: _jsx(DoubleQuotesLIcon, {}),
61
+ keywords: ["block quote"],
62
+ onSelect: () => editor.update(() => {
63
+ const selection = node.selectStart();
64
+ $setBlocksType(selection, () => $createQuoteNode());
65
+ }),
66
+ }),
67
+ new ComponentPickerOption("코드 블록", {
68
+ icon: _jsx(CodeViewIcon, {}),
69
+ keywords: ["javascript", "python", "js", "codeblock"],
70
+ onSelect: () => editor.update(() => {
71
+ const selection = node.selectStart();
72
+ if (selection.isCollapsed()) {
73
+ $setBlocksType(selection, () => $createCodeNode());
74
+ }
75
+ else {
76
+ // Will this ever happen?
77
+ const textContent = selection.getTextContent();
78
+ const codeNode = $createCodeNode();
79
+ selection.insertNodes([codeNode]);
80
+ selection.insertRawText(textContent);
81
+ }
82
+ }),
83
+ }),
84
+ new ComponentPickerOption("구분선", {
85
+ icon: _jsx(SeparatorIcon, {}),
86
+ keywords: ["horizontal rule", "divider", "hr"],
87
+ onSelect: () => {
88
+ node.selectStart();
89
+ editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined);
90
+ },
91
+ }),
92
+ new ComponentPickerOption("이미지", {
93
+ icon: _jsx(ImageLineIcon, {}),
94
+ keywords: ["image", "photo", "picture", "file"],
95
+ onSelect: () => {
96
+ node.selectStart();
97
+ setOpen(true);
98
+ },
99
+ }),
100
+ new ComponentPickerOption("블록 삭제", {
101
+ icon: _jsx(CloseFillIcon, {}),
102
+ keywords: [],
103
+ onSelect: () => {
104
+ editor.update(() => {
105
+ node.remove();
106
+ });
107
+ },
108
+ }),
109
+ ];
110
+ }
11
111
  function getHeadingContextMenuOptions(editor, node, setOpen) {
12
112
  return [
13
113
  ...[1, 2, 3].map((n) => {
@@ -25,10 +125,8 @@ function getHeadingContextMenuOptions(editor, node, setOpen) {
25
125
  icon: iconMap[n],
26
126
  keywords: ["heading", "header", `h${n}`, titleMap[n]],
27
127
  onSelect: () => editor.update(() => {
28
- const selection = $getSelection();
29
- if ($isRangeSelection(selection)) {
30
- $setBlocksType(selection, () => $createHeadingNode(`h${n}`));
31
- }
128
+ const selection = node.selectStart();
129
+ $setBlocksType(selection, () => $createHeadingNode(`h${n}`));
32
130
  }),
33
131
  });
34
132
  }),
@@ -48,12 +146,18 @@ function getListContextMenuOptions(editor, node, setOpen) {
48
146
  new ComponentPickerOption("일반 목록", {
49
147
  icon: _jsx(ListUnorderedIcon, {}),
50
148
  keywords: ["bulleted list", "unordered list", "ul"],
51
- onSelect: () => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
149
+ onSelect: () => {
150
+ node.selectStart();
151
+ editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
152
+ },
52
153
  }),
53
154
  new ComponentPickerOption("숫자 목록", {
54
155
  icon: _jsx(ListOrderedIcon, {}),
55
156
  keywords: ["numbered list", "ordered list", "ol"],
56
- onSelect: () => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined),
157
+ onSelect: () => {
158
+ node.selectStart();
159
+ editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
160
+ },
57
161
  }),
58
162
  new ComponentPickerOption("블록 삭제", {
59
163
  icon: _jsx(CloseFillIcon, {}),
@@ -67,7 +171,10 @@ function getListContextMenuOptions(editor, node, setOpen) {
67
171
  ];
68
172
  }
69
173
  export function getContextMenuOptions(editor, node, setOpen) {
70
- if (node instanceof HeadingNode) {
174
+ if (node instanceof ParagraphNode) {
175
+ return getParagraphContextOptions(editor, node, setOpen);
176
+ }
177
+ else if (node instanceof HeadingNode) {
71
178
  return getHeadingContextMenuOptions(editor, node, setOpen);
72
179
  }
73
180
  else if (node instanceof ListNode || node instanceof ListItemNode) {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Context Menu (:: 버튼)
3
+ */
4
+ import { LexicalEditor, LexicalNode } from "lexical";
5
+ import { ComponentPickerOption } from "../ComponentPickerMenuPlugin";
6
+ export declare function useContextMenuOptions(): (editor: LexicalEditor, node: LexicalNode, setOpen: (open: boolean) => void) => ComponentPickerOption[];
@@ -0,0 +1,172 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Context Menu (:: 버튼)
4
+ */
5
+ import { $getSelection, $isRangeSelection, } from "lexical";
6
+ import { ComponentPickerOption, getBaseOptions, } from "../ComponentPickerMenuPlugin";
7
+ import { $createHeadingNode, HeadingNode } from "@lexical/rich-text";
8
+ import { $setBlocksType } from "@lexical/selection";
9
+ import { ListItemNode, ListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
10
+ import { H1Icon, H2Icon, H3Icon, CloseFillIcon, ListUnorderedIcon, ListOrderedIcon, InputMethodLineIcon, } from "../../../../icons";
11
+ import { $isColoredQuoteNode, } from "../../nodes/ColoredQuoteNode";
12
+ import { useTheme } from "@emotion/react";
13
+ import { css } from "@emotion/css";
14
+ function getHeadingContextMenuOptions(editor, theme, node, setOpen) {
15
+ return [
16
+ ...[1, 2, 3].map((n) => {
17
+ const titleMap = {
18
+ 1: "큰 제목",
19
+ 2: "중간 제목",
20
+ 3: "작은 제목",
21
+ };
22
+ const iconMap = {
23
+ 1: _jsx(H1Icon, {}),
24
+ 2: _jsx(H2Icon, {}),
25
+ 3: _jsx(H3Icon, {}),
26
+ };
27
+ return new ComponentPickerOption(titleMap[n], {
28
+ icon: iconMap[n],
29
+ keywords: ["heading", "header", `h${n}`, titleMap[n]],
30
+ onSelect: () => editor.update(() => {
31
+ const selection = $getSelection();
32
+ if ($isRangeSelection(selection)) {
33
+ $setBlocksType(selection, () => $createHeadingNode(`h${n}`));
34
+ }
35
+ }),
36
+ });
37
+ }),
38
+ new ComponentPickerOption("블록 삭제", {
39
+ icon: _jsx(CloseFillIcon, {}),
40
+ keywords: [],
41
+ onSelect: () => {
42
+ editor.update(() => {
43
+ node.remove();
44
+ });
45
+ },
46
+ }),
47
+ ];
48
+ }
49
+ function getListContextMenuOptions(editor, theme, node, setOpen) {
50
+ return [
51
+ new ComponentPickerOption("일반 목록", {
52
+ icon: _jsx(ListUnorderedIcon, {}),
53
+ keywords: ["bulleted list", "unordered list", "ul"],
54
+ onSelect: () => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
55
+ }),
56
+ new ComponentPickerOption("숫자 목록", {
57
+ icon: _jsx(ListOrderedIcon, {}),
58
+ keywords: ["numbered list", "ordered list", "ol"],
59
+ onSelect: () => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined),
60
+ }),
61
+ new ComponentPickerOption("블록 삭제", {
62
+ icon: _jsx(CloseFillIcon, {}),
63
+ keywords: [],
64
+ onSelect: () => {
65
+ editor.update(() => {
66
+ node.remove();
67
+ });
68
+ },
69
+ }),
70
+ ];
71
+ }
72
+ function getColoredQuoteContextMenuOptions(editor, theme, node, setOpen) {
73
+ return [
74
+ new ComponentPickerOption("회색", {
75
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.marbleOnContainer })),
76
+ iconContainerClassName: css `
77
+ background: ${theme.color.container.marbleContainer};
78
+ `,
79
+ keywords: ["grey", "gray"],
80
+ onSelect: () => {
81
+ editor.update(() => {
82
+ node.setColor("grey");
83
+ });
84
+ },
85
+ }),
86
+ new ComponentPickerOption("빨간색", {
87
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.dangerOnContainer })),
88
+ iconContainerClassName: css `
89
+ background: ${theme.color.container.dangerContainer};
90
+ `,
91
+ keywords: ["red"],
92
+ onSelect: () => {
93
+ editor.update(() => {
94
+ node.setColor("red");
95
+ });
96
+ },
97
+ }),
98
+ new ComponentPickerOption("노란색", {
99
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.warningOnContainer })),
100
+ iconContainerClassName: css `
101
+ background: ${theme.color.container.warningContainer};
102
+ `,
103
+ keywords: ["yellow"],
104
+ onSelect: () => {
105
+ editor.update(() => {
106
+ node.setColor("yellow");
107
+ });
108
+ },
109
+ }),
110
+ new ComponentPickerOption("파란색", {
111
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.primaryOnContainer })),
112
+ iconContainerClassName: css `
113
+ background: ${theme.color.container.primaryContainer};
114
+ `,
115
+ keywords: ["blue"],
116
+ onSelect: () => {
117
+ editor.update(() => {
118
+ node.setColor("blue");
119
+ });
120
+ },
121
+ }),
122
+ new ComponentPickerOption("초록색", {
123
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.successOnContainer })),
124
+ iconContainerClassName: css `
125
+ background: ${theme.color.container.successContainer};
126
+ `,
127
+ keywords: ["green"],
128
+ onSelect: () => {
129
+ editor.update(() => {
130
+ node.setColor("green");
131
+ });
132
+ },
133
+ }),
134
+ new ComponentPickerOption("블록 삭제", {
135
+ icon: _jsx(CloseFillIcon, {}),
136
+ keywords: [],
137
+ onSelect: () => {
138
+ editor.update(() => {
139
+ node.remove();
140
+ });
141
+ },
142
+ }),
143
+ ];
144
+ }
145
+ export function useContextMenuOptions() {
146
+ const theme = useTheme();
147
+ return (editor, node, setOpen) => {
148
+ if (node instanceof HeadingNode) {
149
+ return getHeadingContextMenuOptions(editor, theme, node, setOpen);
150
+ }
151
+ else if (node instanceof ListNode || node instanceof ListItemNode) {
152
+ return getListContextMenuOptions(editor, theme, node, setOpen);
153
+ }
154
+ else if ($isColoredQuoteNode(node)) {
155
+ return getColoredQuoteContextMenuOptions(editor, theme, node, setOpen);
156
+ }
157
+ else {
158
+ return [
159
+ ...getBaseOptions(editor, setOpen),
160
+ new ComponentPickerOption("블록 삭제", {
161
+ icon: _jsx(CloseFillIcon, {}),
162
+ keywords: [],
163
+ onSelect: () => {
164
+ editor.update(() => {
165
+ node.remove();
166
+ });
167
+ },
168
+ }),
169
+ ];
170
+ }
171
+ };
172
+ }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  import styled from "@emotion/styled";
3
3
  export function ComponentPickerMenuItem(props) {
4
4
  const { index, isSelected, onClick, onMouseEnter, option } = props;
5
- return (_jsxs(ItemContainer, Object.assign({ tabIndex: -1, ref: option.setRefElement, role: "option", "aria-selected": isSelected, id: "typeahead-item-" + index, onMouseEnter: onMouseEnter, onClick: onClick, isSelected: isSelected }, { children: [_jsx(IconContainer, { children: option.icon }), _jsx(Title, { children: option.title })] }), option.key));
5
+ return (_jsxs(ItemContainer, Object.assign({ tabIndex: -1, ref: option.setRefElement, role: "option", "aria-selected": isSelected, id: "typeahead-item-" + index, onMouseEnter: onMouseEnter, onClick: onClick, isSelected: isSelected }, { children: [_jsx(IconContainer, Object.assign({ className: option.iconContainerClassName }, { children: option.icon })), _jsx(Title, { children: option.title })] }), option.key));
6
6
  }
7
7
  const ItemContainer = styled.li `
8
8
  display: flex;
@@ -10,6 +10,7 @@ const ItemContainer = styled.li `
10
10
  align-items: center;
11
11
  gap: 10px;
12
12
  width: 100%;
13
+ cursor: pointer;
13
14
 
14
15
  ${({ isSelected }) => isSelected && "background: #eee;"}
15
16
  `;
@@ -6,17 +6,19 @@
6
6
  *
7
7
  */
8
8
  /** @jsxImportSource @emotion/react */
9
- /// <reference types="react" />
10
9
  import { MenuOption } from "@lexical/react/LexicalTypeaheadMenuPlugin";
11
10
  import { LexicalEditor } from "lexical";
11
+ import { ReactElement } from "react";
12
12
  export declare class ComponentPickerOption extends MenuOption {
13
13
  title: string;
14
- icon?: JSX.Element;
14
+ icon?: ReactElement;
15
+ iconContainerClassName?: string;
15
16
  keywords: Array<string>;
16
17
  keyboardShortcut?: string;
17
18
  onSelect: (queryString: string) => void;
18
19
  constructor(title: string, options: {
19
- icon?: JSX.Element;
20
+ icon?: ReactElement;
21
+ iconContainerClassName?: string;
20
22
  keywords?: Array<string>;
21
23
  keyboardShortcut?: string;
22
24
  onSelect: (queryString: string) => void;
@@ -32,6 +32,7 @@ export class ComponentPickerOption extends MenuOption {
32
32
  this.title = title;
33
33
  this.keywords = options.keywords || [];
34
34
  this.icon = options.icon;
35
+ this.iconContainerClassName = options.iconContainerClassName;
35
36
  this.keyboardShortcut = options.keyboardShortcut;
36
37
  this.onSelect = options.onSelect.bind(this);
37
38
  }
@@ -7,7 +7,7 @@ import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
7
7
  import { setFloatingElemPositionForLinkEditor } from "../../utils/setFloatingElemPositionForLinkEditor";
8
8
  import { $findMatchingParent, mergeRegister } from "@lexical/utils";
9
9
  import styled from "@emotion/styled";
10
- import { CloseFillIcon, CheckLineIcon, EditLineIcon, DeleteBinLineIcon } from "../../../../icons";
10
+ import { CloseFillIcon, CheckLineIcon, EditLineIcon, DeleteBinLineIcon, } from "../../../../icons";
11
11
  import Input from "../../../../components/Input";
12
12
  export function FloatingLinkEditor(props) {
13
13
  const { editor, isLink, setIsLink, anchorElem, isLinkEditMode, setIsLinkEditMode, } = props;
@@ -6,7 +6,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  *
8
8
  */
9
- import { $isAutoLinkNode, $isLinkNode, } from "@lexical/link";
9
+ import { $isAutoLinkNode, $isLinkNode } from "@lexical/link";
10
10
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
11
11
  import { $findMatchingParent, mergeRegister } from "@lexical/utils";
12
12
  import { $getSelection, $isRangeSelection, CLICK_COMMAND, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_LOW, SELECTION_CHANGE_COMMAND, } from "lexical";
@@ -2,6 +2,13 @@ import { Theme } from "@emotion/react";
2
2
  export declare function getTheme(theme: Theme): {
3
3
  paragraph: string;
4
4
  quote: string;
5
+ coloredQuote: {
6
+ grey: string;
7
+ red: string;
8
+ yellow: string;
9
+ blue: string;
10
+ green: string;
11
+ };
5
12
  heading: {
6
13
  h1: string;
7
14
  h2: string;
@@ -23,8 +23,80 @@ export function getTheme(theme) {
23
23
 
24
24
  margin: 16px 0;
25
25
  padding-left: 12px;
26
- border-left: 2px solid ${theme.color.container.marbleContainer};
26
+ border-left: 4px solid ${theme.color.container.marbleContainer};
27
27
  `,
28
+ coloredQuote: {
29
+ grey: css `
30
+ color: ${theme.color.container.marbleOnContainer};
31
+
32
+ /* Default/Paragraph/14px-Rg */
33
+ font-family: ${theme.fontFamily.ui};
34
+ font-size: 14px;
35
+ font-style: normal;
36
+ font-weight: 400;
37
+ line-height: 20px; /* 142.857% */
38
+
39
+ margin: 16px 0;
40
+ padding-left: 12px;
41
+ border-left: 4px solid ${theme.color.container.marbleContainer};
42
+ `,
43
+ red: css `
44
+ color: ${theme.color.container.dangerOnContainer};
45
+
46
+ /* Default/Paragraph/14px-Rg */
47
+ font-family: ${theme.fontFamily.ui};
48
+ font-size: 14px;
49
+ font-style: normal;
50
+ font-weight: 400;
51
+ line-height: 20px; /* 142.857% */
52
+
53
+ margin: 16px 0;
54
+ padding-left: 12px;
55
+ border-left: 4px solid ${theme.color.container.dangerContainer};
56
+ `,
57
+ yellow: css `
58
+ color: ${theme.color.container.warningOnContainer};
59
+
60
+ /* Default/Paragraph/14px-Rg */
61
+ font-family: ${theme.fontFamily.ui};
62
+ font-size: 14px;
63
+ font-style: normal;
64
+ font-weight: 400;
65
+ line-height: 20px; /* 142.857% */
66
+
67
+ margin: 16px 0;
68
+ padding-left: 12px;
69
+ border-left: 4px solid ${theme.color.container.warningContainer};
70
+ `,
71
+ blue: css `
72
+ color: ${theme.color.container.primaryOnContainer};
73
+
74
+ /* Default/Paragraph/14px-Rg */
75
+ font-family: ${theme.fontFamily.ui};
76
+ font-size: 14px;
77
+ font-style: normal;
78
+ font-weight: 400;
79
+ line-height: 20px; /* 142.857% */
80
+
81
+ margin: 16px 0;
82
+ padding-left: 12px;
83
+ border-left: 4px solid ${theme.color.container.primaryContainer};
84
+ `,
85
+ green: css `
86
+ color: ${theme.color.container.successOnContainer};
87
+
88
+ /* Default/Paragraph/14px-Rg */
89
+ font-family: ${theme.fontFamily.ui};
90
+ font-size: 14px;
91
+ font-style: normal;
92
+ font-weight: 400;
93
+ line-height: 20px; /* 142.857% */
94
+
95
+ margin: 16px 0;
96
+ padding-left: 12px;
97
+ border-left: 4px solid ${theme.color.container.successContainer};
98
+ `,
99
+ },
28
100
  heading: {
29
101
  h1: css `
30
102
  color: ${theme.color.foreground.neutralBase};
@@ -1,7 +1,9 @@
1
1
  import React from "react";
2
2
  import { ButtonProps } from "../..";
3
- export interface SegmentedControlButtonProps extends Omit<ButtonProps, "color" | "size"> {
3
+ export interface SegmentedControlButtonProps extends Omit<ButtonProps, "color" | "size" | "startIcon" | "endIcon"> {
4
4
  value: string;
5
+ startIcon?: React.ReactNode | ((isActive: boolean) => React.ReactNode);
6
+ endIcon?: React.ReactNode | ((isActive: boolean) => React.ReactNode);
5
7
  }
6
8
  /**
7
9
  * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?node-id=181%3A89883)
@@ -20,7 +20,7 @@ import { Button, shadows } from "../..";
20
20
  * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?node-id=181%3A89883)
21
21
  */
22
22
  export const SegmentedControlButton = React.forwardRef(function SegmentedControlButton(props, ref) {
23
- const { onClick } = props, other = __rest(props, ["onClick"]);
23
+ const { onClick, startIcon, endIcon } = props, other = __rest(props, ["onClick", "startIcon", "endIcon"]);
24
24
  const context = useContext(SegmentedControlGroupPropsContext);
25
25
  const handleClick = (e) => {
26
26
  var _a;
@@ -30,20 +30,17 @@ export const SegmentedControlButton = React.forwardRef(function SegmentedControl
30
30
  const isActive = context.multiSelect
31
31
  ? context.value.includes(props.value)
32
32
  : context.value === props.value;
33
- return (_jsx(StyledButton, Object.assign({}, other, { ref: ref, isActive: isActive, color: isActive ? "white" : "textNeutral", size: context.size, onClick: handleClick })));
33
+ return (_jsx(StyledButton, Object.assign({}, other, { ref: ref, startIcon: typeof startIcon === "function" ? startIcon(isActive) : startIcon, endIcon: typeof endIcon === "function" ? endIcon(isActive) : endIcon, isActive: isActive, color: isActive ? "white" : "textNeutral", size: context.size, onClick: handleClick })));
34
34
  });
35
35
  const StyledButton = styled(Button, {
36
36
  shouldForwardProp: (prop) => prop !== "isActive",
37
- })(({ theme, isActive }) => css `
37
+ })(({ isActive }) => css `
38
38
  display: flex;
39
39
  justify-content: center;
40
40
  flex-grow: 1;
41
41
  ${isActive &&
42
42
  `
43
43
  box-shadow: ${shadows.shadow04};
44
- svg {
45
- color: ${theme.color.foreground.primary};
46
- }
47
44
  span {
48
45
  font-weight: 700;
49
46
  }
@@ -1,7 +1,8 @@
1
1
  import React from "react";
2
2
  import { SquareButtonProps } from "../..";
3
- export interface SegmentedControlSquareButtonProps extends Omit<SquareButtonProps, "color" | "size"> {
3
+ export interface SegmentedControlSquareButtonProps extends Omit<SquareButtonProps, "color" | "size" | "icon"> {
4
4
  value: string;
5
+ icon: React.ReactNode | ((isActive: boolean) => React.ReactNode);
5
6
  }
6
7
  /**
7
8
  * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?node-id=181%3A89883)
@@ -20,7 +20,7 @@ import { SegmentedControlGroupPropsContext } from "./SegmentedControlGroupPropsC
20
20
  * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?node-id=181%3A89883)
21
21
  */
22
22
  export const SegmentedControlSquareButton = React.forwardRef(function SegmentedControlSquareButton(props, ref) {
23
- const { onClick } = props, other = __rest(props, ["onClick"]);
23
+ const { onClick, icon } = props, other = __rest(props, ["onClick", "icon"]);
24
24
  const context = useContext(SegmentedControlGroupPropsContext);
25
25
  const handleClick = () => {
26
26
  var _a;
@@ -30,20 +30,17 @@ export const SegmentedControlSquareButton = React.forwardRef(function SegmentedC
30
30
  const isActive = context.multiSelect
31
31
  ? context.value.includes(props.value)
32
32
  : context.value === props.value;
33
- return (_jsx(StyledSquareButton, Object.assign({}, other, { ref: ref, isActive: isActive, color: isActive ? "white" : "icon", size: context.size, fullWidth: context.fullWidth, onClick: handleClick })));
33
+ return (_jsx(StyledSquareButton, Object.assign({}, other, { ref: ref, isActive: isActive, icon: typeof icon === "function" ? icon(isActive) : icon, color: isActive ? "white" : "icon", size: context.size, fullWidth: context.fullWidth, onClick: handleClick })));
34
34
  });
35
35
  const StyledSquareButton = styled(SquareButton, {
36
36
  shouldForwardProp: (prop) => prop !== "isActive",
37
- })(({ theme, isActive }) => css `
37
+ })(({ isActive }) => css `
38
38
  display: flex;
39
39
  justify-content: center;
40
40
  flex-grow: 1;
41
41
  ${isActive &&
42
42
  `
43
- box-shadow: ${shadows.shadow04};
44
- svg {
45
- color: ${theme.color.foreground.primary};
46
- }
43
+ box-shadow: ${shadows.shadow04};
47
44
  span {
48
45
  font-weight: 700;
49
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,