@team-monolith/cds 1.7.0 → 1.8.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 (50) hide show
  1. package/dist/patterns/Dropdown/Dropdown.d.ts +36 -0
  2. package/dist/patterns/Dropdown/Dropdown.js +43 -0
  3. package/dist/patterns/Dropdown/DropdownContext.d.ts +11 -0
  4. package/dist/patterns/Dropdown/DropdownContext.js +9 -0
  5. package/dist/patterns/Dropdown/DropdownItem/DropdownItem.d.ts +50 -0
  6. package/dist/patterns/Dropdown/DropdownItem/DropdownItem.js +128 -0
  7. package/dist/patterns/Dropdown/DropdownItem/index.d.ts +2 -0
  8. package/dist/patterns/Dropdown/DropdownItem/index.js +2 -0
  9. package/dist/patterns/Dropdown/DropdownItem/selected.d.ts +8 -0
  10. package/dist/patterns/Dropdown/DropdownItem/selected.js +33 -0
  11. package/dist/patterns/Dropdown/DropdownMenu/DropdownMenu.d.ts +37 -0
  12. package/dist/patterns/Dropdown/DropdownMenu/DropdownMenu.js +92 -0
  13. package/dist/patterns/Dropdown/DropdownMenu/index.d.ts +2 -0
  14. package/dist/patterns/Dropdown/DropdownMenu/index.js +2 -0
  15. package/dist/patterns/Dropdown/DropdownMenu/style.d.ts +8 -0
  16. package/dist/patterns/Dropdown/DropdownMenu/style.js +103 -0
  17. package/dist/patterns/Dropdown/index.d.ts +2 -0
  18. package/dist/patterns/Dropdown/index.js +2 -0
  19. package/dist/patterns/LexicalEditor/LexicalEditor.js +2 -1
  20. package/dist/patterns/LexicalEditor/Plugins.js +2 -1
  21. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/FormAnswer.d.ts +9 -0
  22. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/FormAnswer.js +43 -0
  23. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/FormCharacterCount.d.ts +8 -0
  24. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/FormCharacterCount.js +12 -0
  25. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/FormPlaceholder.d.ts +6 -0
  26. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/FormPlaceholder.js +9 -0
  27. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/index.d.ts +3 -0
  28. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/index.js +3 -0
  29. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.d.ts +16 -0
  30. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +170 -0
  31. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/ProblemInputNode.d.ts +19 -0
  32. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/ProblemInputNode.js +68 -0
  33. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/index.d.ts +2 -0
  34. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/index.js +2 -0
  35. package/dist/patterns/LexicalEditor/nodes/index.d.ts +3 -0
  36. package/dist/patterns/LexicalEditor/nodes/index.js +3 -0
  37. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +8 -5
  38. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.d.ts +1 -1
  39. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +140 -18
  40. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.js +12 -6
  41. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +5 -1
  42. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +58 -4
  43. package/dist/patterns/LexicalEditor/plugins/ProblemInputPlugin/index.d.ts +5 -0
  44. package/dist/patterns/LexicalEditor/plugins/ProblemInputPlugin/index.js +21 -0
  45. package/dist/patterns/LexicalEditor/theme.d.ts +1 -0
  46. package/dist/patterns/LexicalEditor/theme.js +6 -0
  47. package/dist/patterns/SegmentedControl/SegmentedControlGroup.js +1 -1
  48. package/package.json +3 -2
  49. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/getContextMenuOptions.d.ts +0 -6
  50. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/getContextMenuOptions.js +0 -197
@@ -0,0 +1,36 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import React from "react";
3
+ import { DropdownMenuProps } from "./DropdownMenu";
4
+ import { SerializedStyles } from "@emotion/react";
5
+ import { ButtonColor, ButtonSize } from "../../components/Button";
6
+ export interface DropdownProps {
7
+ className?: string;
8
+ component?: React.ElementType;
9
+ /** 버튼 비활성화 여부 */
10
+ disabled?: boolean;
11
+ /** 버튼 컴포넌트 색상 */
12
+ color?: ButtonColor;
13
+ /** 버튼 컴포넌트 크기 */
14
+ size?: ButtonSize;
15
+ /** 버튼 컴포넌트 내 좌측에 표기될 아이콘 */
16
+ startIcon?: React.ReactNode;
17
+ /** 버튼 컴포넌트 내 우측에 표기될 아이콘 */
18
+ endIcon?: React.ReactNode;
19
+ /** button에 적용되는 css 스타일 */
20
+ buttonCss?: SerializedStyles;
21
+ /** 버튼 컴포넌트 내 표기될 문자열 */
22
+ label: string;
23
+ /** 버튼 클릭 시 호출될 콜백 함수 */
24
+ onClick?: React.MouseEventHandler<HTMLButtonElement>;
25
+ /** 드롭다운 메뉴의 props */
26
+ menuProps?: Omit<DropdownMenuProps, "anchorEl" | "children" | "itemState" | "setItemState">;
27
+ /** 드롭다운 메뉴의 children */
28
+ children: React.ReactNode;
29
+ /** 드롭다운 메뉴 아이템 클릭 시 메뉴가 닫히는지 여부 */
30
+ closeOnItemClick?: boolean;
31
+ }
32
+ /**
33
+ * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?type=design&node-id=203-95329&t=FwczLZ1IVvskUVbT-0)
34
+ */
35
+ declare const Dropdown: React.ForwardRefExoticComponent<DropdownProps & React.RefAttributes<any>>;
36
+ export default Dropdown;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { forwardRef, useRef, useState } from "react";
4
+ import DropdownMenu from "./DropdownMenu";
5
+ import Button from "../../components/Button";
6
+ import { ArrowDropDownFillIcon } from "../../icons";
7
+ /**
8
+ * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?type=design&node-id=203-95329&t=FwczLZ1IVvskUVbT-0)
9
+ */
10
+ const Dropdown = forwardRef((props, ref) => {
11
+ const { className, component: Component = "div", disabled, color = "primary", size = "medium", startIcon, endIcon = _jsx(ArrowDropDownFillIcon, {}), buttonCss, label, onClick, menuProps = {}, children, closeOnItemClick, } = props;
12
+ const buttonProps = {
13
+ disabled,
14
+ color,
15
+ size,
16
+ startIcon,
17
+ endIcon,
18
+ };
19
+ const menuRef = useRef(null);
20
+ const [open, setOpen] = useState(false);
21
+ const [itemState, setItemState] = useState(new Map());
22
+ const handleClick = (e) => {
23
+ onClick === null || onClick === void 0 ? void 0 : onClick(e);
24
+ setOpen(true);
25
+ };
26
+ const handleClose = (e) => {
27
+ var _a;
28
+ setOpen(false);
29
+ setItemState((prevState) => {
30
+ const newState = new Map(prevState);
31
+ newState.forEach((value, key) => {
32
+ newState.set(key, {
33
+ open: false,
34
+ checked: value.checked,
35
+ });
36
+ });
37
+ return newState;
38
+ });
39
+ (_a = menuProps === null || menuProps === void 0 ? void 0 : menuProps.onClose) === null || _a === void 0 ? void 0 : _a.call(menuProps, e);
40
+ };
41
+ return (_jsxs(Component, Object.assign({ className: className, ref: ref }, { children: [_jsx(Button, Object.assign({ css: buttonCss, ref: menuRef }, buttonProps, { label: label, onClick: handleClick, fullWidth: true })), _jsx(DropdownMenu, Object.assign({}, menuProps, { open: open, onClose: handleClose, anchorEl: menuRef.current, closeOnItemClick: closeOnItemClick, itemState: itemState, setItemState: setItemState }, { children: children }))] })));
42
+ });
43
+ export default Dropdown;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { ItemState } from "./DropdownItem";
3
+ declare const DropdownContext: React.Context<{
4
+ /** 최상위 드롭다운 메뉴의 open 여부 */
5
+ open: boolean;
6
+ onCloseOnItemClick?: ((e: React.MouseEvent<HTMLElement>) => void) | undefined;
7
+ nestedIndex: string;
8
+ itemState: ItemState;
9
+ setItemState: React.Dispatch<React.SetStateAction<ItemState>>;
10
+ }>;
11
+ export default DropdownContext;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ const DropdownContext = React.createContext({
3
+ open: false,
4
+ onCloseOnItemClick: undefined,
5
+ nestedIndex: "",
6
+ itemState: new Map(),
7
+ setItemState: () => { },
8
+ });
9
+ export default DropdownContext;
@@ -0,0 +1,50 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import { SerializedStyles } from "@emotion/react";
3
+ import * as React from "react";
4
+ import { DropdownMenuProps } from "../DropdownMenu";
5
+ import { CheckboxInputProps } from "../../../components/CheckboxInput";
6
+ export type DropdownItemType = "default" | "danger";
7
+ export type ItemState = Map<string, {
8
+ open: boolean;
9
+ checked?: "checked" | "partial" | null;
10
+ }>;
11
+ export interface DropdownItemProps {
12
+ className?: string;
13
+ labelCss?: SerializedStyles;
14
+ component?: React.ElementType;
15
+ /** 드롭다운 아이템 index */
16
+ index: number;
17
+ /** 드롭다운 아이템의 타입 */
18
+ type?: DropdownItemType;
19
+ /** 체크박스 노출여부 */
20
+ checkbox?: boolean;
21
+ /** 체크박스 설정 props. 초기 체크여부 및 체크박스 값 변경시 호출되는 함수 등을 지정합니다. */
22
+ checkboxProps?: CheckboxInputProps;
23
+ /** 시작 아이콘 설정 */
24
+ startIcon?: React.ReactNode;
25
+ /** 드롭다운 아이템의 라벨 */
26
+ label?: React.ReactNode;
27
+ /** 끝 아이콘 설정. 주어지지 않고, 서브메뉴가 있으면 ArrowRightSLineIcon으로 보여집니다. */
28
+ endIcon?: React.ReactNode;
29
+ /** 아이콘 색상을 결정할때, type에 따른 색상을 사용하지 않고, 아이콘의 고유 색을 사용합니다. */
30
+ preserveIconColor?: boolean;
31
+ /** 비활성화 여부 */
32
+ disabled?: boolean;
33
+ /** 드롭다운 아이템의 활성화 여부. 서브 메뉴가 노출될 때 true가 됩니다. */
34
+ active?: boolean;
35
+ /** 드롭다운 아이템 마우스 호버 시 호출될 콜백 함수 */
36
+ onMouseEnter?: (e: React.MouseEvent<HTMLElement>) => void;
37
+ /** 드롭다운 아이템 마우스 아웃 시 호출될 콜백 함수 */
38
+ onMouseLeave?: (e: React.MouseEvent<HTMLElement>) => void;
39
+ /** 드롭다운 아이템 클릭 시 호출될 콜백 함수 */
40
+ onClick?: (e: React.MouseEvent<HTMLElement>) => void;
41
+ /** 아이템이 가지는 드롭다운 메뉴 props.*/
42
+ subMenuProps?: Omit<DropdownMenuProps, "anchorEl" | "itemState" | "setItemState" | "children">;
43
+ /** 아이템이 가지는 드롭다운 메뉴의 서브 아이템들. 주어지지 않으면 서브메뉴가 없습니다. */
44
+ children?: React.ReactNode;
45
+ }
46
+ /**
47
+ * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?type=design&node-id=106-1900&t=FwczLZ1IVvskUVbT-0)
48
+ */
49
+ declare const DropdownItem: React.ForwardRefExoticComponent<DropdownItemProps & React.RefAttributes<any>>;
50
+ export default DropdownItem;
@@ -0,0 +1,128 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
13
+ /** @jsxImportSource @emotion/react */
14
+ import { css } from "@emotion/react";
15
+ import * as React from "react";
16
+ import styled from "@emotion/styled";
17
+ import { useContext, useRef } from "react";
18
+ import DropdownMenu from "../DropdownMenu";
19
+ import DropdownContext from "../DropdownContext";
20
+ import { getSelected, setSelected } from "./selected";
21
+ import CheckboxInput from "../../../components/CheckboxInput";
22
+ import { ArrowRightSLineIcon } from "../../../icons";
23
+ import { HOVER } from "../../../utils/hover";
24
+ const TYPE_TO_COLOR = (theme, type) => {
25
+ return {
26
+ default: theme.color.foreground.neutralBase,
27
+ danger: theme.color.foreground.danger,
28
+ }[type];
29
+ };
30
+ /**
31
+ * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?type=design&node-id=106-1900&t=FwczLZ1IVvskUVbT-0)
32
+ */
33
+ const DropdownItem = React.forwardRef((props, ref) => {
34
+ const { className, index, labelCss, component: Component = "div", type = "default", checkbox, checkboxProps = { checked: false }, startIcon, label, endIcon, preserveIconColor = false, disabled, active, onMouseEnter, onMouseLeave, onClick, subMenuProps, children } = props, other = __rest(props, ["className", "index", "labelCss", "component", "type", "checkbox", "checkboxProps", "startIcon", "label", "endIcon", "preserveIconColor", "disabled", "active", "onMouseEnter", "onMouseLeave", "onClick", "subMenuProps", "children"]);
35
+ const itemRef = useRef(null);
36
+ const { open, onCloseOnItemClick, nestedIndex, itemState, setItemState } = useContext(DropdownContext);
37
+ const absItemIndex = nestedIndex ? `${nestedIndex}-${index}` : `${index}`;
38
+ // 서브메뉴가 존재하는지 여부
39
+ const isSubMenuExist = Boolean(children);
40
+ // 서브메뉴 노출 여부
41
+ const isSubMenuShowed = isSubMenuExist && getSelected(itemState, absItemIndex);
42
+ // 드롭다운 아이템을 클릭하면, 같은 레벨에 있는 다른 아이템들은 모두 닫히도록 설정해야 합니다.
43
+ const handleClick = (e) => {
44
+ e.stopPropagation();
45
+ onClick === null || onClick === void 0 ? void 0 : onClick(e);
46
+ // 서브메뉴가 없는 드롭다운 아이템 클릭시 상위 메뉴가 닫히는 설정
47
+ if (onCloseOnItemClick && !isSubMenuExist) {
48
+ onCloseOnItemClick(e);
49
+ }
50
+ else {
51
+ setSelected(setItemState, absItemIndex);
52
+ }
53
+ };
54
+ return (_jsxs(_Fragment, { children: [_jsx(Component, Object.assign({ className: className, ref: ref, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onClick: handleClick }, other, { children: _jsxs(Item, Object.assign({ ref: itemRef, disabled: disabled, selected: isSubMenuShowed || Boolean(active) }, { children: [_jsxs(LeftWrapper, { children: [checkbox && (_jsx(StyledCheckboxInput, Object.assign({}, checkboxProps, { disabled: disabled, onClick: (e) => {
55
+ e.stopPropagation();
56
+ } }))), startIcon && (_jsx(IconDiv, Object.assign({ type: type, preserveIconColor: preserveIconColor }, { children: startIcon }))), _jsx(LabelDiv, Object.assign({ css: labelCss, type: type }, { children: label }))] }), endIcon && (_jsx(IconDiv, Object.assign({ type: type, preserveIconColor: preserveIconColor }, { children: endIcon }))), !endIcon && isSubMenuExist && (_jsx(IconDiv, Object.assign({ type: type, preserveIconColor: preserveIconColor }, { children: _jsx(ArrowRightSLineIcon, {}) })))] })) })), isSubMenuExist && (_jsx(DropdownContext.Provider, Object.assign({ value: {
57
+ open,
58
+ onCloseOnItemClick,
59
+ nestedIndex: absItemIndex,
60
+ itemState,
61
+ setItemState,
62
+ } }, { children: _jsx(DropdownMenu, Object.assign({}, subMenuProps, { open: isSubMenuShowed, anchorEl: itemRef.current, isNestedMenu: true, anchorOrigin: {
63
+ vertical: "top",
64
+ horizontal: "right",
65
+ }, itemState: itemState, setItemState: setItemState }, { children: children })) })))] }));
66
+ });
67
+ const StyledCheckboxInput = styled(CheckboxInput) `
68
+ margin: 4px;
69
+ `;
70
+ const IconDiv = styled.div(({ theme, type, preserveIconColor }) => css `
71
+ width: 14px;
72
+ height: 14px;
73
+ svg {
74
+ width: 14px;
75
+ height: 14px;
76
+
77
+ ${!preserveIconColor &&
78
+ css `
79
+ path {
80
+ fill: ${TYPE_TO_COLOR(theme, type)};
81
+ }
82
+ `}
83
+ }
84
+ `);
85
+ const LabelDiv = styled.div `
86
+ overflow: hidden;
87
+ text-overflow: ellipsis;
88
+ white-space: nowrap;
89
+ font-size: 14px;
90
+ font-weight: 400;
91
+ font-family: ${({ theme }) => theme.fontFamily.ui};
92
+ color: ${({ theme, type }) => TYPE_TO_COLOR(theme, type)};
93
+ `;
94
+ const LeftWrapper = styled.div `
95
+ display: flex;
96
+ align-items: center;
97
+ overflow: hidden;
98
+ white-space: nowrap;
99
+ gap: 4px;
100
+ `;
101
+ const Item = styled.div(({ theme, selected, disabled }) => css `
102
+ background-color: ${theme.color.background.neutralBase};
103
+ box-sizing: content-box;
104
+ height: 24px;
105
+ border-radius: 6px;
106
+ padding: 4px 8px;
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: space-between;
110
+ line-height: 20px;
111
+ cursor: pointer;
112
+
113
+ ${HOVER(css `
114
+ background-color: ${theme.color.background.neutralAlt};
115
+ `)}
116
+ ${selected &&
117
+ css `
118
+ background-color: ${theme.color.background.neutralAlt};
119
+ `}
120
+ ${disabled &&
121
+ css `
122
+ cursor: auto;
123
+ // mui MenuItem-disabled의 opacity: 0.38을 참고하여 적용합니다.
124
+ opacity: 0.38;
125
+ background-color: ${theme.color.background.neutralBase};
126
+ `}
127
+ `);
128
+ export default DropdownItem;
@@ -0,0 +1,2 @@
1
+ export { default } from "./DropdownItem";
2
+ export * from "./DropdownItem";
@@ -0,0 +1,2 @@
1
+ export { default } from "./DropdownItem";
2
+ export * from "./DropdownItem";
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { ItemState } from "./DropdownItem";
3
+ /** index에 해당하는 아이템의 open state를 리턴합니다. */
4
+ export declare function getSelected(state: ItemState, index: string): boolean;
5
+ /** index에 해당하는 아이템의 open state를 true로 업데이트합니다.
6
+ * 동시에, 같은 레벨의 다른 형제 아이템과 그 자식들의 open state를 false로 업데이트합니다.
7
+ */
8
+ export declare function setSelected(setState: React.Dispatch<React.SetStateAction<ItemState>>, index: string): void;
@@ -0,0 +1,33 @@
1
+ // 각 함수의 index인자는 모두 절대index입니다. (nestedIndex) ex) "3", "0-3-2", ...
2
+ /** index에 해당하는 아이템의 open state를 리턴합니다. */
3
+ export function getSelected(state, index) {
4
+ var _a, _b;
5
+ return (_b = (_a = state.get(index)) === null || _a === void 0 ? void 0 : _a.open) !== null && _b !== void 0 ? _b : false;
6
+ }
7
+ /** index에 해당하는 아이템의 open state를 true로 업데이트합니다.
8
+ * 동시에, 같은 레벨의 다른 형제 아이템과 그 자식들의 open state를 false로 업데이트합니다.
9
+ */
10
+ export function setSelected(setState, index) {
11
+ setState((prevState) => {
12
+ var _a;
13
+ const newState = new Map(prevState);
14
+ // 같은 레벨의 형제 아이템과 그 자식들을 닫기 위해 상위 index로 시작하는 모든 아이템을 닫아야 합니다.
15
+ // 상위 index : ex) index: "0-4-3" -> prevIndex: "0-4-"
16
+ const joinIndex = index.split("-").slice(0, -1).join("-");
17
+ const prevIndex = joinIndex ? joinIndex + "-" : "";
18
+ newState.forEach((value, key) => {
19
+ if (key.startsWith(prevIndex)) {
20
+ newState.set(key, {
21
+ open: false,
22
+ checked: value.checked,
23
+ });
24
+ }
25
+ });
26
+ // index에 해당하는 item을 엽니다.
27
+ newState.set(index, {
28
+ open: true,
29
+ checked: (_a = newState.get(index)) === null || _a === void 0 ? void 0 : _a.checked,
30
+ });
31
+ return newState;
32
+ });
33
+ }
@@ -0,0 +1,37 @@
1
+ import { SerializedStyles } from "@emotion/react";
2
+ import React from "react";
3
+ import { ModalProps } from "@mui/material";
4
+ import { OriginProps } from "./style";
5
+ import { ItemState } from "../DropdownItem";
6
+ export declare const DROPDOWN_MENU_WIDTH = 124;
7
+ export declare const DROPDOWN_MENU_MAX_HEIGHT = 160;
8
+ export interface DropdownMenuProps extends Omit<ModalProps, "open" | "onClose" | "children"> {
9
+ /** 드롭다운 메뉴의 커스텀 스타일 */
10
+ menuCss?: SerializedStyles;
11
+ /** 드롭다운 메뉴가 열리는지 여부 */
12
+ open?: boolean;
13
+ /** 드롭다운 메뉴의 위치참조 */
14
+ anchorEl: HTMLElement | null;
15
+ /** 드롭다운 메뉴가 닫힐 때 실행될 콜백함수 */
16
+ onClose?: (e: React.MouseEvent<HTMLElement>) => void;
17
+ /** 현재 드롭다운 메뉴가 중첩 드롭다운인지 여부. 최상위일 경우 false입니다.
18
+ * 최상위 메뉴만 백드롭이 생성됩니다.
19
+ */
20
+ isNestedMenu?: boolean;
21
+ /** 드롭다운 메뉴 원점이 anchor의 어디에 위치하는지 조정 */
22
+ anchorOrigin?: OriginProps;
23
+ /** 드롭다운 메뉴 원점이 메뉴 자신의 어디에 위치하는지 조정 */
24
+ transformOrigin?: OriginProps;
25
+ /** 드롭다운 메뉴 아이템 클릭 시 메뉴가 닫히는지 여부 */
26
+ closeOnItemClick?: boolean;
27
+ /** 드롭다운 메뉴의 드롭다운 아이템들 */
28
+ children: React.ReactNode;
29
+ /** 중첩 드롭다운 메뉴가 아닐 경우 주어주지 않아도 됩니다. */
30
+ itemState?: ItemState;
31
+ setItemState?: React.Dispatch<React.SetStateAction<ItemState>>;
32
+ }
33
+ /**
34
+ * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?type=design&node-id=106-1921&t=FwczLZ1IVvskUVbT-0)
35
+ */
36
+ declare const DropdownMenu: React.ForwardRefExoticComponent<Omit<DropdownMenuProps, "ref"> & React.RefAttributes<any>>;
37
+ export default DropdownMenu;
@@ -0,0 +1,92 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
13
+ /** @jsxImportSource @emotion/react */
14
+ import styled from "@emotion/styled";
15
+ import { css } from "@emotion/react";
16
+ import React, { useContext } from "react";
17
+ import { Modal as MuiModal, Grow as MuiGrow, Portal, } from "@mui/material";
18
+ import { ORIGIN_PROPS_TO_POSITION, ORIGIN_PROPS_TO_TRANSFORM, } from "./style";
19
+ import DropdownContext from "../DropdownContext";
20
+ import shadows from "../../../foundation/shadows";
21
+ export const DROPDOWN_MENU_WIDTH = 124;
22
+ export const DROPDOWN_MENU_MAX_HEIGHT = 160;
23
+ /**
24
+ * [피그마](https://www.figma.com/file/PnQp3tPxiCjgsPZfLUaUL1/Codle-PD-Kit---Patterns?type=design&node-id=106-1921&t=FwczLZ1IVvskUVbT-0)
25
+ */
26
+ const DropdownMenu = React.forwardRef((props, ref) => {
27
+ const { className, open = false, anchorEl, onClose = () => { }, isNestedMenu = false, anchorOrigin = {
28
+ vertical: "bottom",
29
+ horizontal: "left",
30
+ }, transformOrigin = {
31
+ vertical: "top",
32
+ horizontal: "left",
33
+ }, closeOnItemClick, children, menuCss, itemState = new Map(), setItemState = () => { } } = props, other = __rest(props, ["className", "open", "anchorEl", "onClose", "isNestedMenu", "anchorOrigin", "transformOrigin", "closeOnItemClick", "children", "menuCss", "itemState", "setItemState"]);
34
+ const { nestedIndex } = useContext(DropdownContext);
35
+ // 드롭다운 메뉴 위치 조정
36
+ const anchorRect = anchorEl === null || anchorEl === void 0 ? void 0 : anchorEl.getBoundingClientRect();
37
+ const menuStyle = [
38
+ ORIGIN_PROPS_TO_POSITION(anchorOrigin, anchorRect, isNestedMenu),
39
+ ORIGIN_PROPS_TO_TRANSFORM(transformOrigin),
40
+ ];
41
+ // 최상위 드롭다운 메뉴
42
+ if (!isNestedMenu) {
43
+ return (_jsx(DropdownContext.Provider, Object.assign({ value: {
44
+ open,
45
+ onCloseOnItemClick: closeOnItemClick ? onClose : undefined,
46
+ nestedIndex,
47
+ itemState,
48
+ setItemState,
49
+ } }, { children: _jsx(MuiModal, Object.assign({ className: className, open: open, onClose: onClose, slotProps: {
50
+ backdrop: {
51
+ style: {
52
+ backgroundColor: "transparent",
53
+ },
54
+ },
55
+ } }, other, { children: _jsx(Grow, Object.assign({ in: open }, { children: _jsx(Menu, Object.assign({ ref: ref, css: [menuCss, menuStyle] }, { children: children })) })) })) })));
56
+ }
57
+ // 중첩 드롭다운 메뉴
58
+ // Portal을 사용하여 DOM이 중첩되지 않고 최상단에 렌더링 됩니다.
59
+ // 드롭다운 메뉴에서 메뉴가 열리고 닫힐 때 애니메이션을 위해 Grow를 사용하고 있습니다.
60
+ // Grow에서는 transform속성이 있는데, 자식에 배치되는 Menu에서는 position: fixed 속성이 있습니다.
61
+ // position: fixed는 뷰포트를 기준으로 요소를 배치하는데, 부모가 transform속성을 지니면 fixed동작이 제대로 이루어지지 않습니다.
62
+ // 아이템이 열리고 닫힐 때 transform속성이 생기고, 그 아이템의 자식인 서브메뉴는 position: fixed이므로 문제가 발생합니다.
63
+ // https://stackoverflow.com/questions/2637058/position-fixed-doesnt-work-when-using-webkit-transform
64
+ // 그래서 중첩구조를 파괴하기 위해 Portal을 사용하여 중첩 드롭다운 메뉴를 최상단에 렌더링합니다.
65
+ return (_jsx(Portal, { children: _jsx(DropdownContext.Provider, Object.assign({ value: {
66
+ open,
67
+ onCloseOnItemClick: closeOnItemClick ? onClose : undefined,
68
+ nestedIndex,
69
+ itemState,
70
+ setItemState,
71
+ } }, { children: _jsx(Grow, Object.assign({ in: open }, { children: _jsx(Menu, Object.assign({ className: className, ref: ref, css: [menuCss, menuStyle] }, { children: children })) })) })) }));
72
+ });
73
+ const Grow = styled(MuiGrow)(css `
74
+ transform-origin: top left;
75
+ `);
76
+ const Menu = styled.div(({ theme }) => css `
77
+ position: fixed;
78
+ display: flex;
79
+ flex-direction: column;
80
+ gap: 2px;
81
+ padding: 4px;
82
+ background-color: ${theme.color.background.neutralBase};
83
+ box-shadow: ${shadows.shadow04};
84
+ border-radius: 8px;
85
+ width: ${DROPDOWN_MENU_WIDTH}px;
86
+ max-height: ${DROPDOWN_MENU_MAX_HEIGHT}px;
87
+ overflow-y: auto;
88
+ overflow-x: hidden;
89
+ // 최상위 메뉴의 mui modal 백드롭의 z-index는 1300입니다.
90
+ z-index: 1300;
91
+ `);
92
+ export default DropdownMenu;
@@ -0,0 +1,2 @@
1
+ export { default } from "./DropdownMenu";
2
+ export * from "./DropdownMenu";
@@ -0,0 +1,2 @@
1
+ export { default } from "./DropdownMenu";
2
+ export * from "./DropdownMenu";
@@ -0,0 +1,8 @@
1
+ export interface OriginProps {
2
+ vertical: "top" | "center" | "bottom";
3
+ horizontal: "left" | "center" | "right";
4
+ }
5
+ /** anchor를 기준으로 드롭다운 메뉴 원점 위치 스타일을 리턴합니다. */
6
+ export declare const ORIGIN_PROPS_TO_POSITION: (anchorOrigin: OriginProps, anchorRect: DOMRect | undefined, nested?: boolean) => import("@emotion/utils").SerializedStyles;
7
+ /** 드롭다운 메뉴 원점을 조정하는 스타일을 리턴합니다. */
8
+ export declare const ORIGIN_PROPS_TO_TRANSFORM: (transformOrigin: OriginProps) => import("@emotion/utils").SerializedStyles;
@@ -0,0 +1,103 @@
1
+ import { css } from "@emotion/react";
2
+ import { DROPDOWN_MENU_WIDTH } from "./DropdownMenu";
3
+ /** anchor를 기준으로 드롭다운 메뉴 원점 위치 스타일을 리턴합니다. */
4
+ export const ORIGIN_PROPS_TO_POSITION = (anchorOrigin, anchorRect, nested) => {
5
+ const { vertical, horizontal } = anchorOrigin;
6
+ if (!anchorRect)
7
+ return css `
8
+ display: none;
9
+ `;
10
+ const { top, bottom, left, right, width, height } = anchorRect;
11
+ return {
12
+ top: {
13
+ left: css `
14
+ top: ${top}px;
15
+ left: ${left}px;
16
+ margin-right: 12px;
17
+ `,
18
+ center: css `
19
+ top: ${top}px;
20
+ left: ${left + width / 2}px;
21
+ margin-bottom: 8px;
22
+ `,
23
+ right: css `
24
+ top: ${top}px;
25
+ left: ${left + (nested ? DROPDOWN_MENU_WIDTH : width)}px;
26
+ margin-left: 12px;
27
+ `,
28
+ },
29
+ center: {
30
+ left: css `
31
+ top: ${top + height / 2}px;
32
+ left: ${left}px;
33
+ margin-right: 12px;
34
+ `,
35
+ center: css `
36
+ top: ${top + height / 2}px;
37
+ left: ${left + width / 2}px;
38
+ margin-bottom: 8px;
39
+ `,
40
+ right: css `
41
+ top: ${top + height / 2}px;
42
+ left: ${right}px;
43
+ margin-left: 12px;
44
+ `,
45
+ },
46
+ bottom: {
47
+ left: css `
48
+ top: ${bottom}px;
49
+ left: ${left}px;
50
+ margin-top: 8px;
51
+ `,
52
+ center: css `
53
+ top: ${bottom}px;
54
+ left: ${left + width / 2}px;
55
+ margin-top: 8px;
56
+ `,
57
+ right: css `
58
+ top: ${bottom}px;
59
+ left: ${right}px;
60
+ margin-top: 8px;
61
+ `,
62
+ },
63
+ }[vertical][horizontal];
64
+ };
65
+ /** 드롭다운 메뉴 원점을 조정하는 스타일을 리턴합니다. */
66
+ export const ORIGIN_PROPS_TO_TRANSFORM = (transformOrigin) => {
67
+ const { vertical, horizontal } = transformOrigin;
68
+ return {
69
+ top: {
70
+ left: css `
71
+ transform: none;
72
+ `,
73
+ center: css `
74
+ transform: translateX(-50%) !important;
75
+ `,
76
+ right: css `
77
+ transform: translateX(-100%) !important;
78
+ `,
79
+ },
80
+ center: {
81
+ left: css `
82
+ transform: translateY(-50%) !important;
83
+ `,
84
+ center: css `
85
+ transform: translateX(-50%) translateY(-50%) !important;
86
+ `,
87
+ right: css `
88
+ transform: translateX(-100%) translateY(-50%) !important;
89
+ `,
90
+ },
91
+ bottom: {
92
+ left: css `
93
+ transform: translateY(-100%) !important;
94
+ `,
95
+ center: css `
96
+ transform: translateX(-50%) translateY(-100%) !important;
97
+ `,
98
+ right: css `
99
+ transform: translateX(-100%) translateY(-100%) !important;
100
+ `,
101
+ },
102
+ }[vertical][horizontal];
103
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./Dropdown";
2
+ export { default } from "./Dropdown";
@@ -0,0 +1,2 @@
1
+ export * from "./Dropdown";
2
+ export { default } from "./Dropdown";
@@ -10,7 +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
+ import { ColoredQuoteNode, ProblemInputNode } from "./nodes";
14
14
  function validateValue(value) {
15
15
  var _a, _b;
16
16
  if (value && typeof value !== "object") {
@@ -34,6 +34,7 @@ export function LexicalEditor(props) {
34
34
  namespace: "CodleLexicalEditor",
35
35
  onError: (error) => console.error(error),
36
36
  nodes: [
37
+ ProblemInputNode,
37
38
  HeadingNode,
38
39
  ListNode,
39
40
  ListItemNode,
@@ -27,6 +27,7 @@ import FloatingLinkEditorPlugin from "./plugins/FloatingLinkEditorPlugin";
27
27
  import useLexicalEditable from "@lexical/react/useLexicalEditable";
28
28
  import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
29
29
  import styled from "@emotion/styled";
30
+ import ProblemInputPlugin from "./plugins/ProblemInputPlugin";
30
31
  export default function Plugins(props) {
31
32
  const { className, onChange } = props;
32
33
  const isEditable = useLexicalEditable();
@@ -41,7 +42,7 @@ export default function Plugins(props) {
41
42
  onChange === null || onChange === void 0 ? void 0 : onChange(editorState.toJSON());
42
43
  },
43
44
  // ignore 하지 않으면 Form에서 수정하지 않았는데 Dirty로 처리됨.
44
- ignoreSelectionChange: true }), _jsx(AutoFocusPlugin, {}), isEditable && (_jsxs(_Fragment, { children: [_jsx(TabIndentationPlugin, {}), _jsx(ComponentPickerMenuPlugin, {}), _jsx(MarkdownShortcutPlugin, { transformers: CODLE_TRANSFORMERS }), _jsx(HistoryPlugin, {})] })), floatingAnchorElem && isEditable && (_jsxs(_Fragment, { children: [_jsx(ComponentAdderPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem, isLinkEditMode: isLinkEditMode, setIsLinkEditMode: setIsLinkEditMode })] })), !isEditable && _jsx(LexicalClickableLinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(HorizontalRulePlugin, {}), _jsx(ImagesPlugin, {}), _jsx(TablePlugin, {}), _jsx(LinkPlugin, {}), _jsx(ListMaxIndentLevelPlugin, { maxDepth: 5 })] }));
45
+ ignoreSelectionChange: true }), _jsx(AutoFocusPlugin, {}), isEditable && (_jsxs(_Fragment, { children: [_jsx(TabIndentationPlugin, {}), _jsx(ComponentPickerMenuPlugin, {}), _jsx(MarkdownShortcutPlugin, { transformers: CODLE_TRANSFORMERS }), _jsx(HistoryPlugin, {})] })), floatingAnchorElem && isEditable && (_jsxs(_Fragment, { children: [_jsx(ComponentAdderPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem, isLinkEditMode: isLinkEditMode, setIsLinkEditMode: setIsLinkEditMode })] })), !isEditable && _jsx(LexicalClickableLinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(HorizontalRulePlugin, {}), _jsx(ImagesPlugin, {}), _jsx(TablePlugin, {}), _jsx(LinkPlugin, {}), _jsx(ListMaxIndentLevelPlugin, { maxDepth: 5 }), _jsx(ProblemInputPlugin, {})] }));
45
46
  }
46
47
  const ScrollArea = styled.div `
47
48
  min-height: 150px;
@@ -0,0 +1,9 @@
1
+ import { Control } from "react-hook-form";
2
+ import { ProblemInputPayload } from "../InputComponent";
3
+ export interface FormAnswerProps {
4
+ index: number;
5
+ control: Control<ProblemInputPayload, any>;
6
+ onDelete?: () => void;
7
+ disabled?: boolean;
8
+ }
9
+ export declare function FormAnswer(props: FormAnswerProps): import("@emotion/react/jsx-runtime").JSX.Element;